# GET /certificate/{identifier}/download

Télécharger le certificat d'authenticité d'un article au format PDF, aux couleurs de votre marque. Point d'entrée public, sans clef d'API.

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

---

Vous récupérez un fichier PDF prêt à imprimer ou à joindre à un message : le
certificat d'authenticité d'un article, aux couleurs de votre marque, en
français ou en anglais.

L'adresse complète est
`https://api.sealtrust.io/v1/certificate/{identifier}/download`. La même route
existe sans le préfixe `/v1`, et c'est la forme `/v1` qui est recommandée pour
une nouvelle intégration.

En cas de succès, la réponse est le document lui-même, de type
`application/pdf`, servi en pièce jointe. Écrivez le corps de la réponse dans un
fichier. Ne tentez pas de le lire comme du texte.

Le serveur fabrique le document à chaque appel et ne le stocke nulle part. Deux
appels successifs peuvent donc donner deux fichiers différents si l'état du
certificat a changé entre-temps.

> [!ATTENTION] Toute personne qui connaît l'identifiant obtient le document
> Ce point d'entrée ne demande aucune clef d'API et aucune session. L'identifiant
> que vous mettez dans l'adresse est la seule chose qui protège le fichier.
> Traitez un numéro de certificat comme une donnée que vous ne diffusez qu'aux
> personnes à qui vous voulez remettre le certificat. Le document imprime aussi
> le nom de la marque émettrice et jusqu'à quatre des champs libres attachés au
> certificat : n'y mettez rien que vous ne voudriez pas rendre public.

## Autorisation

Aucune, point d'entrée public. Il n'attend ni clef d'API, ni cookie de session,
ni en-tête `Authorization`. Un appel serveur à serveur est accepté.

## Plafond d'appels

60 appels par fenêtre de 60 secondes, comptés par adresse IP appelante. Toutes
les adresses qui commencent par `/certificate` partagent ce compteur, et la
forme `/v1/certificate/{identifier}/download` compte dans le même compteur que
la forme sans préfixe.

En temps normal, chaque réponse porte les en-têtes `X-RateLimit-Limit`,
`X-RateLimit-Remaining` et `X-RateLimit-Reset`, ce dernier donnant l'heure de
remise à zéro en secondes depuis le 1er janvier 1970. Traitez ces trois en-têtes
comme facultatifs : lisez-les quand ils sont là, ne faites pas dépendre votre
intégration de leur présence. Un dépassement renvoie 429 avec en plus
`Retry-After`, en secondes.

Ce point d'entrée ne consomme aucun quota de votre offre.

## Paramètres de chemin et de requête

| Nom | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | oui | Ce qui désigne l'article ou le certificat. Quatre formes sont acceptées, voir le détail ci-dessous. Le serveur retire les espaces de bord. |
| `lang` | `string` | non | Langue du document : `fr` ou `en`. Valeur par défaut `en`. |

### Les quatre formes de l'identifiant

Le serveur essaie d'abord le numéro de certificat. S'il n'aboutit pas, il
regarde la forme de la valeur : `0x` suivi de 64 caractères hexadécimaux est
traité comme une empreinte d'article, toute autre valeur comme un identifiant de
jeton. Une empreinte n'est donc jamais essayée comme identifiant de jeton, et
l'inverse non plus. En dernier recours, le serveur essaie le numéro de série
imprimé.

| Forme | Exemple | Détail |
| --- | --- | --- |
| Numéro de certificat | `ST-CERT-000000000000` | Comparaison exacte, la casse compte. C'est le numéro que votre console affiche sur le certificat et que renvoie `GET /certificate/{identifier}`. |
| Empreinte de l'article | `0x0000000000000000000000000000000000000000000000000000000000000000` | `0x` suivi de 64 caractères hexadécimaux. La casse n'a pas d'importance. |
| Identifiant du jeton | `11111111111111111111111111111111111111111111111111111111111111111111111111111` | Le nombre porté par le jeton sur la chaîne, en base 10, tel quel. Ce nombre fait 77 à 78 chiffres. Lisez-le comme du texte, jamais comme un entier de votre langage. |
| Numéro de série imprimé | `00000000ABCD` | Les 12 caractères imprimés sur l'étiquette de l'article. La casse n'a pas d'importance, et le serveur ramène à leur forme canonique les caractères que l'on confond à la lecture : `I` et `L` valent `1`, `O` vaut `0`. Vous pouvez donc recopier un numéro à la main sans vous soucier de ces trois lettres. |

Les trois dernières formes désignent un article. Le serveur cherche alors le
certificat de cet article : le certificat en cours de validité s'il en existe
un, sinon le plus récent quel que soit son état.

