Skip to content

Projects

A Project is the real-world construction project (or business opportunity) an incoming request belongs to. Multiple requests — a tender, its follow-up amendments, later related orders — usually roll up under one project. Every incoming LV becomes a tender that Mercura groups into a Project on your behalf, and the tender.completed / order.completed webhooks carry the project_id so partners can reconcile the exported result against the same project record on their side.

Each project carries:

  • id — the numeric identifier equal to the project_id field on the tender.completed / order.completed webhooks (stringified). Round-trip the value verbatim to GET /projects/{id}.
  • object_number — the ERP-facing grouping key. Not unique: Mercura groups multiple project rows that share the same object_number into one master project internally, so filtering by object_number may return several rows.
  • name and status — display name and lifecycle state (ACTIVE, PROCESSED, BID_SUBMITTED, CUSTOMER_LOST, CUSTOMER_WON_PENDING, CUSTOMER_WON_AWARDED_ELSEWHERE, CUSTOMER_WON_AWARDED_TO_US).
  • estimated_value / currency / submission_date bid metadata as captured on the LV.
  • responsible_user — the Mercura user (name + email) assigned to the project. Convenient for surfacing "assigned to Anna Schmidt" in a downstream inbox without a second lookup.
  • construction_site_address / planner_address / developer_address — up to three postal addresses associated with the project. Fields not set on the internal row surface as null.
  • custom_fields — organisation-defined key-value pairs. Keys and value shapes are governed by your organisation's custom-column configuration (Settings → Organisation → Custom fields), not by this API. Multi-select fields return the option labels (e.g. ["3030"]), not the internal option ids. Empty custom-field sets are omitted from the response entirely.
  • request_ids — the Mercura request ids linked to the project via the Request.project_id foreign key. Each id is request-keyed, so drill down to a tender with GET /tenders/{id} or to an order with GET /orders/{id}.
  • acknowledgement — your side's last reported ERP/CRM import outcome for this project, when one was recorded via POST /projects/{project_id}/acknowledgements. null when the project was never acknowledged. See Acknowledging a project below.

You can:

  • GET /projects — cursor-paginated list of projects for the authed organisation. Use modified_since for delta-sync catch-up.
  • GET /projects/{project_id} — fetch a single project. This is what the tender.completed / order.completed webhooks (see the Webhooks chapter) point you at when partners need project-level context (site address, custom fields) beyond what the tender or order itself carries. The response carries a strong ETag for optimistic concurrency on PATCH.
  • PATCH /projects/{project_id} — edit a project in place. The inbound counterpart of the otherwise Mercura-produced project: write your ERP's own commission / project number (and other metadata) back onto the Mercura object. See Updating a project below.
  • POST /projects/{project_id}/acknowledgements — report whether your ERP/CRM managed to create/import the project (SUCCESS with your own record id, or FAILED with a reason). A SUCCESS id is written onto the project's object_number too, so this is also the method-minimal way to reconcile identifiers when your middleware cannot issue the PATCH verb. See Acknowledging a project below.

And Mercura can tell you when to act:

  • project.exported — the webhook a Mercura user triggers with the Send to ERP button on the project page. It is the signal to create the project as an object in your system; you then fetch it with GET /projects/{project_id} and report the result with an acknowledgement. See Creating a project in your ERP below.

Updating a project

PATCH /projects/{project_id} writes back the fields your ERP owns. The canonical use is reconciling identifiers: your ERP creates a project / commission number and PATCHes it into object_number so every later tender.completed / order.completed webhook and GET /projects lines up with your own key.

Writable fields (all optional): object_number, name, status, estimated_value, currency, submission_date, custom_fields.

If object_number is the only field you need to write, you can skip PATCH entirely and send it as the external_id of an acknowledgement instead — see Acknowledging a project.

Semantics:

  • Partial. Send only what you change. An omitted field is left untouched; an explicit null clears that field. status is the one exception — it cannot be null (a project always has a lifecycle state), and an unknown status value is a 400.
  • object_number is a grouping key. Mercura derives the master-project grouping from this value at read time, so writing it simply moves the one project row you addressed into (or out of) a group — sibling rows that shared the old value are not rewritten.
  • custom_fields is label-keyed and shallow-merged. Use the same labels you receive on GET (and, for a multi-select, the same option labels). Keys you include are set; a key mapped to null clears that one field; keys you omit are left as they are. An unknown label — or an unknown option label on a multi-select — returns 400 VALIDATION_FAILED naming the offending keys, so a typo fails loudly instead of silently writing nothing.

Optimistic concurrency: read the project first, then echo the ETag you received as an If-Match header on the PATCH. If someone else changed the project in the meantime the ETag no longer matches and the request is rejected with 412 Precondition Failed — re-fetch and retry. Omit If-Match (or send If-Match: *) to skip the check. A no-op edit (an empty body, or values equal to the current ones) does not advance the project's updated_at.

PATCH /projects/90 HTTP/1.1
Authorization: Bearer <your-api-key>
If-Match: "3f8b6d21..."
Content-Type: application/json

{
  "object_number": "P-2026-00417",
  "status": "CUSTOMER_WON_AWARDED_TO_US",
  "custom_fields": { "ERP-Auftragsnummer": "SO-88231" }
}