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

# Get Certificate Upload

> Retrieve a single certificate upload, including a link to the submitted document

Retrieve a single certificate upload by id, including a short-lived
`document_url` for the originally submitted document. This is the polling
endpoint — after
[`POST /tax/certificates`](/api-reference/v2026-03-01/endpoint/submit_certificate),
call it until `status` leaves `processing`, then read `certificate_ids`.

`upload_id` accepts the `upl_*` form (`upl_4471`) or the bare numeric form
(`4471`).

<Note>
  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). Uploads outside your
  account return `CERTIFICATE_UPLOAD_NOT_FOUND` (404) — unknown, malformed, and
  other-account ids are indistinguishable by design.
</Note>

## Polling

`processing` is the only non-terminal status. `completed` populates
`certificate_ids`, `failed` populates `error_message`, and `duplicate` is
assigned at submit time. Once the status is terminal it will not change, so
you can stop polling.

Each id in `certificate_ids` is a `cert_*` id usable with
[`GET /tax/certificates/{certificate_id}`](/api-reference/v2026-03-01/endpoint/get_certificate)
to read the certificate's own status, jurisdictions, and download URL.

## The `document_url` field

`document_url` is a pre-signed URL pointing to the document you submitted.
It expires **one hour** after the response is issued — re-fetch the upload
to mint a fresh URL. It may be non-null while `status` is still
`processing`.

`document_url` is `null` (not omitted) when no document is attached to the
upload. The upload metadata is still returned — the response is `200`, not
`404`.

<Warning>
  Treat `document_url` as confidential. Anyone with the URL can download the
  document until the URL expires. Do not log, cache past the TTL, or expose
  the URL on a public page.
</Warning>


## OpenAPI

````yaml GET /tax/certificate-uploads/{upload_id}
openapi: 3.0.1
info:
  title: Numeral API
  description: >-
    API for sales tax calculations - Version 2026-03-01. This version adds
    IP-based tax resolution for calculations and platform calculations, in
    addition to Merchant management and Platform Calculations.
  license:
    name: MIT
  version: '2026-03-01'
servers:
  - url: https://api.numeralhq.com/
security:
  - bearerAuth: []
paths:
  /tax/certificate-uploads/{upload_id}:
    get:
      summary: Get Certificate Upload
      description: >-
        Retrieve a single certificate upload by id, including a short-lived
        `document_url` for the originally submitted document. This is the
        polling endpoint — call it until `status` leaves `processing`.
        Live-only.
      operationId: getCertificateUpload_v20260301
      parameters:
        - name: X-API-Version
          in: header
          required: true
          schema:
            type: string
            enum:
              - '2026-03-01'
          description: >-
            Must be `2026-03-01` or later. Older versions return
            `INVALID_REQUEST` (400).
        - name: upload_id
          in: path
          required: true
          schema:
            type: string
          description: The upload id (`upl_4471`) or its bare numeric form (`4471`).
      responses:
        '200':
          description: Certificate upload detail (includes pre-signed `document_url`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CertificateUploadDetailResponse'
              example:
                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=...
        '400':
          description: >-
            `TESTMODE_NOT_SUPPORTED` or `INVALID_REQUEST` (API version below
            2026-03-01)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: >-
            `CERTIFICATE_UPLOAD_NOT_FOUND` — unknown id, malformed id, or an
            upload belonging to another account (indistinguishable by design)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CertificateUploadNotFoundError'
components:
  schemas:
    CertificateUploadDetailResponse:
      allOf:
        - $ref: '#/components/schemas/CertificateUploadResponse'
        - type: object
          properties:
            document_url:
              type: string
              format: uri
              nullable: true
              description: >-
                Pre-signed URL to the originally submitted document. Valid for
                one hour — refetch the upload to mint a new URL. May be non-null
                while `status` is still `processing`. `null` (response still
                `200`) when no document is attached. Not returned by the list
                endpoint. Treat as confidential.
          required:
            - document_url
    Error:
      type: object
      description: Standard error response format for API version 2026-01-01
      properties:
        code:
          type: integer
          description: HTTP status code
          example: 400
        type:
          type: string
          description: Machine-readable error type
          example: MISSING_FIELD
        message:
          type: string
          description: Human-readable error message
          example: Required field 'address_country' is missing
      required:
        - code
        - type
        - message
    CertificateUploadNotFoundError:
      type: object
      properties:
        code:
          type: integer
          example: 404
        type:
          type: string
          example: CERTIFICATE_UPLOAD_NOT_FOUND
        message:
          type: string
          example: Certificate upload not found
    CertificateUploadResponse:
      type: object
      description: >-
        A certificate upload — the asynchronous processing job created by `POST
        /tax/certificates`.
      properties:
        id:
          type: string
          description: >-
            Upload id, `upl_<number>`. Endpoints that accept an upload id also
            accept the bare numeric form.
          example: upl_4471
        object:
          type: string
          description: 'The type of object: `tax.certificate_upload`'
          example: tax.certificate_upload
        status:
          $ref: '#/components/schemas/CertificateUploadStatus'
        customer:
          allOf:
            - $ref: '#/components/schemas/CertificateUploadCustomer'
          nullable: true
          description: >-
            The customer supplied at submission. `null` when the upload was
            submitted without a customer — attribution then happens during
            processing.
        certificate_ids:
          type: array
          items:
            type: string
            example: cert_a8f3d2c1-1b9a-4c5e-8d7e-6f4a3b2c1d0e
          description: >-
            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:
          type: string
          nullable: true
          description: >-
            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:
          type: string
          format: date-time
          nullable: true
        livemode:
          type: boolean
          enum:
            - true
          description: Always `true` — these endpoints are live-only.
      required:
        - id
        - object
        - status
        - customer
        - certificate_ids
        - error_message
        - created_at
        - livemode
    CertificateUploadStatus:
      type: string
      enum:
        - processing
        - completed
        - failed
        - duplicate
      description: >-
        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.
    CertificateUploadCustomer:
      type: object
      description: The customer the upload was attributed to at submission.
      properties:
        id:
          type: string
          example: cust_6126acaf-7379-411a-8ada-00005bac0715
        reference_customer_id:
          type: string
          nullable: true
          example: '20506'
      required:
        - id
        - reference_customer_id
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````