---
title: "Create Payment On Behalf"
method: POST
path: "/api/v1/admin/projects/{project_id}/payment"
tags: ["admin-projects"]
---

# Create Payment On Behalf

`POST /api/v1/admin/projects/{project_id}/payment`

Create a payment record for a project that has none.

The Payment-panel escape hatch for **force-advanced projects**: the
force-status override only mutates ``Project.status`` — it never
creates a ``payments`` row, so the panel is stuck in a dead "pending"
state and every payment action 404s. This attaches a payment so the
normal panel (attach PO / issue invoice / mark paid) lights up.

**Reconciliation override:** no approved-quote requirement. The admin
records the out-of-band settlement ``amount`` directly (the editable
field defaults to the latest quote total client-side); the payment is
linked to the project's **latest quote of any status** to satisfy the
non-null ``quote_id`` FK. A project with no quote at all returns a
**structured 400** — ``detail={"code": "no_quote", "message": ...}`` —
so the UI can guide the admin to create/submit a quote first.

Creates a ``manual`` (out-of-band — wire/check/ACH) record supporting
issue-invoice + mark-invoice-paid; no PO doc, no confirm-PO. The
entered ``amount`` is stamped verbatim as the settlement total (both
``amount`` and ``total_with_tax``, no tax breakout). Purchase-order
records are created via the separate ``upload-po-on-behalf`` flow;
``method`` only accepts ``"manual"`` here (the schema 422s anything
else).

Idempotent: a project that already has a payment returns **409**.
Audit-logged as ``payment_created_on_behalf`` with a
``reconciliation`` marker + the entered amount. Requires
``payments:create``.

## Path parameters

- `project_id` string, uuid, required

## Request body

- CreatePaymentOnBehalfRequest — Admin request to create a payment record for a project with none. Used when a project was force-advanced past the payment flow (the force-status override never creates a ``payments`` row) so the admin Payment panel is stuck in a dead "pending" state. This is a **reconciliation override**: it does NOT require an APPROVED/reviewed quote — a force-advanced project's quote is typically ``changes_requested`` / unreviewed. The admin records the out-of-band settlement ``amount`` directly (the editable field defaults to the latest quote's grand total client-side); the payment is linked to the project's latest quote (any status) to satisfy the non-null ``Payment.quote_id`` FK. A project with no quote at all is rejected with a 400 ``no_quote``. The entered ``amount`` is the **settlement total**: it is stamped as both ``Payment.amount`` and ``Payment.total_with_tax`` with no tax breakout (``tax_amount = NULL``) — no fabricated split from a stale quote. This endpoint creates a MANUAL (out-of-band — wire/check/ACH/paid- elsewhere) record that supports issue-invoice + mark-invoice-paid but has no PO doc and skips confirm-PO. Purchase-order records are created via the separate ``upload-po-on-behalf`` flow (the admin attaches the customer PO doc + number there), NOT here — routing a force-advanced project back through the PO submit path would regress it to AWAITING_CONFIRMATION and fire a spurious ``on_po_submitted`` notification. ``method`` is therefore constrained to ``"manual"``; a ``"purchase_order"`` body now 422s.
  - `method` 'manual', required
  - `amount` union, required
    - number
    - string
  - `order_placed_at` string, date, nullable

## Response `201`

Successful Response

