---
title: "Upload Supplier Quote Attachment"
method: POST
path: "/api/v1/admin/supplier-quotes/{quote_id}/attachments"
tags: ["admin-supplier-quotes"]
---

# Upload Supplier Quote Attachment

`POST /api/v1/admin/supplier-quotes/{quote_id}/attachments`

Append an admin-uploaded attachment to a SupplierQuote (Phase 4).

The inbound agent persists supplier attachments automatically (see
``prepare_quote_attachment`` MCP tool), but two failure modes call
for a manual admin upload:

* The Anthropic Files API was down AND the local PDF fallback
  returned empty text (the agent escalated rather than persisted).
* The supplier sent a follow-up document (an invoice) on a thread
  that was already closed at the ``quoted`` state.

Behavior:

* Streams the file to R2 under the SAME prefix the inbound agent
  uses (``vendors/inbound-attachments/<gmail_message_id>/<filename>``)
  so the existing ``source-preview-url`` endpoint serves admin and
  agent uploads uniformly.
* Appends the R2 key to ``SupplierQuote.attachment_r2_keys`` AND the
  kind tag to ``SupplierQuote.attachment_kinds`` (parallel-array
  invariant). When the quote was created by the inbound agent
  BEFORE Phase 3 shipped, ``attachment_kinds`` may be empty; the
  handler synthesizes ``["quote"] * len(attachment_r2_keys)`` to
  backfill before appending the new tag.
* Returns the updated :class:`SupplierQuoteRead` so the FE can
  immediately re-render the attachment list without a second GET.

Enforces a 25MB cap matching the inbound agent's ceiling.

## Path parameters

- `quote_id` string, uuid, required

## Response `201`

Successful Response

- SupplierQuoteRead — Full detail response for ``GET /admin/supplier-quotes/{id}``. Joins the supplier contact's name + email at the schema layer so the FE doesn't have to make a second round-trip. PRO-113 V2: ``attachment_r2_keys`` is now exposed (previously hidden in V1 since admins kept PDFs in Gmail). The V2 Quote Correction View consumes this to render the source-preview panel for attachment- backed inbound replies. Frontend resolves R2 keys to presigned URLs via the dedicated ``/source-preview-url`` endpoint.
  - `id` string, uuid, required
  - `project_id` string, uuid, required
  - `supplier_contact_id` string, uuid, required
  - `supplier_contact_name` string, required
  - `supplier_contact_email` string, required
  - `gmail_thread_id` string, nullable
  - `source` string
  - `total_amount_cents` integer, nullable, required
  - `currency` string, required
  - `lead_time_days` integer, nullable, required
  - `valid_until` string, date-time, nullable, required
  - `line_items` object[], required
  - `notes` string, nullable, required
  - `source_email_body` string, nullable
  - `attachment_r2_keys` string[]
  - `attachment_kinds` string[]
  - `supplier_documents` SupplierQuoteDocumentRead[]
    - `extraction_status` string
    - `latest_extraction_id` string, uuid, nullable
    - `last_extracted_at` string, date-time, nullable
    - `has_reviewable_suggestion` boolean
    - `extraction_error_message` string, nullable
    - `id` string, uuid, required
    - `project_id` string, uuid, required
    - `supplier_contact_id` string, uuid, required
    - `supplier_quote_id` string, uuid, nullable
    - `vendor_thread_state_id` string, uuid, nullable
    - `source` string, required
    - `document_kind` string, required
    - `r2_key` string, nullable
    - `filename` string, nullable
    - `mime_type` string, nullable
    - `file_size_bytes` integer, nullable
    - `gmail_thread_id` string, nullable
    - `gmail_message_id` string, nullable
    - `latest_extraction` CommercialDocumentExtractionSummary — Compact extraction status for list/detail views.
      - `id` string, uuid, required
      - `status` string, required
      - `provider` string, required
      - `schema_version` string, required
      - `confidence` number, nullable
      - `aggregate_total_cents` integer, nullable
      - `currency` string, nullable
      - `max_lead_time_days` integer, nullable
      - `error_message` string, nullable
      - `started_at` string, date-time, nullable
      - `completed_at` string, date-time, nullable
      - `created_at` string, date-time, required
      - `updated_at` string, date-time, required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
  - `document_kind` string, nullable
  - `extraction_confidence` number, nullable
  - `needs_human_review` boolean, required
  - `reviewed_at` string, date-time, nullable, required
  - `reviewed_by` string, uuid, nullable, required
  - `accepted_at` string, date-time, nullable
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/prototyping/apis/prototyping-io-api.md) · [All operations](https://skmtc.dev/prototyping/apis/prototyping-io-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/prototyping/prototyping-io-api/revisions/f4a0079fbb57/schema)
