> ## Documentation Index
> Fetch the complete documentation index at: https://docs.numeral.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit Certificates via API

> Upload exemption certificate documents and have exemptions apply automatically to calculations

If your customers already hand you their exemption certificates — through
your own onboarding flow, a sales rep, or a support ticket — you can push
those documents into Numeral with
[`POST /tax/certificates`](/api-reference/v2026-03-01/endpoint/submit_certificate)
and have the exemption apply to future calculations without any further
wiring.

<Note>
  These endpoints require API version `2026-03-01` or later and are
  **live-only** — `sk_test_*` keys return `TESTMODE_NOT_SUPPORTED`.
</Note>

## How it works

<Steps>
  <Step title="Presign a GET to your storage">
    The document must be reachable by Numeral's servers over `https`. The
    usual pattern is a short-lived presigned `GET` URL to the object in your
    own bucket. The file must be a PDF, PNG, or JPEG under 10 MB.
  </Step>

  <Step title="Submit the document">
    Call `POST /tax/certificates` with the URL and, ideally, your
    `reference_customer_id` for the buyer.

    ```bash theme={null}
    curl -X POST https://api.numeralhq.com/tax/certificates \
      -H "Authorization: Bearer sk_prod_..." \
      -H "X-API-Version: 2026-03-01" \
      -H "Content-Type: application/json" \
      -d '{
        "document_url": "https://your-bucket.s3.amazonaws.com/certs/resale.pdf?X-Amz-Signature=...",
        "file_name": "resale.pdf",
        "customer": { "reference_customer_id": "20506" }
      }'
    ```

    The response is a `tax.certificate_upload` with `status: "processing"`.
    Store its `id`.

    ```json theme={null}
    {
      "id": "upl_4471",
      "object": "tax.certificate_upload",
      "status": "processing",
      "customer": { "id": "cust_6126acaf-7379-411a-8ada-00005bac0715", "reference_customer_id": "20506" },
      "certificate_ids": [],
      "error_message": null,
      "created_at": "2026-09-15T17:04:11.000Z",
      "livemode": true
    }
    ```
  </Step>

  <Step title="Poll the upload">
    Call
    [`GET /tax/certificate-uploads/{upload_id}`](/api-reference/v2026-03-01/endpoint/get_certificate_upload)
    until `status` leaves `processing`.

    ```bash theme={null}
    curl "https://api.numeralhq.com/tax/certificate-uploads/upl_4471" \
      -H "Authorization: Bearer sk_prod_..." \
      -H "X-API-Version: 2026-03-01"
    ```

    * `completed` — `certificate_ids` lists the certificates Numeral
      extracted. One document can yield more than one.
    * `failed` — `error_message` explains why. Fix the document and submit
      again.
    * `duplicate` — you already submitted this exact document. Use your
      original upload id to find its certificates.
  </Step>

  <Step title="Read the certificates">
    Each id in `certificate_ids` works with
    [`GET /tax/certificates/{certificate_id}`](/api-reference/v2026-03-01/endpoint/get_certificate)
    to read the certificate's type, jurisdictions, validity dates, and its
    own `status`.
  </Step>

  <Step title="Calculate as usual">
    Once a certificate is `active`, any `POST /tax/calculations` whose
    `customer.reference_customer_id` matches the value you submitted with
    applies the exemption automatically. Nothing else to configure.
  </Step>
</Steps>

## Things to know

### Processing is asynchronous

`POST /tax/certificates` returns `201` as soon as the document is stored. It
does not return a validation verdict. Certificate type, jurisdiction, and
validity are determined during processing, so poll the upload rather than
inspecting the create response.

### The certificate type comes from the document

There is no `certificate_type_id` input. Numeral classifies the certificate
type and jurisdiction from the document itself, so the same endpoint handles
resale, exemption, and multi-state certificates without you having to
identify the form up front.

### Two status vocabularies

An **upload** is a processing job. A **certificate** is its output. They
carry different statuses:

| Object                      | Statuses                                                               |
| --------------------------- | ---------------------------------------------------------------------- |
| `tax.certificate_upload`    | `processing`, `completed`, `failed`, `duplicate`                       |
| `tax.exemption_certificate` | `processing`, `needs_info`, `active`, `expiring`, `expired`, `invalid` |

A `completed` upload does not mean an `active` certificate — the certificate
may still be `processing` or `needs_info` under Numeral's review. Read the
certificate's own status before relying on the exemption.

### Pre-mapping before the customer exists

Submitting with a `reference_customer_id` that Numeral has not seen yet
creates the customer. You can therefore upload certificates during your own
onboarding flow, before the first calculation for that buyer.

### Presigned URLs expire

If the document host returns 401 or 403, the API responds with
`DOCUMENT_URL_UNAUTHORIZED` (422). This almost always means the presigned
URL expired between minting and submission. Mint a fresh URL and retry.

### Share links do not work

Google Drive, Dropbox, and similar viewer links serve an HTML page rather
than the file and fail with `UNSUPPORTED_FILE_TYPE`. Use a direct download
or presigned URL.

## Related

<CardGroup cols={2}>
  <Card title="Submit Certificate" icon="file-arrow-up" href="/api-reference/v2026-03-01/endpoint/submit_certificate">
    `POST /tax/certificates` reference.
  </Card>

  <Card title="Get Certificate Upload" icon="magnifying-glass" href="/api-reference/v2026-03-01/endpoint/get_certificate_upload">
    The polling endpoint.
  </Card>

  <Card title="Exemption Certificates" icon="file-certificate" href="/v2026-03-01/objects/exemption-certificates">
    The upload and certificate objects.
  </Card>

  <Card title="Exemptions Behavior" icon="circle-xmark" href="/essentials/integration-guides/exemptions-behavior">
    How exemptions show up in calculation responses.
  </Card>
</CardGroup>
