# Public Tenders Get

Fetch one tender by its public id.
The response carries a strong `ETag` over the tender content; pass it back
as `If-Match` on `PATCH /tenders/{tender_id}` for optimistic concurrency.
The ETag covers the *editable* content only, so it is unaffected by
`include_deleted` and `include_all_positions`, and a document fetched
any of those ways round-trips through `PATCH` (echoed deleted and
`is_lv_only` lines are ignored).

Endpoint: GET /tenders/{tender_id}
Version: 1.79.2

## Path parameters:

  - `tender_id` (string, required)

## Query parameters:

  - `include_deleted` (boolean)
    Also report lines that were **removed** from the tender: they come back flagged (`is_deleted` / `deleted_at`) instead of being omitted, so an ERP that already imported the offer can drop what is no longer on it. Removals are reported per **line**: taking one article off a position yields that article flagged next to the position's live lines, and a position that vanished completely comes back with *all* of its lines flagged — so a consumer keyed on the ordinal number can still tell the two apart. Deleted lines never count toward `totals`, and a position merely flagged 'not relevant' is not a deletion and stays hidden.

  - `include_all_positions` (boolean)
    Also report the ordinal numbers of the source LV that carry **no priced line** — one Mercura matched nothing to, one whose matched articles were all taken off again, or one flagged 'not relevant'. They come back flagged `is_lv_only`, one line each, carrying the LV's own `position_number` / `specification_text` / `quantity` / `unit` / `gaeb_position_type` and no article and no prices, so an ERP that imports the **whole Leistungsverzeichnis** (rather than only its priced part) receives every ordinal number in its LV order. LV-only lines never count toward `totals` and cannot be edited via `PATCH`.

## Header parameters:

  - `authorization` (string | null)
    Bearer <mrc_live_…> API key

## Response 200:

  - `200` (unknown)
    Successful Response

## Response 200 fields (application/json):

  - `id` (string, required)
    Public id; equals `request_id` for a tender.

  - `request_id` (string, required)
    Mercura request id backing this tender.

  - `status` (string, required)
    Lifecycle state of a tender (open enum — new values are SemVer MINOR).
