Skip to main content

Submitting invoices

POST /v1/partner/invoices is the workhorse of the Supplier API. One call pushes an invoice (header, lines, taxes, charges, discounts, addresses, attachments) into the customer's processing queue and returns a stable invoice_token you can use to query status later.

This page covers the request shape, the field map, attachment rules, the three-step routing chain, the idempotency contract, the response, and every error you can expect.

For the auto-generated interactive reference, see Submit invoice (Partner). The page you're reading is the human guide.

Endpoint

POST /v1/partner/invoices
Host: api.apreceiving.com
X-API-Key: <your active key>
Idempotency-Key: <optional, your unique submission id, max 255 chars>
Content-Type: application/json

Mounted at https://api.apreceiving.com/api/v1/partner/invoices in production. See Environments for the test and dev hosts. The path is identical across all three; only the host changes.

The handler is a thin wrapper around the Customer API's /invoice-import route with three partner-only policies:

  • Workflow is forced to full_enrichment. Any workflow value in the body is ignored. Suppliers do not choose how their invoice is enriched; the customer's processing rules apply.
  • Attachments are base64 only. The Customer API also accepts { document_token, type } references to pre-uploaded files. The partner endpoint rejects that shape with a 400, since a supplier cannot reference an upload they did not perform.
  • Unknown routing is a soft fallback, not a hard error. If your receiving_inbox or external_identifier does not match anything, Nuntiq routes to the customer's default inbox, sets a Warning header, and returns 200. Invoice loss is worse than mis-routing.

Request body

The body has up to four top-level keys. Only invoice is required.

{
"receiving_inbox": "invoices@acme.apreceiving.com",
"external_identifier": "ACME-001",
"attachments": [ /* base64 attachments, see below */ ],
"invoice": {
"header": { /* invoice-level fields */ },
"lines": [ /* line items */ ],
"addresses": [ /* REMITTO, SUPPLIER, SHIPTO, BILLTO */ ],
"taxes": [ /* tax lines */ ],
"charges": [ /* freight, handling, etc. */ ],
"discounts": [ /* early-payment discounts */ ]
}
}
Top-level keyTypeRequiredNotes
receiving_inboxstringnoDirect routing target. See Routing.
external_identifierstringnoAlias for an inbox. Used when receiving_inbox is missing or unknown.
attachmentsarraynoInline base64 only. See Attachments.
invoiceobjectyesThe invoice itself. invoice.header is required in practice; the others are optional.

Invoice header fields

The header is where most of your data lives. Below is the complete field map; anything not in this list is silently dropped.

Identification

FieldTypeNotes
invoice_numberstringYour supplier-side invoice id.
document_typestringINVOICE (default), CREDIT_NOTE, etc. Optional.
original_invoice_numberstringFor credit notes, the invoice being credited.

Dates (ISO 8601 YYYY-MM-DD)

FieldNotes
invoice_dateIssue date.
due_datePayment due date.
delivery_dateGoods or service delivery date.

Bad dates fail with 400 and a message naming the field.

References

FieldNotes
order_number_1Purchase order number. The "primary PO" field.
order_number_2Secondary PO reference.
contract_numberMaster contract id.
delivery_noteDelivery / packing slip number.
reference_numberGeneral-purpose reference.
account_numberSupplier-side account number.

Amounts

FieldTypeNotes
net_amountdecimalSubtotal before tax.
tax_amountdecimalTotal tax.
gross_amountdecimalNet + tax.
currency_codestringISO 4217 (USD, EUR, GBP, ...).

Supplier (you)

FieldNotes
supplier_nameDisplay name.
supplier_tax_idVAT / EIN / equivalent.
supplier_contactFree-text contact info.

Customer (the bill-to)

FieldNotes
customer_name
customer_tax_id
customer_contact

Payment

FieldNotes
bank_accountYour bank account / IBAN.
bank_name
payment_methodWIRE, ACH, CHECK, CARD, free text.
payment_termsNet 30, 2/10 Net 30, etc.

Custom fields

The customer can map these to ERP fields on their side. Use whichever slots they ask for; unused slots can be omitted.

FamilySlotsTypeNotes
custom_text_1..2525stringFree text.
custom_number_1..1515numberNumeric, no formatting.
custom_date_1..1515string (YYYY-MM-DD)ISO date only.
ext_reference_1..55stringExternal system reference ids.
identifier_field_1..55stringGeneric identifier slots.

Invoice lines

Each line is one row in invoice.lines. The recognized fields are:

