Invoices API

Invoices have a numeric on-chain id and a separate merchant-sequential invoiceNumber. Use id in all URL paths. Amounts are USDC base-unit strings; invoice timestamps are Unix seconds. Status is Issued, Paid, or Canceled.

For dashboard workflows, see issue an invoice, manage invoices, and share invoices.

Read invoices#

EndpointInputSuccess envelope
GET /v1/invoicesList query below{ data: { items, page }, metadata }
GET /v1/invoices/lookupSequential number and issuance year{ data: { id, invoiceNumber } }
GET /v1/invoices/:idNumeric ID; optional mode{ data: invoice, metadata }
GET /v1/invoices/:id/pdfNumeric IDPDF bytes, not JSON

These routes accept HMAC or JWT and restrict private reads to the caller's studio. They currently have no explicit granular scope guard. See the authorization matrix.

List query parameters:

ParameterMeaning
statusCase-sensitive Issued, Paid, or Canceled; omit for all
customerIdCustomer UUID
payerPayer EVM address
issuedFrom, issuedToInclusive issuance-date bounds in integer Unix seconds; reversed ranges are rejected
limitInteger 1 through 50, default 25; strong mode clamps to 25
cursorCursor returned in data.page.cursor; do not synthesize it
orderBy, orderDirectionIndexer ordering field and asc or desc; defaults are issuedBlock and desc
modeeventual, hybrid (default), or strong

Although the query schema accepts merchant and agent, listing always uses the caller's Safe as merchant and does not forward an agent filter. Do not use those parameters to select another studio or claim an agent-specific list. Follow data.page.hasNextPage and its cursor; lists do not use a page or pageSize parameter.

Invoice cursors represent consumed row offsets, independent of invoice IDs. Keep filters and ordering unchanged between pages. Invalid or obsolete cursors return 400; restart without a cursor. Lists are not snapshots: concurrent changes and indexer lag can affect traversal. During chain-only fallback, payer/customer filtering and indexer ordering are not fully preserved, and some historical timestamps are synthesized rather than settlement-event timestamps. Verify historical accounting against known IDs and transaction evidence.

To resolve INV-2026-000007, request /v1/invoices/lookup?number=7&year=2026. Lookup is scoped to the authenticated studio's Safe, uses the invoice's issuance year, and fetches at most two candidates to detect ambiguity. It returns 404 for no match and 409 for ambiguous data. The returned global id can differ from number; use that id for detail, share, and cancellation routes.

An invoice includes id, invoiceNumber, merchant, agent, payer, token, amount, status, metaHash, tokenURI, timestamps, and customer display fields when available. A payer may be absent before payment. Detail reads can also include emailDelivery. tokenURI alone is not a decrypted invoice or a substitute for a tokenized share link.

Server-signed issuance#

POST /v1/invoices/issue creates an on-chain invoice. It requires HMAC, invoice:write, an active write-enabled key, and an active server signer on the studio's chain. The studio must have a deployed Safe and a complete, valid business profile. Supply an existing customer UUID from that studio. Although customerId is optional in the initial request schema, the issuance workflow requires it.

Body fieldMeaning
customerIdRequired existing customer UUID
amountPositive amount in USDC base units, preferably a decimal string
dueAtAbsolute nonnegative Unix-second timestamp, preferably a decimal string; use exactly one of dueAt and dueDays
dueDaysRelative nonnegative whole days, anchored to the original idempotency record; use instead of dueAt
descriptionOptional string, maximum 500 characters
sendEmailOptional boolean; email is opt-in

Example mutation: issue an invoice#

The following request creates an invoice, and is not a connectivity test. Use a test studio and its actual customer UUID. Sign this body exactly as described in authentication.

http
POST /v1/invoices/issue
Content-Type: application/json
Idempotency-Key: billing-example-order-1042
json
{
  "customerId": "123e4567-e89b-42d3-a456-426614174000",
  "amount": "500000000",
  "description": "Consulting services",
  "dueAt": "1793664000",
  "sendEmail": false
}

This amount is 500 USDC. The fixed example date is 2026-11-03 at 00:00 UTC. Choose the intended due date before the first attempt, then persist the full payload and idempotency key. On a retry, do not recompute dueAt from the current time.

Alternatively, send "dueDays": 30 instead of dueAt. The server fingerprints the relative term and derives the effective due date from the durable operation's original creation time. Reusing the same key and intent preserves that date across process restarts. Sending both fields, or neither, returns 400.

A normal confirmed 200 response is top-level, not nested under data:

json
{
  "id": "42",
  "invoiceNumber": "7",
  "amount": "500000000",
  "status": "Issued",
  "issuedAt": "1791072000",
  "dueAt": "1793664000"
}

This illustrative subset omits returned userOpHash, txHash, shareLink, paymentUrl, optional delivery, and idempotency fields. Preserve the complete server-provided payment URL, including ?token=....