- AdminPaymentDetailResponse — Full payment detail for admin view.
  - `id` string, uuid, required
  - `project_id` string, uuid, required
  - `quote_id` string, uuid, required
  - `user_id` string, uuid, required
  - `method` string, required
  - `status` string, required
  - `amount` string, required
  - `total_with_tax` string, nullable
  - `deposit_amount` string, nullable
  - `currency` string, required
  - `stripe_payment_intent_id` string, nullable
  - `po_number` string, nullable
  - `po_document_r2_key` string, nullable
  - `po_document_download_url` string, nullable
  - `shipping_address` string, nullable
  - `notes` string, nullable
  - `paid_at` string, date-time, nullable
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `user` UserSummary, required — Minimal user info for denormalized list views.
    - `id` string, uuid, required
    - `email` string, required
    - `first_name` string, required
    - `last_name` string, required
    - `company` string, nullable
  - `project_name` string, required
  - `invoice` InvoiceResponse — Invoice summary returned in payment detail views. ``invoice_number`` is the sequential accounting identifier (``INV-YYYY-NNNNN``) and stays stable in the DB for accounting reconciliation. ``display_number`` is the project-derived short form (``INV-{project_id_first8}``) shown in the UI and on PDFs so quote + invoice + project share a visually-consistent identifier. Backend resolves ``display_number`` at response build time — frontends should prefer it for display and only surface ``invoice_number`` if accounting context is needed.
    - `id` string, uuid, required
    - `payment_id` string, uuid, required
    - `project_id` string, uuid, required
    - `invoice_number` string, required
    - `display_number` string, nullable
    - `status` string, required
    - `amount` string, required
    - `subtotal` string, nullable
    - `tax_amount` string, nullable
    - `tax_rate` string, nullable
    - `tax_excluded` boolean
    - `additional_line_items_post_tax` AdditionalLineItemOutput[], nullable
      - `label` string, required
      - `quantity` integer, nullable — Optional display quantity for admin-entered misc line items. When set, the PDF shows the quantity alongside the label. The amount remains the line amount, not a unit price.
      - `amount` string, required
    - `currency` string, required
    - `issued_at` string, date-time, nullable
    - `paid_at` string, date-time, nullable
    - `invoice_pdf_url` string, nullable
    - `pdf_generated_at` string, date-time, nullable
    - `notes` string, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
  - `packing_slip` PackingSlipResponse — Packing slip summary returned in admin payment detail + standalone fetches. ``packing_slip_pdf_url`` is populated by the router via the storage chokepoint (5-minute presigned token, ``disposition=attachment``) when ``packing_slip_pdf_r2_key`` is present. Frontend uses it directly with ``window.open``.
    - `id` string, uuid, required
    - `project_id` string, uuid, required
    - `shipment_id` string, uuid, nullable
    - `packing_slip_number` string, required
    - `status` string, required
    - `packing_slip_pdf_url` string, nullable
    - `total_weight` string, nullable
    - `total_weight_unit` string, nullable
    - `package_count` integer, nullable
    - `package_snapshot` object[], nullable
    - `ship_date_override` string, date, nullable
    - `generated_at` string, date-time, nullable
    - `notes` string, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
  - `customer_name` string, required
  - `customer_email` string, required
  - `customer_billing_address` string, nullable
  - `submitted_by_admin_id` string, uuid, nullable
  - `submitted_by_label` string, nullable
  - `payment_terms_net_days` integer, nullable
  - `po_document_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
  - `po_document_validation` POExtractionValidationSummary — Admin-only PO extraction validation summary.
    - `extraction_id` string, uuid, required
    - `amount` POAmountValidation, required — Comparison between the extracted PO total and the sell-side order total.
      - `status` 'match' | 'mismatch' | 'matches_pre_tax_only' | 'unknown', required — Canonical PO extraction validation statuses for admin review.
      - `extracted_total_cents` integer, nullable
      - `expected_total_cents` integer, nullable
      - `expected_pre_tax_cents` integer, nullable
      - `delta_cents` integer, nullable
      - `currency` string, nullable
      - `message` string, required
    - `shipping_address` POAddressValidation — Comparison between extracted PO address text and current order address.
      - `status` 'match' | 'mismatch' | 'matches_pre_tax_only' | 'unknown', required — Canonical PO extraction validation statuses for admin review.
      - `field` string, required
      - `extracted_text` string, nullable
      - `current_text` string, nullable
      - `suggested_override` string, nullable
      - `message` string, required
    - `billing_address` POAddressValidation — Comparison between extracted PO address text and current order address.
      - `status` 'match' | 'mismatch' | 'matches_pre_tax_only' | 'unknown', required — Canonical PO extraction validation statuses for admin review.
      - `field` string, required
      - `extracted_text` string, nullable
      - `current_text` string, nullable
      - `suggested_override` string, nullable
      - `message` string, required
    - `extracted_po_number` string, nullable
    - `extracted_order_date` string, date, nullable
    - `extracted_payment_terms_net_days` integer, nullable
    - `extracted_requester_name` string, nullable
    - `extracted_requester_email` string, nullable
  - `order_placed_at_override` string, date-time, nullable
  - `order_placed_at` string, date-time, nullable

## 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)
