---
title: "Upload Intake"
method: POST
path: "/companies/{company_id}/intake"
tags: ["intake"]
---

# Upload Intake

`POST /companies/{company_id}/intake`

## Request

- Base URL: `https://app.zeno-works.ch` (the document declares no server; this is the origin it was published from)
- URL: `https://app.zeno-works.ch/companies/{company_id}/intake`
- Auth: none declared

Accept one file of any type for routing (202).

- Accept-any: the type is decided by the router, never by the caller. An
  unroutable file becomes a ``failed`` queue row with a reason, not a 4xx here —
  the page shows it in the queue alongside its siblings.
- 413 if the upload exceeds the configured cap (25 MB default); 422 on empty bytes.
  These two stay synchronous because there is nothing to queue.
- Scoped: the caller must be a member of ``company_id`` (else 403).

## Path parameters

- `company_id` string, required

## Headers

- `authorization` string, nullable

## Cookies

- `access_token` string, nullable

## Response `202`

Successful Response

- IntakeUploadResponse: 202 envelope for one accepted file.
  - `intake` IntakeOut, required: One queue row.
    - `id` string, required
    - `company_id` string, required
    - `parent_id` string, nullable
    - `filename` string, required
    - `inner_path` string, nullable
    - `sha256` string, required
    - `size` integer, nullable
    - `status` 'queued' | 'routing' | 'awaiting_analysis' | 'expanding' | 'routed' | 'needs_input' | 'failed' | 'skipped', required: Lifecycle of one file dropped on the unified intake (doc/pipeline.md §"Unified intake"). ``queued`` (staged, awaiting the router) → ``routing`` (claimed by the ``DocumentIntakeWorker``) → one of the terminals. ``awaiting_analysis`` is the tier-3 parking state: the row created its ``Item`` and now waits for the analyzer's classification before deciding whether the document ALSO belongs in the accounting-document or bank-statement pipeline. ``needs_input`` means the deterministic probes could not name the destination with confidence — the file is still filed as a document, the user is merely offered the structured-import upgrade. ``expanding`` is the **archive** parking state (ZET-216): a ``.zip`` parent has fanned out into child rows and is waiting for them to finish. It is deliberately NOT terminal — the parent used to flip straight to ``routed`` the moment the children were *enqueued*, so ``get_intake(parent)`` reported terminal success over an import still in flight (measured: ``routed`` at +1.4 s with 0 of 12 members imported, drain done at +3.7 s). The parent leaves ``expanding`` only when every child has reached a terminal status.
    - `detected_kind` 'invoice' | 'bank_statement' | 'accounting_document' | 'accounting_import' | 'document' | 'archive' | 'unknown', required: Where an intake row was routed (its detected destination pipeline). ``document`` is the plain ``Item`` outcome (an invoice that is not yet classified, a letter, a contract — anything the Documents surfaces own). ``archive`` marks a ``.zip`` parent whose children carry the real routing. ``unknown`` is the pre-routing default and the ``needs_input`` resting value.
    - `detection_source` string, nullable
    - `targets` IntakeTargetOut[]
      - `type` string, required
      - `id` string, required
    - `already_imported` boolean, nullable
    - `error_code` string, nullable
    - `error_detail` string, nullable
    - `uploaded_by` string, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, nullable
    - `routed_at` string, date-time, nullable
    - `fault` IngestFaultOut: Why an ingest record failed or is parked, and what removes the cause.
      - `code` 'transient' | 'quota_held' | 'defect_fixed' | 'platform_defect' | 'bytes_gone' | 'too_large' | 'unreadable' | 'unexplained' | 'unsupported_type' | 'parse_error' | 'format_unknown' | 'not_a_statement' | 'no_account_id' | 'nothing_readable' | 'no_statement_found' | 'fiscal_year_undeterminable' | 'corrupt_archive' | 'empty_archive' | 'ambiguous_tabular' | 'analysis_failed' | 'statement_format_not_importable' | 'statement_beside_invoice' | 'statement_beside_card_bill' | 'accounting_document_not_importable' | 'statement_import_failed' | 'archive_incomplete' | 'encrypted' | 'corrupt' | 'limit_exceeded' | 'ratio' | 'depth_exceeded' | 'empty' | 'archive_no_connection' | 'archive_connection_disabled' | 'archive_reconnect_required' | 'archive_no_template' | 'archive_upload_failed' | 'triage_unrelated' | 'triage_needs_review' | 'triage_irrelevant', required: The closed vocabulary. The SPA has exactly one row per member (a test enforces it).
      - `retry` 'auto' | 'user' | 'none', required: Whether running the same operation again can change the outcome.
      - `actions` IngestFaultAction[], required
      - `params` object
  - `msg` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/zeno-works/apis/zeno-api.md) · [All operations](https://skmtc.dev/zeno-works/apis/zeno-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zeno-works/apis/zeno-api/revisions/1eb93dde146c?raw)