202 with status: "pending" and userOpHash means a shareable invoice result is not ready. A successful operation without a recoverable invoice event stays pending. A confirmed retry recovers the actual ID, finalizes encrypted metadata, and returns a tokenized payment link. Finalization errors remain retryable with the same key. Do not invent an ID or build /pay/undefined. See errors and retries.

Client-signed issuance#

Use this flow only when your client implements Safe UserOperation construction/signing correctly. HMAC or JWT authenticates HTTP; neither replaces the required transaction signature. The dashboard implements this flow for users.

EndpointRequestResponse
POST /v1/invoices/prepareownerAddress, raw-unit amount, Unix-second dueAt and issuedAt, tokenURI, 32-byte metaHash and mandateHash; optional quoteId, customerId, metadata{ success, userOp, encryption?, idempotency? }
POST /v1/invoices{ userOp, pendingInvoiceId?, sendEmail?, customerId? }Normally { status: "confirmed", userOpHash, txHash, invoiceId, delivery?, idempotency? }, or 202 pending

Pass full invoice metadata to prepare for the encrypted-metadata workflow. It includes version: "1.0", invoiceNumber, ISO date strings issuedAt/dueAt, merchant and payer billing parties, token details (address, symbol, decimals, chainId), human-readable amount, raw amountRaw, at least one line item, createdBy, and createdAt. A line item has description, positive quantity, unitPrice, and amount, with optional tax fields. Optional metadata includes description, notes, paymentTerms, purchaseOrder, externalRef, quoteId, deliverables, and legalText. These metadata dates differ from the Unix-second prepare fields.

Preparation's encryption object includes pendingInvoiceId, ipfsCid, metaHash, and shareLink. Preserve encryption.pendingInvoiceId as the submit body's pendingInvoiceId so encryption records can be finalized against the confirmed invoice ID. sendEmail: true requires customerId. Preserve final paymaster and gas fields before signing; changing them after signing invalidates the signature. Issuance prepare/submit supports Idempotency-Key; retain the original operation context rather than treating each phase as a new invoice. The client-signed response calls its ID invoiceId, whereas /issue normally returns id.

A serialized v0.7 userOp contains sender, nonce, callData, callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas, and signature, plus returned factory/factoryData and paymaster fields when applicable. Numeric fields are strings in JSON. A placeholder signature is not a signed operation.

Cancel, email, and PDF#

EndpointRequest and effectResult
POST /v1/invoices/:id/cancel/submitHMAC invoice:write, active signer; send {}. Server signs cancellation.{ id, status: "Canceled", userOpHash, txHash }; may return 202 pending
POST /v1/invoices/:id/cancel/prepareHMAC or JWT; { ownerAddress }. Prepares a client-signed cancellation.{ success, userOp, expiresAt }, expiry is an ISO date-time
POST /v1/invoices/:id/cancelHMAC or JWT; { userOp } with valid signature. Submits cancellation.{ status: "confirmed", userOpHash, txHash }, or 202 pending
POST /v1/invoices/:id/send-emailHMAC invoice:write or JWT; { customerId? }. Sends/retries eligible delivery for an Issued invoice.{ delivery }
GET /v1/invoices/:id/pdfAuthenticated merchant readapplication/pdf attachment

Only Issued invoices can be canceled. Cancellation is not a refund; a paid invoice is terminal. Read the status after a pending cancellation before attempting another write.

The server-signed cancel route tracks attempts internally per studio, chain, and invoice using a fixed cancellation key. It can repoll an existing attempt, but checks current invoice status before that tracking step: an already-canceled invoice can return INVALID_STATUS instead of replaying success. This differs from caller-supplied issuance Idempotency-Key behavior.

Invoice issuance and email delivery are separate outcomes. An invoice can exist even when email is disabled, deferred, or failed. Detail reads expose delivery state when available. For explicit email recovery, provide the matching customer UUID; the route checks it against the invoice's customer hash. A successful issuance retry is not a promise to resend email.

These merchant-owned routes accept HMAC or JWT. They currently lack explicit granular scope guards, including the mutations. They apply to invoices with encrypted metadata.

EndpointInputSuccess response
GET /v1/invoices/:id/shareNumeric ID{ success, invoiceId, hasEncryption, shareLink, tokenHint }
GET /v1/invoices/:id/tokensNumeric ID{ success, tokens }, no token secrets
POST /v1/invoices/:id/tokens{ label?, expiresAt? }, expiry is a date-time string201 { success, tokenId, token, shareLink, hint, expiresAt }
DELETE /v1/invoices/:id/tokens/:tokenIdToken UUID200 { success, message }
POST /v1/invoices/:id/share/regenerateNumeric ID, no configuration body required{ success, tokenId, token, shareLink, hint }

Token creation grants access to decrypted metadata. The secret is shown only at creation. Regeneration replaces the default share token and revokes the old default; it does not revoke every separately created token. Revocation is permanent. Treat links as bearer credentials and do not log or publish them unintentionally.

The share response's invoiceId may be an encryption-storage identifier. Continue using the original numeric route ID for invoice API requests. Never remove the query token from shareLink, or reconstruct a bare URL in place of the returned link. See security.