Skip to main content
POST
Submit Certificate
Submit an exemption certificate document for asynchronous processing. You provide an https URL to the document (typically a presigned URL to your own storage); Numeral downloads it, stores it, and runs the classification pipeline. The certificate type and jurisdiction are determined from the document itself — there is no certificate_type_id input. The response is a tax.certificate_upload, a processing job. Poll GET /tax/certificate-uploads/{upload_id} until its status leaves processing, then read certificate_ids to find the resulting certificates.
Remember to include the X-API-Version: 2026-03-01 header — older versions return INVALID_REQUEST (400). Certificate endpoints are live-only — using a sk_test_* key returns TESTMODE_NOT_SUPPORTED (400).

The document_url field

document_url is fetched server-side, so it must be reachable by Numeral’s servers without any session or cookie:
  • https only. Redirects are followed up to 3 hops, and every hop must also be https.
  • PDF, PNG, or JPEG. The type is detected from the file content, not the extension or Content-Type header.
  • 10 MB max, checked against both Content-Length and the downloaded bytes.
Share or viewer links (Google Drive, Dropbox, and similar) return an HTML page rather than the file and fail with UNSUPPORTED_FILE_TYPE. Use a direct download link or a presigned URL instead.

Pre-mapping to a customer

Pass customer to attribute the certificate to a buyer up front. Either identifier alone is sufficient:
  • customer.id — an existing Numeral customer id. An unknown id returns CUSTOMER_NOT_FOUND (404).
  • customer.reference_customer_id — your own customer identifier. An unknown value creates the customer, so you can pre-map a certificate before the customer exists in Numeral.
If both are present and resolve to different customers, the request fails with BUYER_IDENTITY_CONFLICT (409) and nothing is written.
Pre-mapping is the headline feature. Submit with a reference_customer_id, and once the resulting certificate is active, any POST /tax/calculations carrying the same reference_customer_id applies the exemption automatically. No further wiring is needed.
Omitting customer is valid. The upload processes unattributed, Numeral matches it to a buyer during processing, and the response customer is null.

Duplicate submissions

Resubmitting a byte-identical document with the same file_name returns 201 with status: "duplicate" rather than reprocessing the document. The duplicate upload’s certificate_ids stays empty — use your original upload id to retrieve its certificates. When the original upload had failed, error_message on the duplicate carries the same reason so you can see why resubmitting the same bytes will not help.

Response

201 Created with the tax.certificate_upload object. status is processing or duplicate on create, and certificate_ids is always empty at this point.

Errors

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Headers

X-API-Version
enum<string>
required

Must be 2026-03-01 or later. Older versions return INVALID_REQUEST (400).

Available options:
2026-03-01

Body

application/json
document_url
string<uri>
required

An https URL that Numeral's servers can GET — typically a presigned URL to your own storage. Fetched server-side. The file must be a PDF, PNG, or JPEG (detected from content, not extension or Content-Type), at most 10 MB, reachable within 3 redirects (each hop must also be https).

Example:

"https://your-bucket.s3.amazonaws.com/certs/resale.pdf?X-Amz-Signature=..."

file_name
string

Display name for the document (1–255 characters). Defaults to the URL's basename when it has an extension, otherwise to a generic name derived from the detected file type.

Required string length: 1 - 255
Example:

"resale.pdf"

customer
object

Pre-maps the resulting certificate to a customer. Supply at least one of id or reference_customer_id. Either alone is sufficient; if both are present and resolve to different customers the request fails with BUYER_IDENTITY_CONFLICT (409) and nothing is written.

Response

Upload accepted. status is processing or duplicate; certificate_ids is always empty at this point — poll GET /tax/certificate-uploads/{upload_id} for results.

A certificate upload — the asynchronous processing job created by POST /tax/certificates.

id
string
required

Upload id, upl_<number>. Endpoints that accept an upload id also accept the bare numeric form.

Example:

"upl_4471"

object
string
required

The type of object: tax.certificate_upload

Example:

"tax.certificate_upload"

status
enum<string>
required

Processing state of a certificate upload. This is a separate vocabulary from certificate status — an upload is a processing job, a certificate is its output. processing is the only non-terminal state; completed (certificates produced), failed (see error_message), and duplicate (same document already submitted) are terminal.

Available options:
processing,
completed,
failed,
duplicate
customer
object | null
required

The customer supplied at submission. null when the upload was submitted without a customer — attribution then happens during processing.

certificate_ids
string[]
required

Certificates produced by this upload, usable with the certificate endpoints. Empty while status is processing; populated at completed. One document can yield multiple certificates.

error_message
string | null
required

Human-readable reason processing failed. Also set on duplicate uploads when the original upload had failed, so you can see why re-submitting the same bytes will not help.

created_at
string<date-time> | null
required
livemode
enum<boolean>
required

Always true — these endpoints are live-only.

Available options:
true