# GET /certificate/{identifier}

Read a product's certificate of authenticity from its certificate number, its UID hash, its token id or its printed serial number. Public endpoint.

Source: https://docs.sealtrust.io/en/reference/get-certificate/

---

You are reading a product's certificate of authenticity. When you leave this
page, you will know which identifier to send, how to read the certificate
status, and which responses to expect when the product or the certificate does
not exist.

Full address:

```http
GET https://api.sealtrust.io/v1/certificate/{identifier}
```

The same endpoint also answers without the `/v1` prefix, at
`https://api.sealtrust.io/certificate/{identifier}`. Both addresses call the
same code. Use the `/v1` form for a new integration.

## Authorization

None, public endpoint. Send neither an API key nor a session token.

The response carries no internal brand or product identifier. It gives the
certificate number, the status, the dates, the product name, the brand name and
its styling. It gives no brand number, no product number, no contract address
and no owner address.

> [!INFO] Call this endpoint from your server
> The browser allows cross-origin calls only from SealTrust sites. A web page
> hosted on your own domain will have its request blocked by the browser. Go
> through your server, or through a mobile app, where this rule does not apply.

## Rate limit

60 calls per 60-second window, counted per IP address.

This counter is shared by every address that starts with `/certificate`, with or
without the `/v1` prefix. Reading the certificate and downloading its PDF are
counted in the same counter.

Every accepted response carries three headers.

| Header | Content |
| --- | --- |
| `X-RateLimit-Limit` | the limit applied over the window, here `60` |
| `X-RateLimit-Remaining` | what is left to you in the current window |
| `X-RateLimit-Reset` | the timestamp for the end of the window, in seconds |

A refusal returns 429 with the same three headers, `X-RateLimit-Remaining` at
`0`, and `Retry-After` equal to `60`.

## Path and query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | yes | The product or the certificate to read. Four forms are accepted, described below. |

This endpoint has no query parameter.

### The four identifier forms

| Form | What it looks like |
| --- | --- |
| Certificate number | A string that starts with `ST-CERT-`, followed by 12 characters. It is the `certificate_number` field this same response returns. |
| UID hash | `0x` followed by 64 hexadecimal characters, that is 66 characters in all. Case does not matter. |
| Token id | The token's whole number, written in digits. It has 77 or 78 digits. |
| Printed serial number | The 12 characters carried on the product label, the ones found in the `/p/{serial}` address. |

The server first looks for a certificate number. If it finds none, it looks at
the shape of the string: `0x` followed by 64 hexadecimal characters is treated
as a UID hash, any other shape as a token id. As a last resort it looks for a
printed serial number.

The server strips leading and trailing spaces before the lookup, whatever the
form.

The server ignores the case of the serial number and brings the characters that
are confused when read back to their canonical form before the lookup: `I` and
`L` count as `1`, `O` counts as `0`. So you still find a number copied by hand
from a label even if the person typed the letter `O` where the label carries the
digit `0`.

> [!ATTENTION] A product that has been destroyed or withdrawn from the catalog no longer resolves
> The UID hash, the token id and the serial number find only the products still
> in the catalog. A product that has been destroyed, replaced by a later version
> or archived answers 404. The certificate number, for its part, stays resolved
> in every case.

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Accept` | `string` | no | If it contains `text/html`, the response is a 307 redirect to the public certificate page, readable by a human. Any other value, including `*/*` and `application/json`, gives the JSON described below. |

`curl`, `requests` and `fetch` send `*/*` by default and therefore receive the
JSON. The redirect exists so that a certificate link shared and opened in a
browser displays the public certificate page.

## Request body

None. This request has no body.

## Example request

Reading the certificate whose number is `ST-CERT-000000000000`.

:::onglets
```bash title="curl"
curl -i -H "Accept: application/json" https://api.sealtrust.io/v1/certificate/ST-CERT-000000000000
```
```typescript
const response = await fetch(
  "https://api.sealtrust.io/v1/certificate/ST-CERT-000000000000",
  {
    method: "GET",
    headers: {
      Accept: "application/json",
    },
  },
);

console.log(response.status);
console.log(response.headers.get("X-RateLimit-Remaining"));
console.log(await response.json());
```
```python
import requests

response = requests.get(
    "https://api.sealtrust.io/v1/certificate/ST-CERT-000000000000",
    headers={
        "Accept": "application/json",
    },
    timeout=30,
)

print(response.status_code)
print(response.headers["X-RateLimit-Remaining"])
print(response.json())
```
:::

The TypeScript SDK `@sealtrust-io/sdk` does not cover this endpoint. The example
above uses `fetch`, available with no dependency.

The three other identifier forms are written the same way.

```bash
# UID hash
curl -i -H "Accept: application/json" https://api.sealtrust.io/v1/certificate/0x0000000000000000000000000000000000000000000000000000000000000000