Quand il résout ces trois formes, le serveur écarte les articles détruits et les
articles retirés du catalogue, et répond 404. Le numéro de certificat ne passe
pas par l'article : il trouve la ligne du certificat directement, et le document
se télécharge même quand l'article a été détruit ou retiré du catalogue.

### La langue du document

`fr` et `en` sont les deux valeurs prévues, en minuscules. Une valeur qui ne
commence ni par `fr` ni par `en` produit un document en anglais.

> [!ATTENTION] Envoyez la langue en minuscules
> `FR` en majuscules donne un document dont les libellés sont en français, mais
> les dates et le texte du sceau filigrane restent en anglais. Envoyez `fr`, ou
> `fr-FR`, qui donnent tous deux un document entièrement français.

## Corps de la requête

Aucun. C'est une requête `GET`, tout passe par l'adresse.

## Requête d'exemple

:::onglets
```bash title="curl"
curl -sS -D - \
  -o certificat.pdf \
  "https://api.sealtrust.io/v1/certificate/ST-CERT-000000000000/download?lang=fr"
```
```typescript
import { writeFile } from "node:fs/promises";

const response = await fetch(
  "https://api.sealtrust.io/v1/certificate/ST-CERT-000000000000/download?lang=fr",
);

if (!response.ok) {
  throw new Error(`${response.status} ${await response.text()}`);
}

console.log(response.headers.get("Content-Type"));
console.log(response.headers.get("Content-Disposition"));
console.log(response.headers.get("X-RateLimit-Remaining"));

await writeFile("certificat.pdf", Buffer.from(await response.arrayBuffer()));
```
```python
import requests

response = requests.get(
    "https://api.sealtrust.io/v1/certificate/ST-CERT-000000000000/download",
    params={"lang": "fr"},
    timeout=60,
)
response.raise_for_status()

print(response.headers["Content-Type"])
print(response.headers["Content-Disposition"])
print(response.headers["X-RateLimit-Remaining"])

with open("certificat.pdf", "wb") as fichier:
    fichier.write(response.content)
```
:::

> [!INFO] L'onglet TypeScript appelle l'API en direct, et c'est voulu
> Partout ailleurs sur ce site, l'onglet TypeScript utilise le paquet
> `@sealtrust-io/sdk`. Ici, le SDK n'expose pas ce point d'entrée : il couvre la
> frappe en lot, le suivi de lot, l'historique d'un article, la vérification par
> lot, le contrôle d'intégrité des métadonnées et les abonnements aux
> notifications. Le téléchargement d'un certificat s'appelle donc en HTTP
> direct, comme ci-dessus. Ce n'est pas un oubli de rédaction.

## Réponse d'exemple

Code HTTP 200. Le corps est le fichier PDF.

```http
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="certificate-ST-CERT-000000000000.pdf"
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1755000060
```

Le nom de fichier proposé est toujours `certificate-` suivi du numéro de
certificat, puis `.pdf`. Ce numéro peut différer de l'identifiant que vous avez
envoyé : si vous avez interrogé l'article par son numéro de série, le nom de
fichier porte le numéro du certificat trouvé.

### Ce que contient le document

Une page A4. Voici ce que le serveur y dessine, du haut vers le bas, puis le
cadre qui entoure toute la page.

| Élément | Détail |
| --- | --- |
| Bandeau de titre | « Certificat d'authenticité » ou « Certificate of Authenticity », suivi de « Émis par » et du nom de votre marque. Le bandeau est peint dans votre couleur secondaire. |
| Logo | Le logo de votre marque. Le serveur va le chercher seulement si son adresse est en `https`, si elle répond 200 en moins de 4 secondes avec un type `image/...`, et sans redirection. Une adresse en `http`, une redirection, un délai plus long ou une adresse qui pointe vers un réseau privé laissent le document sans logo, le reste est inchangé. Le logo récupéré est aussi posé au centre du QR code. |
| Pastille d'état | `ACTIF`, `RÉVOQUÉ` ou `EXPIRÉ`, en haut à droite. Le serveur recalcule l'état au moment du rendu : un certificat enregistré actif dont la date d'expiration est passée s'imprime `EXPIRÉ`. |
| Détails | Produit, numéro de certificat, date d'émission, puis la marque émettrice et la date d'expiration quand elles sont renseignées, puis au plus quatre des champs libres attachés au certificat. Le libellé imprimé d'un champ libre est le nom du champ avec les tirets bas remplacés par des espaces et chaque mot en capitale initiale : `numero_lot` devient `Numero Lot`. La valeur est imprimée telle quelle, convertie en texte. |
| Preuve blockchain | Le nom du réseau, toujours présent, `Base` en production. Puis l'adresse du contrat sous forme abrégée et l'identifiant du jeton, chacun seulement quand l'article en porte un. |
| QR code de vérification | Renvoie vers la page publique du certificat sur le site SealTrust. L'adresse est aussi écrite en toutes lettres sous l'encadré. |
| Sceau filigrane | Deux cercles, une coche et les mots `AUTHENTIQUE` et `VÉRIFIÉ BLOCKCHAIN`, dessinés en transparence dans votre couleur principale, au milieu du bas de page. Le serveur le dessine toujours. |
| Pied de page | « Propulsé par SealTrust, authenticité vérifiée par blockchain » ou « Powered by SealTrust, Blockchain-verified authenticity ». Si votre offre comprend la marque blanche, cette mention n'est pas imprimée et la ligne reste vide. Le serveur dessine toujours le bandeau coloré qui porte ce texte. |
| Cadre | Un liseré arrondi dans votre couleur principale, sur tout le pourtour de la page. Le serveur le dessine toujours. |

