# Issue purchase codes

Issue in bulk the purchase codes of the "purchase code required" rule, for pieces that already exist or when a batch is minted. Each code is shown once. Permission claim_codes:issue.

Source: https://docs.sealtrust.io/en/api-codes-achat/

---

A brand can require a purchase code before a buyer becomes the owner of a
piece: that is the "purchase code required" rule
([the claim rules](/en/console/regles-de-revendication/)). The seller hands over
this short code with the piece, on the receipt or the care card. Holding the
object is no longer enough.

This page is for the brand, or the reseller, with hundreds or thousands of
pieces. In the console, a code is issued one piece at a time. With the API, you
issue codes for a list of pieces in one call, or while you mint a batch.

## What you need

- An API key carrying the `claim_codes:issue` permission. It is never ticked by
  default: a code lets whoever holds it claim the piece. You tick it yourself,
  in the console, Settings then Developers.
- An offer that includes API access and piece identity. A brand on the
  Compliance offer gets `403` with the code `OFFER_EXCLUDES_IDENTITY`
  ([the two offers](/en/offres-conformite-et-identite/)).
- For a reseller, a reseller key: it acts for your brand and for each client
  brand of your contract.
- Your server. Codes must never go through a browser.

## What to know first

**Each code is shown once**, in the answer. SealTrust keeps only a keyed
digest, which cannot be read back. Print it or store it right away. A lost code
is re-issued, and the old one then stops working.

**Never write a code in a log**, or in a file others read. Send it only where
it will be printed, or to the buyer.

## The two endpoints

Every address also exists with the `/v1` prefix, which is the recommended form.

| Method and path | What it does | Permission |
| --- | --- | --- |
| `POST /partner/claim-codes/brands/{brand_code}/issue` | issues a code for each piece, and shows it once | `claim_codes:issue` |
| `POST /partner/claim-codes/brands/{brand_code}/status` | says whether each piece has a live code, a used one, or none | `claim_codes:issue` |

`{brand_code}` is the brand's public code, ten characters such as
`7K3QXW9M2A`. `GET /partner/catalog/brands` lists the ones your key may serve.

## Issue the codes

Name each piece by its serial (twelve characters, printed under its QR code)
or by its token number, never by any other number. 500 pieces at most per call.

```http
POST /v1/partner/claim-codes/brands/7K3QXW9M2A/issue
Authorization: Bearer st_live_...
Content-Type: application/json

{
  "pieces": [
    { "serial": "Y5T2VGGF2NP9" },
    { "token_id": "1000" }
  ]
}
```

```json
{
  "brand_code": "7K3QXW9M2A",
  "shown_once": true,
  "items": [
    { "serial": "Y5T2VGGF2NP9", "token_id": "1000", "code": "7KQ4MZ2P", "previous_status": "none" }
  ]
}
```

`previous_status` says what the piece had before:

- `none`: no code;
- `live`: a code in service, which no longer works. Paper already printed is
  to be thrown away;
- `used`: a code that was already used. See below.

It is all or nothing. If a single piece is a problem, no code is issued.

## Read the status, never the code

```http
POST /v1/partner/claim-codes/brands/7K3QXW9M2A/status
Content-Type: application/json

{ "pieces": [{ "serial": "Y5T2VGGF2NP9" }] }
```

```json
{
  "brand_code": "7K3QXW9M2A",
  "items": [
    { "serial": "Y5T2VGGF2NP9", "token_id": "1000", "status": "live", "issued_at": "2026-09-30T09:12:00+00:00", "used_at": null }
  ]
}
```

`status` is `none`, `live` or `used`. The code itself is never returned.

## A code already used

A used code is the record that a buyer claimed the piece. It is never replaced
unless you ask: the call answers `409` with the code
`PURCHASE_CODE_ALREADY_USED` and the list of pieces concerned, and nothing is
issued. To replace it anyway, after a return for instance, add
`"replace_used": true`. The answer then says `previous_status: "used"`, and the
brand's audit log records it.

## At mint time

You can ask for the codes while you mint a batch. The key must carry
`mint:batch` and `claim_codes:issue`.

```http
POST /v1/partner/mint/batch?issue_purchase_codes=true
```

A code cannot exist before its piece. The batch keeps your request, and the
codes come with the list of the batch's pieces:

```http
GET /v1/partner/mint/batch/{job_id}/items
```

Each row then carries `purchase_code` and `purchase_code_status`. The code
appears on the **first** read that finds the piece minted, then `null` on later
reads, with the status `live`. Print it next to the `print_url` link, which is
the piece's QR code. A second read never replaces a code already handed out.

A key without `claim_codes:issue` reading this batch sees the status, never the
code.

## From the console

Minting a batch in the console offers the same option: "Issue a purchase code
for each piece". The codes come in the QR code archive, in the
`purchase_codes.csv` file, next to each piece's QR file. The first download
contains them, later ones only give the status.

## From your ERP

Connectors never send a code to a third-party tool. To print codes from your
ERP or your labelling tool, have it call these endpoints with your key.

## What is refused

| Answer | Why |
| --- | --- |
| `403` | no `claim_codes:issue` permission, a brand the key does not serve, or the Compliance offer (`OFFER_EXCLUDES_IDENTITY`) |
| `404` `PIECE_NOT_FOUND` | an unknown piece, or one of another brand: the same answer in both cases |
| `409` `PURCHASE_CODE_ALREADY_USED` | a piece's code was already used, and `replace_used` is not `true` |
| `422` | a malformed reference, a piece named twice, more than 500 pieces, a malformed brand code |
| `429` | too many calls, or more than 20,000 codes issued for the brand in the hour (`PURCHASE_CODE_BUDGET_EXCEEDED`, with `Retry-After`) |

The brand's audit log records who issued codes, and for which serials. It
never contains the codes.