# Token id
curl -i -H "Accept: application/json" https://api.sealtrust.io/v1/certificate/10000000000000000000000000000000000000000000000000000000000000000000000000000

# Printed serial number
curl -i -H "Accept: application/json" https://api.sealtrust.io/v1/certificate/00000000ABCD
```

## Example response

HTTP code `200`.

```json
{
  "certificate_number": "ST-CERT-000000000000",
  "status": "active",
  "issued_at": "2026-03-04T10:22:07.415000Z",
  "expires_at": null,
  "issuer_name": "Exemple SAS",
  "product_name": "Sac cabas modèle 1",
  "brand_name": "Exemple SAS",
  "brand_logo_url": "https://exemple-sas.test/logo.svg",
  "brand_primary_color": "#1F2937",
  "brand_hide_powered_by": false,
  "custom_fields": {
    "atelier": "Atelier 3",
    "matiere": "cuir pleine fleur"
  }
}
```

The response has eleven fields and nothing else.

| Field | Type | Description |
| --- | --- | --- |
| `certificate_number` | `string` | The number of the certificate. It is the value to reuse as `identifier` to find this certificate directly. |
| `status` | `string` | The status of the certificate. See below. |
| `issued_at` | `string` | Issue date and time, in universal time, in ISO 8601 format. |
| `expires_at` | `string` or `null` | End of validity date. No certificate issued by the platform carries one today, the value is always `null`. Do not build your integration on an end date. |
| `issuer_name` | `string` or `null` | The name of the brand that issued the certificate. The server computes this field at read time and always puts the brand name in it. `null` when the certificate is attached to no brand. |
| `product_name` | `string` or `null` | The name of the product covered by the certificate. |
| `brand_name` | `string` or `null` | The name of the issuing brand. |
| `brand_logo_url` | `string` or `null` | The address of the brand logo, to display the certificate in the brand's colors. |
| `brand_primary_color` | `string` or `null` | The primary color of the brand. |
| `brand_hide_powered_by` | `boolean` | `true` when the brand's plan includes white labeling. The certificate page then hides the SealTrust mention. The default value is `false`, so an absent field means the mention stays displayed. |
| `custom_fields` | `object` or `null` | The free fields the brand filled in when issuing the certificate. `null` when it filled in none. The content is specific to each brand, no key is imposed. |

### How to read `status`

The server computes the status at read time. The value stored in the database is
not copied as is.

`status` is either `active` or `revoked`.

- A revoked certificate is returned as `revoked` forever. Revocation is a
  deliberate act and it prevails over every other rule. No endpoint returns a
  revoked certificate in the `active` status.
- Every other certificate is returned as `active`.

The vocabulary of the API contains a third value, `expired`. The server computes
it at read time from `expires_at`. Since no certificate carries an end date
today, the API does not return it. Accept it in your code to stay robust if it
appears one day, and build no business rule on its presence.

> [!INFO] A revoked certificate answers 200
> When a product carries several certificates, the server returns the most
> recent one that is still valid. If none is valid, it still returns the most
> recent of the others, with its real status. So you receive `revoked` in the
> clear. The server does not answer 404 in that case. A 404 would suggest that
> the certificate never existed.

### Redirect to the readable page

If your request announces `text/html` in the `Accept` header, the response is a
`307` whose `Location` header points to the public certificate page. The server
returns no JSON body in that case.

## Errors

The body of an error response carries a `detail` field.

| Code | Condition | What to do |
| --- | --- | --- |
| 404 | The requested path matches no route, for example because the identifier contains an unencoded slash. `detail` is `Not Found`. | Encode the identifier before placing it in the address. |
| 404 | No product matches this identifier, or the matching product has been destroyed, replaced or archived. `detail` is `Product not found`. | Check the form of the identifier. A serial number is typed as it appears on the label, over 12 characters. |
| 404 | The product exists, but no certificate has ever been issued for it. `detail` is `No certificate found for this product`. | The product can be genuine without carrying a certificate. Use the product passport to display it. |
| 429 | The limit of 60 calls per 60 seconds is reached for your IP address. `detail` is `Rate limit exceeded: 60 requests per 60s`. The `Retry-After` header and the `X-RateLimit-*` family accompany the response. | Wait the number of seconds indicated by `Retry-After`, then try again. This counter is shared with the PDF download. |
| 500 | An unexpected error occurred while your call was being processed. `detail` is `Internal Server Error`. | Try again. If the error persists, contact support giving the time of the call. |

## See also

- [`GET /certificate/{identifier}/download`](/en/reference/get-certificate-download/),
  download the certificate of authenticity as a PDF.
- [`GET /p/{serial}`](/en/reference/get-p-serial/),
  translate the printed serial number into a consumer page address.
- [`GET /resolve/{identifier}`](/en/reference/get-resolve/),
  read in one call everything a product page displays.
- [Core concepts](/en/notions/),
  tell a model, a batch and an item apart before ordering a single label.
