Skip to main content

Push documents and spend

Feeding SpendOne from an upstream system: supplier invoices, quotes, and purchase requests that started somewhere else. The counterpart to pulling invoice data.

Everything here ends in the same place, an entity in the spend lifecycle, and everything goes through the same AI extraction and the same human confirmation step. Nothing you push posts to accounting on its own.

Invoices​

Three channels reach the platform, and only the third is an API call:

  1. The tenant inbox. A worker watches the configured mailbox, pulls PDF attachments and feeds them through extraction. Suppliers can forward straight to it. No integration work.
  2. Dashboard upload by a user.
  3. POST /invoices, multipart.
curl -X POST https://api.spendone.tech/api/v1/invoices \
-H "Authorization: Bearer $TOKEN" \
-F "po_code=PO-000123" \
-F "file=@invoice.pdf"

Both po_code and file are required: an invoice is created against a purchase order, not free-standing. file takes a PDF, an image, or e-invoice XML; a structured e-invoice is parsed as structured data rather than run through OCR.

A third field, ignore_duplicate_file, bypasses the duplicate-file guard. Send it only when you know the existing copy belongs to another order or has been replaced. The guard exists because the same PDF arriving twice is the single most common ingestion fault.

Extraction runs automatically. POST /invoices/{code}/reprocess re-runs it. After that the invoice moves through matching, tax validation, reconciliation and the booking proposal; the states are described in Pull invoice data.

No purchase order to hang it on? POST /purchase-requests/from-invoice turns an inbound invoice into a purchase request, which is the supported route when the procurement step was skipped.

Invoices that arrived without an order​

Documents landing in the tenant inbox with no resolvable order queue up as inbound attachments:

GET /api/v1/inbound-attachments
GET /api/v1/inbound-attachments/{id}
PUT /api/v1/inbound-attachments/{id}/assign-po
DELETE /api/v1/inbound-attachments/{id}

GET /me/inbound-attachments is the caller-scoped view of the same queue.

Extraction without creating anything​

Where you want the structured data but not the record (pre-filling your own form, validating a document before you commit it), the extraction endpoints stand alone:

POST /api/v1/extract/invoice
POST /api/v1/extract/order-confirmation
POST /api/v1/extract/delivery-slip
POST /api/v1/extract/quotes
POST /api/v1/extract/auto # classify and extract in one call

These take a JSON body with file_data (the file's bytes as a JSON array), not multipart, and they create nothing. What each document type yields is in AI Capabilities.

Purchase requests​

curl -X POST https://api.spendone.tech/api/v1/purchase-requests \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"purchase_flow_code": "standard",
"supplier_code": "SUP-000042",
"rationale": "Annual renewal of the design-tool licenses",
"currency": "EUR",
"line_items": [
{"product_name": "Design tool seat", "quantity": 10, "amount": 250.00, "vat_rate": 19}
]
}'

This creates a draft, and every field is optional in that state, so an integration can write what it has and let a person complete the rest. Two things worth setting from the start: purchase_flow_code, which is stamped on the draft and decides the approval rule and payment methods when it is submitted, and cost_center_splits where the spend is shared.

From a supplier quote instead:

POST /api/v1/purchase-requests/from-quote # multipart, the quote PDF
POST /api/v1/purchase-requests/from-quote/text # pasted quote text

Both pre-fill the draft from extraction, including subscription hints (billing frequency, term length) where the quote carries them.

Attachments go on separately: POST /purchase-requests/{code}/attachments.

Submitting​

POST /api/v1/purchase-requests/validate/{code} # server-side validation, changes nothing
POST /api/v1/purchase-requests/{code}/request-approval

Validate first. It runs the same checks the submit does and returns the field-level problems, which is a much better error surface than a rejected submit.

Once submitted, the approval engine resolves the approvers from the rules, the amount and the org unit's leadership. Your integration does not choose approvers. It can watch them:

GET /api/v1/approvals/tasks # tasks for the calling user
GET /api/v1/approvals/{code}/tasks # pending tasks on one case
POST /api/v1/approvals/{code}/tasks/{id}/approve
POST /api/v1/approvals/{code}/tasks/{id}/reject

An approved request becomes a purchase order on its own; there is no call to make. The order's own state machine is explicit POST actions, never PUT or DELETE: POST /purchase-orders/{code}/order, POST /purchase-orders/{code}/receive, POST /purchase-orders/{code}/complete, POST /purchase-orders/{code}/cancel, and the dispute trio POST /purchase-orders/{code}/dispute, POST /purchase-orders/{code}/resolve-dispute and POST /purchase-orders/{code}/cancel-dispute.

What the platform will not do for you​

  • It will not create a supplier from an extracted seller block. Extraction proposes a match against your master data and a human confirms it. Push suppliers deliberately, through POST /suppliers or the suppliers import kind.
  • It will not approve anything. Every financial transition is gated by an explicit user action or a deterministic rule an administrator configured.
  • It will not post to accounting. That is the export, and it runs on the tenant's configured method. See Accounting System Integration.

Permissions​

Creating invoices and driving the invoice workflow requires ACCOUNTANT. Raising purchase requests requires can_request on a STANDARD permission set, and doing it for somebody else additionally requires can_purchase_on_behalf. See Roles and Permissions.