FieldNotes
invoice_line_number1-based row index.
product_code_1Primary SKU / item code.
product_nameDisplay name.
product_code_2Secondary code (e.g. supplier internal).
quantityDecimal.
unit_priceDecimal.
unit_of_measureEA, HR, KG, etc.
net_amountLine subtotal before tax.
tax_amountLine tax.
tax_ratePercent (e.g. 10.00).
gross_amountnet_amount + tax_amount.
order_number_1Per-line PO reference.
order_number_2Secondary PO reference.
delivery_dateISO 8601.
delivery_note

Custom fields available at line level: custom_text_1..15, custom_number_1..10, custom_date_1..10.

Invoice addresses

Each address row needs address_type. Allowed values:

REMITTO (pay-to), SUPPLIER (you), SHIPTO (delivery), BILLTO (the customer's billing party).

FieldNotes
address_typeOne of the four above. Required.
address_nameDisplay name (e.g. "Headquarters").
streetStreet and number.
city
state
postal_code
countryISO 3166-1 alpha-2 or alpha-3 preferred.

Unknown address_type values fail with a 400.

Taxes, charges, discounts

"taxes": [
{ "tax_name": "Sales Tax", "tax_rate": 10.0, "tax_base_amount": 1000.00, "tax_amount": 100.00, "tax_description": "TX state" }
],
"charges": [
{ "charge_name": "Shipping", "charge_amount": 15.00 }
],
"discounts": [
{ "discount_description": "2/10 Net 30", "discount_rate": 2.0, "discount_amount": 20.00, "discount_due_date": "2024-01-25" }
]
  • Taxes: tax_name, tax_rate, tax_base_amount, tax_amount, tax_description.
  • Charges: charge_name, charge_amount.
  • Discounts: discount_description, discount_rate, discount_amount, discount_due_date (ISO 8601).

Attachments

attachments is an array of base64-encoded files. Each entry has:

FieldRequiredNotes
typeyesIMAGE or ATTACHMENT. Exactly one IMAGE per request.
filenameyesDisplay name with extension. No path separators.
contentyesBase64-encoded body. Data-URL prefix (data:application/pdf;base64,...) is tolerated.
content_typenoMIME type. If supplied, must match the sniffed type, or the request fails.

Limits

  • 10 MB decoded per file. Larger files are rejected.
  • Encrypted PDFs are rejected.
  • PDFs containing JavaScript actions are rejected.
  • Max one IMAGE per request. The IMAGE is the human-display PDF that the AP team will see in the portal. Anything else (delivery notes, supporting docs, contracts) goes in as ATTACHMENT.
  • No document_token shape. Reserved for the Customer API.

What if you don't send an IMAGE?

Nuntiq generates a display PDF from the customer's configured invoice template, so the AP team always has something to look at. The response field pdf_will_be_generated: true confirms that's what happened.

Attachment validation errors

A failed attachment returns 400 with the failing index pinned:

{
"error": "Attachment validation failed",
"message": "attachments[2].content exceeds 10 MB decoded",
"attachment_index": 2
}

The base64-only check uses a different error envelope (same status, same attachment_index):

{
"error": "Validation failed",
"message": "attachments[0].document_token is not allowed on this endpoint; provide { filename, content (base64), type } instead",
"attachment_index": 0
}

Routing

Most customers have multiple receiving inboxes (per business unit, per entity, per workflow). Nuntiq resolves the target inbox in this exact order, top to bottom, first match wins:

  1. receiving_inbox matches one of the customer's inboxes (case-insensitive, exact-string match against the configured prod / test / dev addresses for this environment).
  2. external_identifier matches an alias configured against a specific inbox in the admin portal (under each inbox's External Identifiers list, case-insensitive, scoped per customer).
  3. Customer default inbox (configured once per customer).

A successful resolution returns 200 with the path used:

"resolution_path": "explicit_inbox" | "external_identifier" | "default_fallback"

Soft fallback

If you provided receiving_inbox or external_identifier and neither matched, but the customer has a default inbox, Nuntiq still accepts the invoice and routes it to the default. You get:

HTTP/1.1 200 OK
Warning: 199 nuntiq "unknown receiving_inbox 'invoices@old.example.com', used default"
{
"success": true,
"data": {
"receiving_inbox": "default@acme.apreceiving.com",
"external_identifier": "OLD-ID",
"resolution_path": "default_fallback",
"receiving_inbox_fallback_used": true,
"...": "..."
}
}

The receiving_inbox_fallback_used: true flag tells you "we accepted this but you should fix your config." You do not need to retry.

If neither value is provided and no default is configured, you get a 400 with error: "Configuration error".

If neither value is provided but a default IS configured, the invoice routes to the default with resolution_path: "default_fallback" and no Warning header (since you never asked for a specific target).

Idempotency

Send Idempotency-Key: <your-unique-id> on every submission. Within a 24-hour window, repeated requests with the same key return the original result, including the original invoice_token. No duplicate invoice is created.

  • Namespace is per API key (specifically, per api_user_id resolved from X-API-Key). Two partners using the same string do not collide; the same partner using the same string twice gets the original result back.
  • Max length 255 characters.
  • Keep the key stable across retries of the same logical submission. Do not generate a new UUID on each retry attempt.

A replay returns the same shape with an extra flag:

HTTP/1.1 200 OK
Content-Type: application/json

{
"success": true,
"idempotent": true,
"data": {
"invoice_token": "<the original token>",
"...": "..."
}
}

idempotent: true is the marker that this was a replay. Use it to detect "my retry succeeded, the original call also succeeded."

Successful response

{
"success": true,
"data": {
"invoice_token": "01HX5W6Y3VEXAMPLE...",
"invoice_id": 94821,
"source_document_id": 30481,
"workflow": "full_enrichment",
"effective_workflow": "full_enrichment",
"invoice_status": 1,
"source_document_status": 1,
"template_id": 5,
"template_type": "global",
"attachments_linked": 1,
"pdf_will_be_generated": false,
"receiving_inbox": "invoices@acme.apreceiving.com",
"external_identifier": "ACME-001",
"resolution_path": "explicit_inbox",
"receiving_inbox_fallback_used": false
}
}

Fields you care about

FieldUse it for
invoice_tokenUUID handle for every downstream call (status, events, attachments via the Customer API). Stable forever, save this.
source_document_idNumeric id useful for support tickets.
invoice_idNumeric internal id. Use invoice_token in your code; use this when Nuntiq support asks.
effective_workflowWhat Nuntiq actually ran. May be collapsed from full_enrichment to basic_enrichment if you sent no IMAGE attachment AND the customer has no image-capture rules.
invoice_statusInitial status code. For full_enrichment this is 1 ("API enrich with PDF capture").
pdf_will_be_generatedtrue when Nuntiq is rendering a display PDF from the customer template (because you sent no IMAGE).
receiving_inbox_fallback_usedtrue only when you specified a routing target that didn't match. See Routing.
resolution_pathexplicit_inbox, external_identifier, or default_fallback.

What happens after the 200

The 200 means Nuntiq has accepted, persisted, and queued the invoice. What runs next depends on the workflow:

  • full_enrichment (the partner default): the invoice goes through PDF capture (OCR + AI extraction against your IMAGE), enrichment against supplier and PO data, then validation rules. Total processing time is typically a few minutes; you can poll with the Customer API once the reserved status endpoint lands, or look for lifecycle messages via the Customer API today.

You receive nothing else from this endpoint after the 200. Status changes are queryable separately.

Error responses

All 4xx error bodies are JSON with at minimum a message. Many also include an error short code and context fields.

StatusWhenBody shape
400Bad payload, missing field, attachment shape error, base64 decode error, encrypted PDF, JS-PDF, oversized file, unknown address_type, bad date, neither routing field matched and no customer default.{ "error": "...", "message": "...", "attachment_index"?: N }
401Missing X-API-Key, unknown key, expired key (see below).{ "error": "...", "code"?: "key_expired", "message": "...", "regenerate_url"?: "..." }
403Your key lacks SUBMIT_SUPPLIER_INVOICE. Every partner key carries this permission, so this almost only happens after the customer has revoked the key.{ "message": "..." }
429Rate limit. The customer-API instance applies a global 300-request-per-minute IP limit; this is shared across all routes, not per-key. Back off and retry.{ "message": "Too many requests, please try again later." }
500Server-side. Retry with the same Idempotency-Key after a back-off.{ "message": "..." }

Expired key (401 key_expired)

When your key is past its expiry date the response is a structured 401:

{
"error": "Unauthorized",
"code": "key_expired",
"message": "This API key expired on 2026-05-19. Generate a new key at https://acme.apquery.com/regen?token=...",
"regenerate_url": "https://acme.apquery.com/regen?token=..."
}

Send a recipient through the regenerate_url to mint a replacement. See Expired-key error for the full handling guide, including how to avoid hitting it (rotate ahead of time, watch the rotation reminder emails).

What's reserved but not yet implemented

Three GET endpoints under /v1/partner/invoices/{id}/... are documented in the reference but return 501 Not Implemented today:

  • GET /v1/partner/invoices/{id} returns the full invoice when shipped.
  • GET /v1/partner/invoices/{id}/status returns the current LCM + payment state, a cheap polling endpoint.
  • GET /v1/partner/invoices/{id}/events returns the chronological LCM event history, useful for an audit trail.

If you need any of these now, use the Customer API with customer-issued credentials. The URL shape is reserved so your client code can target it now and start working the day the endpoints land.