Le serveur coupe une valeur trop longue pour sa ligne et la termine par un
caractère de suite. C'est le cas de l'identifiant du jeton, qui fait 77 à 78
chiffres. Ne recopiez pas une valeur longue depuis le document, lisez-la sur
l'API.

Le document reprend la couleur principale et la couleur secondaire de votre
marque. Si votre marque n'a renseigné aucune couleur, le document utilise
`#6386F1` en principale et `#0f172a` en secondaire.

Le logo et les deux couleurs sont les trois réglages qui changent l'allure du
document, et vous les posez vous-même dans l'administration, onglet
`Paramètres`, puis `Marque`. Ils s'appliquent au certificat dès le
téléchargement suivant. Toutes les offres payantes ouvrent cet écran. L'essai
gratuit le garde fermé.

> [!ATTENTION] Un 200 ne veut pas dire « certificat valide »
> Un certificat révoqué et un certificat expiré se téléchargent normalement, en
> 200. C'est la pastille imprimée sur le document qui porte l'état. Si votre
> traitement a besoin de l'état sous forme exploitable, lisez-le d'abord sur
> `GET /certificate/{identifier}`, qui renvoie du JSON.

## Erreurs

Les erreurs, elles, sont bien du JSON. Un document commence par `%PDF`. Un corps
qui commence par `{` signale un refus. Contrôlez le code HTTP avant d'écrire le
fichier.

| Code | Condition | Que faire |
| --- | --- | --- |
| 404 | Aucun certificat ne porte ce numéro, et aucun article au catalogue ne correspond à cet identifiant. Message `Product not found`. Un article détruit, remplacé par une nouvelle frappe ou retiré du catalogue répond la même chose. | Vérifiez la valeur envoyée dans l'adresse. Si l'article a été détruit ou retiré du catalogue, ce code est définitif pour cet identifiant. Le numéro de certificat, lui, continue de fonctionner. |
| 404 | L'article existe, aucun certificat ne lui a jamais été émis. Message `No certificate found`. | Émettez un certificat pour cet article depuis votre console, puis rappelez. |
| 429 | Plus de 60 appels en 60 secondes depuis la même adresse IP, toutes adresses `/certificate` confondues. Message `Rate limit exceeded: 60 requests per 60s`. | Attendez le nombre de secondes indiqué par `Retry-After`. Répartissez vos appels au lieu de les envoyer en rafale. |
| 500 | Erreur inattendue du serveur. Corps figé `{"detail": "Internal Server Error"}`. | Réessayez. Quand l'en-tête `X-Request-Id` est présent, il identifie l'appel : transmettez-le-nous si l'erreur se répète. |

Ce point d'entrée n'a pas d'autre code de refus. Le paramètre `lang` n'est jamais
rejeté, et l'identifiant est accepté quelle que soit sa forme, quitte à ne rien
trouver.

> [!INFO] L'ordre des contrôles
> Les contrôles s'enchaînent dans cet ordre : plafond d'appels, recherche par
> numéro de certificat, puis recherche de l'article par empreinte ou par
> identifiant de jeton selon la forme de la valeur, puis par numéro de série,
> puis recherche du certificat de cet article, puis fabrication du document. Le
> serveur lit `lang` au dernier moment, et une valeur inattendue donne l'anglais.

## Voir aussi

- [`GET /certificate/{identifier}`](/reference/get-certificate/),
  lire le certificat d'authenticité d'un article.
- [`GET /resolve/{identifier}`](/reference/get-resolve/),
  lire en un appel tout ce qu'une page produit affiche.
- [Notions de base](/notions/),
  distinguer modèle, lot et article avant de commander la moindre étiquette.