A translated subset of the internal `RequestStatus`. The mapping lives in
`tenders.service._TENDER_STATUS_MAP` and a test asserts it covers every
internal value.
    Enum: "NEW", "IN_PROGRESS", "DONE", "CANCELLED", "PARSING", "PARSING_FAILED"

  - `completed_at` (string | null)
    When the tender was last completed (offer exported / sent). Refreshed on every completion; null if never completed. Use it with the `GET /tenders?completed_since=…` completion feed.

  - `customer_id` (string | null)
    Customer reference only — resolve full details via `GET /customers/{customer_id}`. Null when the tender has no customer.

  - `indirect_customer_id` (string | null)
    Indirect-customer reference (Mercura's *indirekter Kunde*) — resolve full details via `GET /customers/{customer_id}`. Distinct from `customer_id` (the direct customer / Sold-To). Null when the tender has no indirect customer.

  - `project_id` (string | null)
    Project reference only — resolve full details via `GET /projects/{project_id}`. Null when the tender has no project.

  - `project_name` (string | null)
    The project's display name, surfaced inline for convenience (same value as `name` on `GET /projects/{project_id}`). Null when the tender has no project. Read-only.

  - `object_number` (string | null)
    Your own object number (Objektnummer) for this tender — the identifier your ERP opens the job under, surfaced so you do not have to resolve it through `GET /projects/{project_id}`. It is the request's own number when one is maintained (an ERP that opens one object per incoming tender gives sibling tenders on the same project different numbers), otherwise the project's `object_number`. Null when neither carries one. Read-only: Mercura users maintain it in the app, and `POST /projects/{project_id}/acknowledgements` writes the project-level one.

  - `submission_date` (string | null)
    Bid-submission date (Submissions-/Abgabetermin) as originally supplied on the project — an ISO date or free text; null when unknown. This is the tender's submission deadline, **not** an offer-validity ("gültig bis") date.

  - `deadline` (string | null)
    Mercura's internal handling deadline for this request (typically the submission date minus a buffer) — an ISO date or free text; null when unset. Informational only; **not** the submission deadline and **not** an offer-validity date.

  - `user_email` (string | null)
    Email of the Mercura user **responsible** for this tender (its Bearbeiter / Sachbearbeiter) — not necessarily the user who triggered the export. Null when no responsible user is assigned.

  - `branch` (object)
    The Mercura branch (Niederlassung) a tender is assigned to. Read-only.

  - `branch.id` (string, required)
    Mercura branch id.

  - `branch.name` (string)
    Branch (Niederlassung) display name.

  - `request_custom_fields` (object | null)
    Organisation-defined custom fields on the request, keyed by display label. Null when none.

  - `user_custom_fields` (object | null)
    Organisation-defined custom fields on the responsible Mercura user (the `user_email` above), keyed by display label — e.g. a user's `SGI`. Null when the tender has no responsible user or the user has no custom fields.

  - `export_fields` (object | null)
    What the Mercura user entered in the ERP export dialog when releasing this tender — your organisation's own export fields (e.g. a customer or ship-to number to book the offer under), keyed by the field key configured for your organisation. Reflects the latest release made through the export dialog; null when the tender was never released that way or your organisation configures no such fields. Read-only.

  - `chapters` (array)

  - `chapters.id` (string, required)
    Opaque, stable chapter handle. Target for adding lines via PATCH.

  - `chapters.chapter_number` (string)
    GAEB chapter number (e.g. '01').

  - `chapters.name` (string)
    Chapter title.

  - `chapters.is_pretext` (boolean)
    A Vorbemerkung / pretext chapter (intro text, not priced).

  - `chapters.sequence` (integer)
    Where this chapter's heading stands in the order Mercura shows the tender in — the same running number as a line's `sequence`, so a chapter sorts right before its own content. Only compares within one response. Not part of the ETag. Read-only.

  - `chapters.remarks` (array)
    The chapter's own free texts, in the source LV's order — the Vorbemerkungen of a pretext chapter and the Hinweistexte of any other. They belong to the chapter rather than to an ordinal number, so they are not lines and carry no number, quantity or price. Empty when the chapter has none. Read-only.

  - `chapters.chapters` (array)
    Nested sub-chapters.

  - `chapters.positions` (array)
    Lines directly under this chapter.

  - `chapters.positions.id` (string, required)
    Opaque, re-match-stable line handle. Pass it back in `PATCH /tenders/{tender_id}` to edit this line; re-fetch on a `404`/`412` (a document reprocess recreates lines).

  - `chapters.positions.position_number` (string, required)
    GAEB display number (e.g. '01.10'). Read-only.

  - `chapters.positions.chapter_id` (string, required)
    Handle of the chapter this line sits in — the same value as the enclosing chapter's `id`, repeated here so an ERP that stores a flat line list can keep the chapter reference without walking the nesting. Read-only.

  - `chapters.positions.chapter_number` (string)
    GAEB number of the chapter this line sits in (e.g. '01.02'); empty when the chapter carries none. Read-only.

  - `chapters.positions.line_type` (string, required)
    What a line *is*, from `Selection.item_type`.
`ARTICLE` — matched to a catalogue article; `TEXT` — a free-text line;
`DIVERSE` — a priced free-text / lump-sum ("Pauschal") line.
    Enum: "ARTICLE", "TEXT", "DIVERSE"

  - `chapters.positions.labels` (array)
    Role/label overlay ({text, type}); several possible. Read-only.

  - `chapters.positions.labels.text` (string, required)
    Label display text (org-defined for CUSTOM labels).

  - `chapters.positions.labels.type` (string, required)
    Role overlay on a line, from `ArticleLabel.type`.
`CUSTOM` labels carry a meaningful org-defined `text` (product groups,
"Bestellware", …); the others are structural roles.
    Enum: "CUSTOM", "ALTERNATIVE", "ACCESSORY", "PARTS_LIST"

  - `chapters.positions.gaeb_position_type` (string)
    GAEB Positionsart, from `Position.gaeb_position_type` (or null).
`BASE` — Grundposition; `OPTIONAL_WITH_TOTAL` — Bedarfsposition mit
Gesamtbetrag; `OPTIONAL_WITHOUT_TOTAL` — Bedarfsposition ohne
Gesamtbetrag; `ALTERNATIVE` — Wahlposition. Read-only.
    Enum: "BASE", "OPTIONAL_WITH_TOTAL", "OPTIONAL_WITHOUT_TOTAL", "ALTERNATIVE"

  - `chapters.positions.article_number` (string | null)
    Catalogue article number — the number this line's article carries in **your** article master, and nothing else. Null when Mercura matched the line to no catalogue article: a free-text line, or a line taken from a supplier response that could not be resolved against the master data. Such a line reports what it does carry as `manufacturer_article_number` instead, so a number you cannot look up never arrives as an `article_number`. Required to *add* a line.

  - `chapters.positions.parent_line_id` (string | null)
    Set on an **article-note** line: the `id` of the line this note annotates (both sit on the same position). Null on every other line. Pass it back on an add to attach a note — see `PublicTenderPositionUpdate.parent_line_id`.

  - `chapters.positions.manufacturer_article_number` (string | null)
    Supplier/manufacturer article number (Lieferantenartikelnummer) as stored on the line; null when the line carries none. On a **free-text** line this is writable — see `PublicTenderPositionUpdate.manufacturer_article_number`; on an ARTICLE line it is read-only (the matched article's number).

  - `chapters.positions.manufacturer` (string | null)
    Manufacturer / supplier name (Hersteller/Lieferant) from the matched article, or null for a free-text line. Read-only.

  - `chapters.positions.supplier_name` (string | null)
    Supplier chosen on this line by the operator, or null. Unlike `manufacturer` this is not article-derived: it is set by hand where the article cannot name the supplier — above all on a DIVERSE line, whose collective article number stands for products from many vendors. Read-only.

  - `chapters.positions.supplier_number` (string | null)
    The chosen supplier's number in your system (the `external_id` you supplied with the supplier master, e.g. SAP LIFNR). Null when the operator typed a supplier that matched none of your suppliers. Read-only.

  - `chapters.positions.name` (string | null)
    The matched article's name — the article text maintained in Mercura (the title shown in the UI). Null for a free-text line. Read-only.

  - `chapters.positions.description` (string)
    Line description: the matched article's own `description` field, or the free-text for a text/DIVERSE line. Distinct from `name` (the article title).

  - `chapters.positions.description_lines` (array)
    `description` split into its lines, each without trailing whitespace, trailing empty lines dropped; always derived from `description` (empty when it is). For an ERP that keeps a line's text as fixed-length rows, e.g. SAP's two 40-character rows: map the rows one by one instead of splitting `description` yourself. Your organisation can have the text of DIVERSE lines entered as a fixed number of rows of a maximum length, so each row arrives as the operator typed it. Read-only.

  - `chapters.positions.additional_text` (string | null)
    Optional long-form note (Zusatztext), or null.

  - `chapters.positions.specification_title` (string | null)
    The source LV's own **heading** for this ordinal number (its Kurztext, e.g. 'Info PVC Energiekabel NYCY') — verbatim and independent of what Mercura matched to it, the counterpart to `specification_text`. A partner importing the Leistungsverzeichnis needs both: the heading names what the ordinal number is, the long text describes it. Every line sharing a `position_number` carries the same value (it belongs to the position, not the line). Null when the source LV carried no heading. Read-only.

  - `chapters.positions.specification_text` (string | null)
    The source LV's own long text for this ordinal number — the Ausschreibungstext as it was tendered, verbatim and independent of what Mercura matched to it. Distinct from `description`, which is the *matched article's* text; an ERP importing the Leistungsverzeichnis itself wants this one. Every line sharing a `position_number` carries the same value (it belongs to the position, not the line). Null when the source LV carried no long text. Read-only.

  - `chapters.positions.page_number` (integer | null)
    1-based page of the source document this line's ordinal number was read from — for request documents that reference their items by page rather than by number. It is the page's position in the file as Mercura received it, not a page label printed on it. Every line sharing a `position_number` carries the same value (it belongs to the position, not the line). Null when no page is known: GAEB and other structured files, and positions added by hand. Not part of the ETag. Read-only.

  - `chapters.positions.quantity` (number | null)

  - `chapters.positions.unit` (string | null)

  - `chapters.positions.custom_attributes` (object | null)
    The matched article's catalogue custom attributes — same shape as `GET /articles/{id}`'s `custom_attributes` (e.g. `sales_unit` and other source-system unit/extra fields). Null for a free-text line. Read-only.

  - `chapters.positions.list_price` (string | null)
    Per-unit list price (Brutto/gross), EUR.

  - `chapters.positions.discount_percentage` (string | null)
    Per-line manual discount percentage (0–100) on top of `list_price`.

  - `chapters.positions.net_price` (string | null)
    Per-unit net price, EUR (`Selection.net_price_ea`).

  - `chapters.positions.line_net` (string | null)
    `net_price * quantity`, 2 dp. Read-only (derived).

  - `chapters.positions.line_gross` (string | null)
    `list_price * quantity`, 2 dp. Read-only (derived).

  - `chapters.positions.is_counted_in_total` (boolean)
    False for lines excluded from the totals (alternatives, Bedarf ohne GP). Read-only.

  - `chapters.positions.partner_pricing_override_at` (string | null)
    UTC time a partner last overrode this line's price via PATCH, or null. Read-only.

  - `chapters.positions.is_deleted` (boolean)
    True when **this line** was removed from the tender — either the article was taken off the position, or the whole position went away (then every line of it is flagged). Only ever true in a response fetched with `include_deleted=true`. Deleted lines never count toward `totals`. Read-only.

  - `chapters.positions.deleted_at` (string | null)
    UTC time this line was removed, or null while it is live (also null on a legacy row deleted before removal times were recorded). Read-only.

  - `chapters.positions.is_lv_only` (boolean)
    True for a line that exists **only** because the request set `include_all_positions=true`: an ordinal number of the source LV with no priced line of its own — Mercura matched nothing to it, everything matched to it was taken off again, or it is flagged not relevant. Such a line reports the LV's own `position_number` / `specification_text` / `quantity` / `unit` / `gaeb_position_type` and carries no article and no prices; it never counts toward `totals`, is excluded from the ETag, and cannot be edited via `PATCH` (echoing it back is a no-op). Read-only.

  - `chapters.positions.sequence` (integer)
    Where this line stands in the order Mercura shows the tender in: a running number from 1, counted over chapters and lines together across the whole tender. Sort every line by it to get Mercura's order — needed because a chapter's own lines can stand **between** its sub-chapters (lines, then a sub-chapter, then more lines), which the separate `positions[]` and `chapters[]` arrays cannot express on their own. The numbers only compare within one response: `include_deleted` / `include_all_positions` add lines and shift them, and any edit to the tender can too, so never store it as a handle (use `id`). Not part of the ETag. Read-only.

  - `chapters.positions.annotations` (array)
    Review notes on this line's ordinal number (special conditions, open technical questions, …), written by Mercura's matching agent or a reviewer. They belong to the ordinal number, so every line sharing a `position_number` carries the same list, an `is_lv_only` line included. Empty when there are none. Not part of the ETag, and a change to them alone does not move the tender's `updated_at`. Read-only.

  - `chapters.positions.annotations.key` (string, required)
    Stable lower_snake_case identifier of the note, unique per ordinal number.

  - `chapters.positions.annotations.level` (string, required)
    Severity of a position annotation, from `PositionAnnotation.level`.
`INFO` — context; `WARNING` — needs a look before the offer goes out;
`CRITICAL` — can change the price or the scope of the whole tender.
    Enum: "INFO", "WARNING", "CRITICAL"

  - `chapters.positions.annotations.value` (string, required)
    Short display text of the note.

  - `chapters.positions.annotations.detail` (string | null)
    Optional longer explanation as Markdown (quotes from the LV, where it was found), or null.

  - `chapters.positions.annotations.url` (string | null)
    HTTP(S) link the note points to (e.g. a data sheet or a manufacturer page), or null. A note carries either `detail` or `url`, never both. Links into the Mercura app are not exposed.

  - `totals` (object, required)
    Aggregate amounts for the tender, all in EUR.
`positions_subtotal` is a **derived helper**: the sum of `line_net` across
lines with `is_counted_in_total = true` (i.e. excluding alternatives and
Bedarfspositionen ohne Gesamtbetrag / `OPTIONAL_WITHOUT_TOTAL`).
`net_total` / `gross_total` are the **authoritative** request totals — the
same figures Mercura prints on the PDF — preferring the request-level persisted
values (which a partner can override via PATCH — see
`PublicTenderTotalsUpdate`) and otherwise falling back to the line sums.
The two can legitimately differ: `net_total` is *after* the header
`additional_discount_percentage` (`positions_subtotal` is before it), and
Mercura's persisted total may include optional positions or other adjustments
that the counted-line subtotal omits. Treat `net_total` as the source of
truth for the amount owed and `positions_subtotal` as an informational sum.

  - `totals.positions_subtotal` (string, required)
    Sum of `line_net` across counted lines (excl. alternatives / `OPTIONAL_WITHOUT_TOTAL`), EUR. Derived helper — may differ from `net_total`.

  - `totals.additional_discount_percentage` (string | null)
    Header-level discount on top of position prices, 0–100.

  - `totals.net_total` (string, required)
    Authoritative final net (after the header discount), EUR — matches the PDF.

  - `totals.gross_total` (string, required)
    Authoritative gross total, EUR — matches the PDF.

  - `totals.partner_totals_override_at` (string | null)
    UTC time a partner last overrode `net_total`/`gross_total` via PATCH, or null. Read-only.

  - `erp_offer_id` (string | null)
    The partner-side ERP offer id, when known — from your acknowledgement's `external_id` or a previous Mercura-managed export; otherwise null.

  - `acknowledgement` (object)
    The partner's last reported ERP/CRM import outcome for a tender.
Used as the `acknowledgement` field on `PublicTenderOut` and as the
response body of `POST /tenders/{tender_id}/acknowledgements`.
`status` / `message` / `metadata` reflect the most recent
acknowledgement; `external_id` is kept once provided (last-provided-wins)
and mirrors the tender's `erp_offer_id`.
    Example: {"acknowledged_at":"2026-07-09T12:00:12Z","external_id":"AB-2026-5567","message":"Angebot in ERP angelegt.","metadata":{"erp_document":"AB-2026-5567","warehouse":"Z-100"},"status":"SUCCESS"}

  - `acknowledgement.status` (string, required)
    Outcome a partner reports after importing a tender's offer into their ERP/CRM.
Translated from the internal `RequestAckStatus` via
`tenders.service._TENDER_ACK_STATUS_MAP`. A *separate axis* from
`PublicTenderStatus`: that is Mercura-side lifecycle, this is the partner's
import result.
    Enum: "SUCCESS", "FAILED"

  - `acknowledgement.external_id` (string | null)
    Partner-side ERP/CRM id of the created offer / record, when provided.

  - `acknowledgement.message` (string | null)
    Free-text note about the outcome. On `FAILED` this is the failure reason (required); on `SUCCESS` an optional note.

  - `acknowledgement.metadata` (object | null)
    Optional extra references / raw payload the partner echoed back for traceability.

  - `acknowledgement.acknowledged_at` (string, required)
    When the partner reported this outcome (server receipt time).

  - `created_at` (string, required)

  - `updated_at` (string, required)

## Response 404:

  - `404` (unknown)
    Tender not found

## Response 422:

  - `422` (unknown)
    Validation Error

## Response 422 fields (application/json):

  - `detail` (array)

  - `detail.loc` (array, required)

  - `detail.msg` (string, required)

  - `detail.type` (string, required)

  - `detail.input` (any)

  - `detail.ctx` (object)

