> ## 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.

# Exemption (Resale) Certificates

> Collect, submit, and read your customers' exemption certificates

An exemption certificate shows that a customer doesn't owe sales tax on their purchases, for example because they buy for resale. Numeral applies `active` certificates to the customer's [calculations](https://docs.numeral.com/v2026-11-01/objects/calculations.md).

Three objects cover certificates:

* A **certificate request** asks one of your [customers](https://docs.numeral.com/v2026-11-01/objects/customers.md) for a certificate.
* A **certificate** is the certificate itself, with its validity in each jurisdiction and a link to the document.
* A **certificate upload** is a document you send through the API. Numeral processes it and creates certificates from it.

```text Where certificates come from theme={null}
Customer owes a certificate
├─ Certificate request is opened           → request id (cert_req_…)
│     └─ Customer submits a certificate    → status: fulfilled, certificate_id
└─ You already have the document
      └─ POST /tax/certificates            → upload id (upl_…)
            └─ GET /tax/certificate-uploads/:id   → certificate_ids

Read a certificate
└─ GET /tax/certificates/:id               → status, jurisdictions, download_url
```

> [!NOTE]
>
> Every certificate endpoint is **live-only**. A test API key (`sk_test_*`) returns `TESTMODE_NOT_SUPPORTED`.

Endpoints
[List certificate requestsGET/tax/certificate-requests](#list-certificate-requests)
[Retrieve a certificate requestGET/tax/certificate-requests/:id](#retrieve-a-certificate-request)
[Cancel a certificate requestDELETE/tax/certificate-requests/:id](#cancel-a-certificate-request)
[List certificatesGET/tax/certificates](#list-certificates)
[Retrieve a certificateGET/tax/certificates/:id](#retrieve-a-certificate)
[Submit a certificatePOST/tax/certificates](#submit-a-certificate)
[List certificate uploadsGET/tax/certificate-uploads](#list-certificate-uploads)
[Retrieve a certificate uploadGET/tax/certificate-uploads/:id](#retrieve-a-certificate-upload)

## The Certificate request object

A certificate request asks a customer for an exemption certificate. It's opened when you, or an integration acting for you, trigger one for a customer. It stays `pending` until the customer submits a certificate, you cancel it, or it expires.

### Attributes

**id** `string`

The ID of the certificate request, `cert_req_…`.

**object** `string, value is "tax.certificate_request"`

String representing the object's type.

**status** `enum`

`pending`, `fulfilled`, `canceled`, or `invalid`. See [Statuses](#request-statuses).

**customer** `object`

The `Customer` record linked to this request. `null` when no live customer is linked yet. A buyer may still be set in `linked_buyer`.

**customer.id** `string`

The Numeral customer ID, `cust_…`.

**customer.reference\_customer\_id** `string`

Your customer ID, as sent when the customer was created. Can be `null`.

**customer.name** `string`

The customer's name. Falls back to the buyer's `legal_name` when the customer has no name. `null` only when neither is set.

**linked\_buyer** `object`

The buyer that owns this request. Set whenever the request has a buyer, even when no `Customer` record is linked yet. Use this field to show who owns the request. Can be `null`.

**linked\_buyer.id** `string`

Stable numeric ID of the buyer.

**linked\_buyer.legal\_name** `string`

The buyer's legal name. Can be `null`.

**certificate\_id** `string`

The `cert_…` ID of the submitted certificate. Set only when `status` is `fulfilled`, otherwise `null`. [Retrieve the certificate](#retrieve-a-certificate) to read it.

**certificate\_type\_id** `string`

Identifier of the requested certificate type, e.g. `US-CA-CDTFA-230`. Can be `null`.

**jurisdictions** `array of strings`

Jurisdictions the request covers, e.g. `US-CA`.

**created\_at** `string`

ISO 8601 timestamp when the request was created. `updated_at` follows the same pattern.

**expires\_at** `string`

ISO 8601 timestamp when the request expires if it isn't fulfilled. `null` if it doesn't expire.

**livemode** `boolean`

Always `true`.

### Request statuses

| `status` | Meaning |
| - | - |
| `pending` | Open. The customer hasn't responded yet. |
| `fulfilled` | The customer submitted a certificate. `certificate_id` is set. |
| `canceled` | Canceled before it was fulfilled. |
| `invalid` | Expired or couldn't be fulfilled. |

`fulfilled`, `canceled`, and `invalid` are final.

```json The Certificate request object theme={null}
{
  "id": "cert_req_b2f1e4a3-9c0d-4e7a-8b1f-2d5a6e7b8c9d",
  "object": "tax.certificate_request",
  "status": "pending",
  "customer": {
    "id": "cust_6126acaf-7379-411a-8ada-00005bac0715",
    "reference_customer_id": "20506",
    "name": "Acme Co"
  },
  "linked_buyer": {
    "id": "12345",
    "legal_name": "Acme Co"
  },
  "certificate_id": null,
  "certificate_type_id": "US-CA-CDTFA-230",
  "jurisdictions": ["US-CA"],
  "created_at": "2026-06-10T18:24:09.123Z",
  "updated_at": "2026-06-10T18:24:09.123Z",
  "expires_at": "2026-07-10T18:24:09.123Z",
  "livemode": true
}
```

```text Request lifecycle theme={null}
pending
├─ Customer submits a certificate          → fulfilled  (certificate_id set)
├─ DELETE /tax/certificate-requests/:id    → canceled
└─ Expires or can't be fulfilled           → invalid
```

## List certificate requests

Returns your certificate requests. Fulfilled and canceled requests are included, so filter by `status` to narrow the list. [Try it in the API reference](https://docs.numeral.com/api-reference/v2026-11-01/endpoint/list_certificate_requests).

Use it to show customers' outstanding requests, or to check which buyers still owe a certificate.

### Parameters

**status** `enum`

`pending`, `fulfilled`, `canceled`, or `invalid`.

**customer\_id** `string`

Only requests for this customer. Read as the type set by `id_type`. If the customer can't be found, the list is empty (no 404).

**id\_type** `enum` (default: id)

`id` for the Numeral `cust_…` ID, or `reference_customer_id` for your own ID. Any other value returns `INVALID_REQUEST`, even without `customer_id`.

**certificate\_type\_id** `string`

Only requests for this certificate type. Takes the identifier (`US-CA-CDTFA-230`) or the numeric type ID (`12`).

**created\_after** `string`

ISO 8601 timestamp. `created_before`, `updated_after`, and `updated_before` work the same way.

**limit** `integer` (default: 10)

Items per page, 1 to 100.

**cursor** `string`

The `next_cursor` from the previous page.

### Returns

An object with a `certificate_requests` array of [Certificate request objects](#the-certificate-request-object) and `has_more`. When there are more pages, `next_cursor` is the last ID on this page. It's left out (not `null`) on the last page.

```bash GET /tax/certificate-requests theme={null}
curl 'https://api.numeralhq.com/tax/certificate-requests?status=pending&limit=5' \
  -H 'Authorization: Bearer <token>' \
  -H 'X-API-Version: 2026-11-01'
```

```json Response theme={null}
{
  "object": "list",
  "certificate_requests": [
    {
      "id": "cert_req_b2f1e4a3-9c0d-4e7a-8b1f-2d5a6e7b8c9d",
      "object": "tax.certificate_request",
      "status": "pending",
      "customer": {
        "id": "cust_6126acaf-7379-411a-8ada-00005bac0715",
        "reference_customer_id": "20506",
        "name": "Acme Co"
      },
      "linked_buyer": {
        "id": "12345",
        "legal_name": "Acme Co"
      },
      "certificate_id": null,
      "certificate_type_id": "US-CA-CDTFA-230",
      "jurisdictions": ["US-CA"],
      "created_at": "2026-06-10T18:24:09.123Z",
      "updated_at": "2026-06-10T18:24:09.123Z",
      "expires_at": "2026-07-10T18:24:09.123Z",
      "livemode": true
    }
  ],
  "has_more": false
}
```

## Retrieve a certificate request

Returns a certificate request by its `id`. [Try it in the API reference](https://docs.numeral.com/api-reference/v2026-11-01/endpoint/get_certificate_request).

**request\_id** `string` *required*

The request's `id`, `cert_req_…`.

### Returns

A [Certificate request object](#the-certificate-request-object). A request that doesn't exist, or belongs to another account, returns `CERTIFICATE_REQUEST_NOT_FOUND`.

```bash GET /tax/certificate-requests/:id theme={null}
curl https://api.numeralhq.com/tax/certificate-requests/cert_req_b2f1e4a3-9c0d-4e7a-8b1f-2d5a6e7b8c9d \
  -H 'Authorization: Bearer <token>' \
  -H 'X-API-Version: 2026-11-01'
```

## Cancel a certificate request

Cancels a pending request, for example when you no longer need the exemption. Reminder emails stop and the request leaves the customer's outstanding list. [Try it in the API reference](https://docs.numeral.com/api-reference/v2026-11-01/endpoint/cancel_certificate_request).

Canceling is final and safe to repeat: canceling a request that's already canceled returns `200` and the same body.

* A `fulfilled` request returns `409 REQUEST_ALREADY_FULFILLED`. [Retrieve the certificate](#retrieve-a-certificate) instead.
* An `invalid` request returns `409 REQUEST_INVALID`.

**request\_id** `string` *required*

The request's `id`, `cert_req_…`.

### Returns

The [Certificate request object](#the-certificate-request-object) with `status: "canceled"`.

```bash DELETE /tax/certificate-requests/:id theme={null}
curl -X DELETE https://api.numeralhq.com/tax/certificate-requests/cert_req_b2f1e4a3-9c0d-4e7a-8b1f-2d5a6e7b8c9d \
  -H 'Authorization: Bearer <token>' \
  -H 'X-API-Version: 2026-11-01'
```

```json Response theme={null}
{
  "id": "cert_req_b2f1e4a3-9c0d-4e7a-8b1f-2d5a6e7b8c9d",
  "object": "tax.certificate_request",
  "status": "canceled",
  "certificate_id": null,
  "certificate_type_id": "US-CA-CDTFA-230",
  "jurisdictions": ["US-CA"],
  "created_at": "2026-03-01T12:00:00Z",
  "updated_at": "2026-03-03T15:45:00Z",
  "expires_at": null,
  "livemode": true,
  "...": "see The Certificate request object"
}
```

## The Certificate object

A certificate is an exemption certificate from one of your customers. Use it to show exemption status in your own app, sync validity and expiration dates into your systems, and link to the original document.

### Attributes

**id** `string`

The ID of the certificate, `cert_…`. Always the top-level certificate ID, so it's safe to store.

**object** `string, value is "tax.exemption_certificate"`

String representing the object's type.

**status** `enum`

Status of the certificate as a whole: `processing`, `needs_info`, `active`, `expiring`, `expired`, or `invalid`. See [Statuses](#certificate-statuses).

**certificate\_type** `object`

The type of certificate. Can be `null`.

**certificate\_type.id** `string`

Stable numeric ID of the type, e.g. `12`.

**certificate\_type.name** `string`

Name of the type, e.g. `California Resale Certificate (CDTFA-230)`.

**certificate\_type.certificate\_identifier** `string`

Stable, readable identifier, e.g. `US-CA-CDTFA-230`. Use this in filters and references.

**customer** `object`

The `Customer` record linked to this certificate: `id`, `reference_customer_id`, and `name`, as on the [certificate request](#the-certificate-request-object). `null` when no live customer is linked yet.

**linked\_buyer** `object`

The buyer that owns this certificate: `id` and `legal_name`. Set even when no `Customer` record is linked yet. Use this field to show who owns the certificate. Can be `null`.

**jurisdictions** `array of objects`

One entry per jurisdiction the certificate covers.

**jurisdictions\[].jurisdiction\_id** `string`

The jurisdiction, e.g. `US-CA`.

**jurisdictions\[].status** `enum`

Status in this jurisdiction. Same values as the top-level `status`. A certificate can be `active` in one state and `expired` in another. Use this one to decide whether a buyer is exempt in a given state.

**jurisdictions\[].effective\_date** `string`

`YYYY-MM-DD` date the certificate takes effect here. Can be `null`.

**jurisdictions\[].expiration\_date** `string`

`YYYY-MM-DD` date the certificate expires here. Can be `null`.

**effective\_date** `string`

`YYYY-MM-DD` date the certificate takes effect. `expiration_date` follows the same pattern. Both can be `null`.

**created\_at** `string`

ISO 8601 timestamp when the certificate was created. `updated_at` follows the same pattern.

**livemode** `boolean`

Always `true`.

**download\_url** `string`

Link to the original document. Expires one hour after the response. [Retrieve the certificate](#retrieve-a-certificate) again for a fresh link. `null` until a document is attached, which is common while the certificate is `processing`. Only returned when you retrieve a single certificate.

> [!WARNING]
>
> Treat `download_url` as confidential. Anyone with the link can read the certificate until it expires. Don't log it, cache it past one hour, or show it on a public page.

### Certificate statuses

| `status` | Meaning |
| - | - |
| `processing` | Numeral is still reviewing the certificate. |
| `needs_info` | More information is needed before it can be approved. |
| `active` | Valid and in effect. |
| `expiring` | Will expire soon. |
| `expired` | Has lapsed. May be renewable, depending on the jurisdiction. |
| `invalid` | Rejected, revoked, or couldn't be validated. |

`expiring` and `expired` are separate from `invalid`, so you can tell a lapsed certificate that can be renewed from one that was rejected or revoked.

```json The Certificate object theme={null}
{
  "id": "cert_a8f3d2c1-1b9a-4c5e-8d7e-6f4a3b2c1d0e",
  "object": "tax.exemption_certificate",
  "status": "active",
  "certificate_type": {
    "id": "12",
    "name": "California Resale Certificate (CDTFA-230)",
    "certificate_identifier": "US-CA-CDTFA-230"
  },
  "customer": {
    "id": "cust_6126acaf-7379-411a-8ada-00005bac0715",
    "reference_customer_id": "20506",
    "name": "Acme Co"
  },
  "linked_buyer": {
    "id": "12345",
    "legal_name": "Acme Co"
  },
  "jurisdictions": [
    {
      "jurisdiction_id": "US-CA",
      "status": "active",
      "effective_date": "2026-01-01",
      "expiration_date": "2027-01-01"
    }
  ],
  "effective_date": "2026-01-01",
  "expiration_date": "2027-01-01",
  "created_at": "2026-01-02T10:15:23.456Z",
  "updated_at": "2026-01-05T11:00:00.000Z",
  "livemode": true,
  "download_url": "https://numeral-ecm-documents.s3.amazonaws.com/certificates/cert_a8f3d2c1.pdf?X-Amz-Expires=3600&X-Amz-Signature=..."
}
```

```json Multi-state jurisdictions theme={null}
{
  "jurisdictions": [
    {
      "jurisdiction_id": "US-CA",
      "status": "active",
      "effective_date": "2026-01-01",
      "expiration_date": "2027-01-01"
    },
    {
      "jurisdiction_id": "US-TX",
      "status": "expiring",
      "effective_date": "2026-01-01",
      "expiration_date": "2026-07-15"
    }
  ]
}
```

## List certificates

Returns your certificates. Archived, deleted, and revised (child) certificates aren't included. [Try it in the API reference](https://docs.numeral.com/api-reference/v2026-11-01/endpoint/list_certificates).

### Parameters

**status** `enum`

`processing`, `needs_info`, `active`, `expiring`, `expired`, or `invalid`.

**customer\_id** `string`

Only certificates for this customer. Read as the type set by `id_type`.

**id\_type** `enum` (default: id)

`id` for the Numeral `cust_…` ID, or `reference_customer_id` for your own ID. Any other value returns `INVALID_REQUEST`, even without `customer_id`.

**jurisdiction** `string`

Only certificates that cover this jurisdiction, e.g. `US-CA`. A multi-state certificate is returned if any of its jurisdictions match.

**certificate\_type\_id** `string`

Only certificates of this type. Takes the identifier (`US-CA-CDTFA-230`) or the numeric type ID (`12`).

**created\_after** `string`

ISO 8601 timestamp. `created_before`, `updated_after`, and `updated_before` work the same way.

**limit** `integer` (default: 10)

Items per page, 1 to 100.

**cursor** `string`

The `next_cursor` from the previous page.

### Returns

An object with a `certificates` array of [Certificate objects](#the-certificate-object), without `download_url`, and `has_more`. When there are more pages, `next_cursor` is the last ID on this page. It's left out (not `null`) on the last page.

```bash GET /tax/certificates theme={null}
curl 'https://api.numeralhq.com/tax/certificates?jurisdiction=US-CA&status=active' \
  -H 'Authorization: Bearer <token>' \
  -H 'X-API-Version: 2026-11-01'
```

```json Response theme={null}
{
  "object": "list",
  "certificates": [
    {
      "id": "cert_a8f3d2c1-1b9a-4c5e-8d7e-6f4a3b2c1d0e",
      "object": "tax.exemption_certificate",
      "status": "active",
      "...": "see The Certificate object"
    }
  ],
  "has_more": false
}
```

## Retrieve a certificate

Returns a certificate by its `id`, with a `download_url` for the original document. [Try it in the API reference](https://docs.numeral.com/api-reference/v2026-11-01/endpoint/get_certificate).

When `download_url` is `null`, the certificate still exists and the response is `200`. Retrieve it again later to get the link.

**certificate\_id** `string` *required*

The certificate's `id`, `cert_…`.

### Returns

A [Certificate object](#the-certificate-object). A certificate that doesn't exist, or belongs to another account, returns `CERTIFICATE_NOT_FOUND`.

```bash GET /tax/certificates/:id theme={null}
curl https://api.numeralhq.com/tax/certificates/cert_a8f3d2c1-1b9a-4c5e-8d7e-6f4a3b2c1d0e \
  -H 'Authorization: Bearer <token>' \
  -H 'X-API-Version: 2026-11-01'
```

## Submit a certificate

Sends a certificate document you already have to Numeral. Numeral downloads it from your URL, stores it, and works out the certificate type and jurisdiction from the document. There's no `certificate_type_id` input. [Try it in the API reference](https://docs.numeral.com/api-reference/v2026-11-01/endpoint/submit_certificate).

Processing happens in the background. The response is a [certificate upload](#the-certificate-upload-object), not a certificate. [Retrieve the upload](#retrieve-a-certificate-upload) until its `status` leaves `processing`, then read `certificate_ids`. For the full flow, see [Submit Certificates via API](https://docs.numeral.com/essentials/integration-guides/submit-certificates-via-api.md).

### Parameters

**document\_url** `string` *required*

An `https` URL Numeral's servers can download without a session or cookie, usually a presigned URL to your own storage.

* PDF, PNG, or JPEG, detected from the file content, not the extension or `Content-Type`.
* 10 MB max.
* Up to 3 redirects, each also `https`.

Share links (Google Drive, Dropbox, and similar) return an HTML page instead of the file and fail with `UNSUPPORTED_FILE_TYPE`. Use a direct download link.

**file\_name** `string`

Name for the document, 1 to 255 characters. Defaults to the URL's file name when it has an extension, otherwise to a name based on the file type.

**customer** `object`

The customer the certificate belongs to. Send at least one of `id` or `reference_customer_id`. Without `customer`, Numeral matches the certificate to a buyer during processing, and the response `customer` is `null`.

**customer.id** `string`

An existing Numeral customer ID. An unknown ID returns `CUSTOMER_NOT_FOUND`.

**customer.reference\_customer\_id** `string`

Your customer ID. An unknown value creates the customer, so you can send a certificate before the customer exists in Numeral.

If `customer.id` and `customer.reference_customer_id` point to different customers, the request fails with `BUYER_IDENTITY_CONFLICT` and nothing is saved.

> [!TIP]
>
> Send `reference_customer_id`. Once the certificate is `active`, Numeral applies the exemption to that customer's calculations automatically.

Sending the same file again with the same `file_name` doesn't reprocess it. The response has `status: "duplicate"` and empty `certificate_ids`, so use your original upload's ID to find its certificates. If the original upload failed, `error_message` says why.

### Returns

`201` with a [Certificate upload object](#the-certificate-upload-object). `status` is `processing` or `duplicate`, and `certificate_ids` is empty.

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

```json Response 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
}
```

## The Certificate upload object

A certificate upload is a document you sent with [Submit a certificate](#submit-a-certificate). It's a processing job, not a certificate: one document can produce several certificates, or none.

### Attributes

**id** `string`

The ID of the upload, `upl_<number>`. Endpoints that take an upload ID also accept the number alone.

**object** `string, value is "tax.certificate_upload"`

String representing the object's type.

**status** `enum`

`processing`, `completed`, `failed`, or `duplicate`. These are different from certificate statuses. See [Statuses](#upload-statuses).

**customer** `object`

The customer sent with the upload. `null` when the upload was sent without one.

**customer.id** `string`

The Numeral customer ID.

**customer.reference\_customer\_id** `string`

Your customer ID. Can be `null`.

**certificate\_ids** `array of strings`

`cert_…` IDs of the certificates created from this upload. Empty while `processing`. Filled in when `completed`.

**error\_message** `string`

Why processing failed. Also set on a `duplicate` upload when the original upload failed. Otherwise `null`.

**created\_at** `string`

ISO 8601 timestamp when the upload was created. Can be `null`.

**livemode** `boolean`

Always `true`.

**document\_url** `string`

Link to the document you sent. Expires one hour after the response. Can be set while `status` is still `processing`. `null` when no document is attached. Only returned when you retrieve a single upload.

> [!WARNING]
>
> Treat `document_url` as confidential. Anyone with the link can download the document until it expires.

### Upload statuses

| `status` | Meaning |
| - | - |
| `processing` | Numeral is downloading or classifying the document. `certificate_ids` is empty. |
| `completed` | Done. `certificate_ids` lists the certificates created. |
| `failed` | Processing failed. `error_message` says why. |
| `duplicate` | You already sent this file with this `file_name`. It wasn't reprocessed. Use the original upload's ID to find its certificates. |

`processing` is the only status that changes. Once an upload is `completed`, `failed`, or `duplicate`, it stays that way.

> [!NOTE]
>
> A `completed` upload doesn't mean an `active` certificate. Each certificate in `certificate_ids` has its own `status`, such as `processing` or `needs_info`, while Numeral reviews it. [Retrieve each certificate](#retrieve-a-certificate) before relying on the exemption.

```json The Certificate upload object theme={null}
{
  "id": "upl_4470",
  "object": "tax.certificate_upload",
  "status": "completed",
  "customer": {
    "id": "cust_6126acaf-7379-411a-8ada-00005bac0715",
    "reference_customer_id": "20506"
  },
  "certificate_ids": ["cert_a8f3d2c1-1b9a-4c5e-8d7e-6f4a3b2c1d0e"],
  "error_message": null,
  "created_at": "2026-09-15T16:58:02.000Z",
  "livemode": true,
  "document_url": "https://numeral-ecm-documents.s3.amazonaws.com/uploads/upl_4470.pdf?X-Amz-Expires=3600&X-Amz-Signature=..."
}
```

```text Upload lifecycle theme={null}
POST /tax/certificates
├─ Same file and file_name already sent     → duplicate
└─ processing
      ├─ Classification succeeds            → completed  (certificate_ids set)
      └─ Classification fails               → failed     (error_message set)
```

## List certificate uploads

Returns your certificate uploads, oldest first (by ID). Uploads in every status are included, so filter by `status` to narrow the list. [Try it in the API reference](https://docs.numeral.com/api-reference/v2026-11-01/endpoint/list_certificate_uploads).

### Parameters

**status** `enum`

`processing`, `completed`, `failed`, or `duplicate`. Any other value returns `INVALID_REQUEST`.

**customer\_id** `string`

Only uploads for this customer. Read as the type set by `id_type`. Only live-mode customers match.

**id\_type** `enum` (default: id)

`id` for the Numeral customer ID, or `reference_customer_id` for your own ID. Any other value returns `INVALID_REQUEST`.

**limit** `integer` (default: 10)

Items per page, 1 to 100.

**cursor** `string`

The `next_cursor` from the previous page, as `upl_…` or the number alone. Returns uploads with a higher ID. A malformed value returns `INVALID_REQUEST`.

### Returns

An object with a `certificate_uploads` array of [Certificate upload objects](#the-certificate-upload-object), without `document_url`, and `has_more`. `next_cursor` is the last ID on this page, and is only present when `has_more` is `true`. [Retrieve an upload](#retrieve-a-certificate-upload) to get its `document_url`.

```bash GET /tax/certificate-uploads theme={null}
curl 'https://api.numeralhq.com/tax/certificate-uploads?limit=3' \
  -H 'Authorization: Bearer <token>' \
  -H 'X-API-Version: 2026-11-01'
```

```json Response theme={null}
{
  "object": "list",
  "certificate_uploads": [
    {
      "id": "upl_4469",
      "object": "tax.certificate_upload",
      "status": "failed",
      "customer": null,
      "certificate_ids": [],
      "error_message": "Document could not be classified as an exemption certificate.",
      "created_at": "2026-09-15T16:41:37.000Z",
      "livemode": true
    },
    {
      "id": "upl_4470",
      "object": "tax.certificate_upload",
      "status": "completed",
      "customer": {
        "id": "cust_6126acaf-7379-411a-8ada-00005bac0715",
        "reference_customer_id": "20506"
      },
      "certificate_ids": ["cert_a8f3d2c1-1b9a-4c5e-8d7e-6f4a3b2c1d0e"],
      "error_message": null,
      "created_at": "2026-09-15T16:58:02.000Z",
      "livemode": true
    },
    {
      "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
    }
  ],
  "has_more": true,
  "next_cursor": "upl_4471"
}
```

## Retrieve a certificate upload

Returns an upload by its `id`, with a `document_url` for the document you sent. Call it after [submitting a certificate](#submit-a-certificate) until `status` leaves `processing`, then [retrieve each certificate](#retrieve-a-certificate) in `certificate_ids`. [Try it in the API reference](https://docs.numeral.com/api-reference/v2026-11-01/endpoint/get_certificate_upload).

**upload\_id** `string` *required*

The upload's `id` (`upl_4471`) or the number alone (`4471`).

### Returns

A [Certificate upload object](#the-certificate-upload-object). An unknown ID, a malformed ID, and an upload in another account all return `CERTIFICATE_UPLOAD_NOT_FOUND`.

```bash GET /tax/certificate-uploads/:id theme={null}
curl https://api.numeralhq.com/tax/certificate-uploads/upl_4470 \
  -H 'Authorization: Bearer <token>' \
  -H 'X-API-Version: 2026-11-01'
```

## Errors

Errors return `code`, `type`, and `message`. Handle them by `type`. Full list in [Error Codes](https://docs.numeral.com/api-reference/v2026-11-01/error-codes.md).

| HTTP | `type` | When |
| - | - | - |
| 400 | `TESTMODE_NOT_SUPPORTED` | A test API key (`sk_test_*`) was used. Every certificate endpoint is live-only. |
| 400 | `INVALID_REQUEST` | An unknown `id_type`, or a bad `status` or `cursor` when listing uploads. |
| 400 | `INVALID_REQUEST` | Submit: `document_url` is missing, `customer` is empty, or the body is otherwise invalid. |
| 400 | `INVALID_REQUEST` | Submit and the upload endpoints: `X-API-Version` is earlier than `2026-03-01`. |
| 400 | `INVALID_DOCUMENT_URL` | `document_url` isn't a valid `https` URL, or points to a blocked, unresolvable, or private host. |
| 400 | `DOCUMENT_FETCH_FAILED` | The document couldn't be downloaded: network error, too many redirects, an HTTP error from the host, or an empty file. |
| 400 | `UNSUPPORTED_FILE_TYPE` | The document isn't a PDF, PNG, or JPEG. Share links that return an HTML page fail with this. |
| 400 | `FILE_TOO_LARGE` | The document is over 10 MB. |
| 404 | `CERTIFICATE_REQUEST_NOT_FOUND` | The request doesn't exist or belongs to another account. |
| 404 | `CERTIFICATE_NOT_FOUND` | The certificate doesn't exist or belongs to another account. |
| 404 | `CERTIFICATE_UPLOAD_NOT_FOUND` | The upload doesn't exist, the ID is malformed, or it belongs to another account. |
| 404 | `CUSTOMER_NOT_FOUND` | Submit: `customer.id` doesn't exist in your account. |
| 409 | `REQUEST_ALREADY_FULFILLED` | Cancel: the request is already fulfilled. Retrieve the certificate instead. |
| 409 | `REQUEST_INVALID` | Cancel: the request is `invalid`. |
| 409 | `BUYER_IDENTITY_CONFLICT` | Submit: `customer.id` and `customer.reference_customer_id` point to different customers. Nothing was saved. |
| 422 | `DOCUMENT_URL_UNAUTHORIZED` | The document host returned 401 or 403, usually an expired presigned URL. Create a fresh URL and retry. |
| 500 | `NUMERAL_ERROR` | Submit: the document was stored but processing couldn't start. Retry the submission. |

```json 404 upload theme={null}
{
  "code": 404,
  "type": "CERTIFICATE_UPLOAD_NOT_FOUND",
  "message": "Certificate upload not found"
}
```

```json 404 customer theme={null}
{
  "code": 404,
  "type": "CUSTOMER_NOT_FOUND",
  "message": "Customer not found"
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.