# Public Tenders Create

Create a tender from uploaded files (async processing).
The programmatic twin of forwarding an LV / RFQ email to
`anfragen@lv.mercura.ai`: Mercura stores the files, picks the file
that drives parsing (GAEB before spreadsheet/CSV/DOCX/TXT before PDF
before image), extracts chapters and positions, matches them against
your catalogue, and lands the tender ready for review.
**Response.**
- `202 Accepted` with `JobAck { job_id, status_url }` on the first
call.
- `200 OK` with the *same* `JobAck` on an idempotent replay (same
`Idempotency-Key` + same form fields and file contents).

**What to do next.** Poll `GET /jobs/{job_id}` until `status` is
`COMPLETED` or `FAILED` (the terminal state is reached when
*processing* finishes, not when the upload is accepted). The resulting offer is then readable via `GET /tenders`.
**Correlating with your system.** Use `custom_fields` to carry your
own identifier (a CRM case number, an ERP reference) onto the tender at
creation time, and `branch` to route it. Both are validated against
your organisation's configuration *before* the job is created, so a
typo fails fast instead of landing an unidentifiable tender. Store the
`request_id` from `GET /jobs/{job_id}` on your side — it is the
stable handle for every later call (`GET`/`PATCH /tenders`).
**Idempotency.** Pass an `Idempotency-Key` header (≤ 255 chars). Same
key with a *different* body returns `422 IDEMPOTENCY_KEY_MISMATCH`.

Endpoint: POST /tenders
Version: 1.79.2

## Header parameters:

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

  - `Idempotency-Key` (string | null)
    Optional client-supplied key for safe retries; <= 255 chars

## Request fields (multipart/form-data):

  - `files` (array, required)
    One or more files, exactly as you would attach them to a forwarded email: GAEB (.d81/.d83/.d94/.p81/.p83/.p93/.p94/.x81/.x83/.x93/.x94/.onlv), .xlsx, .csv, .docx, .txt, .pdf, images (.png/.jpg/.jpeg/.gif/.bmp/.tiff/.webp), and .eml. An attached .eml is handled like a forwarded email: its attachments are added to the tender (its body is parsed if there is no parsable file), it appears on the tender's Email tab, and its sender is used to find the customer when customer_id / customer_email are not given. An .eml alone is enough. Posting an email whose Message-ID Mercura already holds returns 409 CONFLICT. Max 50 MB per file, 100 MB total, 20 files.

  - `name` (string | null)
    Display name for the tender in Mercura (the email channel uses the subject line). Defaults to the primary file's name.

  - `customer_id` (string | null)
    Your customer id — the same identifier the customers resource exposes as `customer_id`. Takes precedence over customer_email. An unknown id is not an error — the tender is created without a customer, exactly like an email from an unknown sender.

  - `customer_email` (string | null)
    Customer contact email, matched with the same rules the email channel applies to the sender address (exact contact match, domain pattern, unique inferred domain).

  - `context` (string | null)
    Free-text context that helps Mercura process the tender — e.g. the original email body, the kind of request, delivery notes, or guidance for article matching.

  - `branch` (string | null)
    Name of the Mercura branch (Niederlassung) this tender belongs to — exactly as it is configured in Mercura, matched case-insensitively. Routes the tender to the team that works that branch. An unknown name is a `400`: an unrouted tender is worse than a rejected upload.

  - `custom_fields` (string | null)
    JSON object of organisation-defined custom fields to stamp on the tender, keyed by the field's display **label** — e.g. `{"Case Number": "00012345"}`. The same labels the read side emits in `request_custom_fields`, so a value set here comes back unchanged on `GET /tenders/{tender_id}`. Unknown labels are a `400`.

## Response 200:

  - `200` (unknown)
    Idempotent replay — same job returned

## Response 200 fields (application/json):

  - `job_id` (string, required)

  - `status_url` (string, required)
    Relative URL to poll for this job's status

  - `warnings` (array)
    Non-fatal notices about the accepted payload — e.g. article translations for unsupported languages that were dropped. The job runs regardless; nothing listed here blocks processing. Deterministic for a given body, so idempotent replays return the same warnings.

## Response 202:

  - `202` (unknown)
    Accepted; poll status_url

## Response 400:

  - `400` (unknown)
    Validation failed (no parsable file, empty file, too many files, unknown branch, unknown custom-field label)

## Response 413:

  - `413` (unknown)
    A file exceeds 50 MB or the upload exceeds 100 MB in total

## Response 415:

  - `415` (unknown)
    A file has an unsupported extension

## Response 422:

  - `422` (unknown)
    Idempotency-Key reused with a different body

