# POST /partner-portal/interventions

Record an intervention on a product from a repairer or recycler partner account. Partner session, no API key.

Source: https://docs.sealtrust.io/en/reference/post-partner-portal-interventions/

---

You record an intervention that you have just carried out on a product: a
repair, a servicing, a reconditioning, a recycling. The intervention is added
to the history of the product, carries your name and states what you proved at
the moment of recording it.

The full address is
`https://api.sealtrust.io/v1/partner-portal/interventions`. The same route
exists without the `/v1` prefix, and the `/v1` form is the one recommended for
a new integration.

The partner portal is a different surface from the key-authenticated API. It
authenticates with the session of a partner account, and an API key gives no
access to it.

This call is not replayable. Two identical calls create two interventions. No
idempotency header is read by this endpoint.

## Authorization

Session of a partner account, of repairer or recycler type. The session token
is the one returned by `POST /v1/auth/login`, in the `access_token` field.

```http
Authorization: Bearer <votre jeton de session>
```

The `access_token` session cookie is accepted as well. It is the form that the
browser uses. It triggers the origin and anti-forgery token checks described
below.

Call this endpoint from your server, with the `Authorization` header. A call
issued by a browser page goes through additional origin and request forgery
checks, which return a 403 when they are not satisfied.

Four conditions must be met for an intervention to be recorded.

1. The account is of repairer or recycler type. Otherwise the response is a 403.
2. The account has at least one active accreditation. Otherwise the response is
   a 403 carrying the message `Aucune accréditation active`.
3. The product belongs to a brand that has accredited this account. Otherwise
   the response is a 403 or a 404.
4. The intervention type is covered by your accreditations on the brand of this
   product. Otherwise the response is a 422 that lists the allowed types.

Accreditations are granted by the brand, brand by brand. One same account can
hold both types on one same brand, and the allowed intervention types are then
the union of the two sets.

| Accreditation | Allowed intervention types |
| --- | --- |
| Repairer | `repair`, `maintenance`, `reconditioning`, `after_sale_service` |
| Recycler | `recycling`, `end_of_life`, `destruction`, `return` |

The `GET /v1/partner-portal/products/{identifier}` endpoint returns the
`allowed_event_types` list computed for the product concerned, which saves you
from guessing.

## Call limit

A call limit applies to this endpoint. It is set for the normal use of the
portal, where you look for a product then record an intervention.

Beyond it, the API replies 429. The refusal carries a `Retry-After` header that
gives the number of seconds to wait. Wait that delay, then call again.

The limit covers the whole partner portal. Alternating between the endpoints
therefore gives you no margin back. Space out your calls instead of sending
them in bursts.

The value of the limit is not a commitment and can change without notice. Do
not hard-code any threshold in your code, rely on `Retry-After`.

There is neither a daily quota nor a per brand counter here: these two
mechanisms are attached to API keys, and the partner portal uses none.

## Path and query parameters

This endpoint has no path parameter and no query parameter. Everything goes
through the request body.

## Request body

`application/json` format. Any field absent from this table gets the request
refused with a 422.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | yes | The product on which you intervened. Edge spaces are removed. An empty value is refused. Four forms are accepted, see below. |
| `event_type` | `string` | yes | The intervention type. The value is stripped of its edge spaces and lowercased before checking. It must appear among the types allowed by your accreditations on the brand of the product. |
| `title` | `string` | yes | Short title of the intervention. 255 characters at most. Edge spaces are removed. An empty value is refused. |
| `description` | `string` | no | Free text. No maximum length. Recorded as it is. |
| `metadata` | `object` | no | Your own information about the intervention. Recorded as it is. The `partner_type` key is added to it if you do not provide it. |
| `proof_code` | `string` | no | The handover code read by the customer, or the work order issued by the brand. Spaces and hyphens are removed, the value is uppercased. An empty string counts as no code. |

### The four accepted forms for `identifier`

They are tried in this order, and the search is bounded to the brands that have
accredited you.

| Form | Example | Recognized by |
| --- | --- | --- |
| Label fingerprint | `0x0000000000000000000000000000000000000000000000000000000000000000` | starts with `0x` |
| Token identifier | `10000000000000000000000000000000000000000000000000000000000000000000000000000` | contains only digits |
| Certificate number | `CERT-EXEMPLE-0001` | matches a certificate of the brand |
| Serial number printed on the product | `000000000000` | 12 characters of the Crockford Base32 alphabet |

The token identifier has 77 to 78 digits. It is stored and returned as a string
of characters. Declare it as a string in your integration, and size the field
accordingly.

The serial number tolerates the common reading confusions: the letters `I` and
`L` are read as the digit `1`, the letter `O` as the digit `0`, and case does
not matter. It is the form to favor when you have the object in hand, because
it is the only one printed on it. A destroyed or withdrawn product is never
resolved.

### The proof level, and how to raise it

An accreditation says that you have the right to work on the products of a
brand. It does not say that this precise product passed through your hands, and
the identifier is printed on the object. The `proof_level` field of the
response therefore records what you actually proved.

| `proof_level` | What you sent | What it is worth |
| --- | --- | --- |
| `declared` | no `proof_code` | you declared the intervention and you knew the identifier |
| `customer_code` | a handover code generated by the end customer | the holder of the product handed it to you |
| `work_order` | a work order issued by the brand | the brand entrusted this precise product to you |

The end customer generates their handover code from their account, through
`POST /v1/custody/repair-codes`. The code is 8 characters long and is displayed
only once. It is valid for a single product, is used only once, and expires
after 30 days by default, a duration that the issuer can set between 1 and 365
days. The work order is issued by the brand from its console, and it names both
the product and your account.

A refused code cancels the whole call, and nothing is recorded. If the code
does not go through, check it with the customer, or record the intervention
without `proof_code`, at the `declared` level.

> [!INFO] A proof level is never raised after the fact
> It is written at the creation of the intervention and is no longer
> modifiable. If the customer hands you their code, send it in that same call.

## Example request

:::onglets
```bash title="curl"
curl -i -X POST https://api.sealtrust.io/v1/partner-portal/interventions \
  -H "Authorization: Bearer VOTRE_JETON_DE_SESSION" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "000000000000",
    "event_type": "repair",
    "title": "Remplacement de la fermeture éclair",
    "description": "Ancienne fermeture remplacée par une pièce du fabricant.",
    "metadata": {
      "duree_minutes": 45,
      "pieces": ["fermeture éclair"]
    },
    "proof_code": "23456789"
  }'
```
```typescript
const response = await fetch(
  "https://api.sealtrust.io/v1/partner-portal/interventions",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer VOTRE_JETON_DE_SESSION",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      identifier: "000000000000",
      event_type: "repair",
      title: "Remplacement de la fermeture éclair",
      description: "Ancienne fermeture remplacée par une pièce du fabricant.",
      metadata: {
        duree_minutes: 45,
        pieces: ["fermeture éclair"],
      },
      proof_code: "23456789",
    }),
  },
);

console.log(response.status);
console.log(await response.json());
```
```python
import requests

response = requests.post(
    "https://api.sealtrust.io/v1/partner-portal/interventions",
    headers={
        "Authorization": "Bearer VOTRE_JETON_DE_SESSION",
        "Content-Type": "application/json",
    },
    json={
        "identifier": "000000000000",
        "event_type": "repair",
        "title": "Remplacement de la fermeture éclair",
        "description": "Ancienne fermeture remplacée par une pièce du fabricant.",
        "metadata": {
            "duree_minutes": 45,
            "pieces": ["fermeture éclair"],
        },
        "proof_code": "23456789",
    },
    timeout=30,
)

print(response.status_code)
print(response.json())
```
:::

> [!INFO] The TypeScript SDK does not cover the partner portal
> The `@sealtrust-io/sdk` package exposes batch minting, verification and
> notification subscriptions, all of them API key operations. The partner
> portal is therefore called over direct HTTP, as above.

## Example response

HTTP code 201.

```json
{
  "id": 8123,
  "product_id": 4096,
  "brand_id": 12,
  "event_type": "repair",
  "proof_level": "customer_code",
  "title": "Remplacement de la fermeture éclair",
  "description": "Ancienne fermeture remplacée par une pièce du fabricant.",
  "event_metadata": {
    "duree_minutes": 45,
    "pieces": ["fermeture éclair"],
    "partner_type": "repairer"
  },
  "performed_by": "Atelier Exemple (réparateur accrédité)",
  "product_name": "Sac de voyage Exemple SAS",
  "occurred_at": "2026-08-20T14:32:07.512430+00:00",
  "created_at": "2026-08-20T14:32:07.512430+00:00"
}
```

> [!ATTENTION] The field changes name between the request and the response
> You send `metadata`. The response returns it under the name
> `event_metadata`. The content is identical. The key changes name. An
> integration that reads `metadata` back in the response reads `undefined`.

The twelve fields of the response.

| Field | Type | Description |
| --- | --- | --- |
| `id` | `integer` | Identifier of the intervention, assigned in order of creation. Do not expose it publicly. |
| `product_id` | `integer` | Identifier of the product resolved from `identifier`. |
| `brand_id` | `integer` | Identifier of the brand of the product. |
| `event_type` | `string` | The intervention type recorded, in lowercase. |
| `proof_level` | `string` or `null` | `declared`, `customer_code` or `work_order`. See the table above. |
| `title` | `string` | The title, stripped of its edge spaces. |
| `description` | `string` or `null` | The description, as you sent it. |
| `event_metadata` | `object` or `null` | What you sent in `metadata`, augmented with the `partner_type` key. |
| `performed_by` | `string` or `null` | Your identity, as it will appear in the history of the product. |
| `product_name` | `string` or `null` | Name of the product as it is recorded. |
| `occurred_at` | `string` | Date and time of the intervention, in ISO 8601 format with time zone. |
| `created_at` | `string` | Date and time of the recording, in ISO 8601 format with time zone. |

### What `performed_by` contains

This field is built from your account, followed by the capacity in which you
intervened, for example `réparateur accrédité`. The value retained is your
first name and your last name, failing that the name of your company, failing
that the email address of the account. Fill in your name or your company in
your profile if you do not want your email address to appear in the history of
the products.

### What `partner_type` contains

The `partner_type` key is added to `metadata` when you do not provide it. It is
`repairer` for the four repair intervention types, and `recycler` for the four
end-of-life types. If you send this key yourself, your value is kept.

## Errors

| Code | Condition | What to do |
| --- | --- | --- |
| 401 | Neither an `Authorization` header nor an `access_token` session cookie. Message `Not authenticated`, with the `WWW-Authenticate: Bearer` header. | Add the `Authorization: Bearer <votre jeton de session>` header. |
| 401 | Token unreadable, expired or badly signed. Message `Invalid JWT token`. | Sign in again through `POST /v1/auth/login` and take the token it returns. |
| 401 | Token revoked by a sign-out or a password change. Message `Token has been revoked`. | Sign in again. |
| 401 | Token issued for something other than a session, for example an address confirmation. Message `Invalid token`. A token waiting for a second factor gives `MFA verification required`. | Finish the sign-in and use the session token it returns. |
| 401 | The token is signed but names no account. Message `Invalid token: missing email`. | Sign in again through `POST /v1/auth/login` and take the token it returns. |
| 401 | Account disabled. Message `Account disabled`. | Contact the brand that accredited you. Trying again will change nothing. |
| 403 | The account is neither a repairer nor a recycler. Message `Partner account required (repairer or recycler)`. | Use the partner account that the brand created for you. |
| 403 | The account has no active accreditation. Message `Aucune accréditation active`. | Ask the brand to accredit you, or to reactivate your accreditation. |
| 403 | The product is not within the scope of your accreditation. Message `Ce produit appartient à une marque qui ne vous a pas accrédité`. | This product is not within your scope. Ask this brand for an accreditation. |
| 403 | The call comes from a browser, from an origin that we do not allow, or without stating its origin. Messages `Forbidden origin` and `Origin or Referer header required`. | Call this endpoint from your server, with the `Authorization` header. |
| 403 | The call comes from a browser and the anti-forgery token is missing or does not match. Message `bad_csrf`. | Call this endpoint from your server, with the `Authorization` header. |
| 404 | No accessible product matches `identifier`. Message `Produit introuvable`. | Check the identifier and its form. A destroyed or withdrawn product replies the same thing. |
| 404 | The account named by the token no longer exists. Message `User not found`. | The account has been deleted. Contact the brand that accredited you. |
| 422 | Invalid body: required field absent, unknown field, `identifier`, `event_type` or `title` empty. | The body of the response lists the fields at fault and the reason for each refusal. Correct and call again. |
| 422 | The `title` exceeds 255 characters. The body of the response does not name the field at fault. | Shorten the title and put the detail in `description`, which has no maximum length. |
| 422 | The intervention type is covered by none of your accreditations on this brand. The message lists the allowed types. | Choose a type from the returned list. If none suits, ask the brand for the corresponding accreditation. |
| 422 | The `proof_code` matches no code issued for this product. Message `Code inconnu pour ce produit.` | Check that the code was indeed issued for this precise product. Call again without `proof_code` to record the intervention at the `declared` level. |
| 422 | The code has already been used. Message `Ce code a déjà servi.` | A code serves only once. Ask for a new one, or call again without `proof_code`. |
| 422 | The code was canceled by its issuer. Message `Ce code a été annulé par son émetteur.` | Ask the customer or the brand to issue a new one. |
| 422 | The code has passed its validity date. Message `Ce code a expiré.` | Ask for a new one. |
| 422 | The work order names a partner other than you. Message `Ce bon de travail a été émis pour un autre partenaire.` | This order is not intended for you. Ask the brand to issue one in your name. |
| 429 | You have exceeded the call limit. The response carries a `Retry-After` header. | Wait the number of seconds given by `Retry-After`, then call again. The limit covers the whole partner portal, so space out all of your calls. |

## See also

- [`GET /partner-portal/interventions`](/en/reference/get-partner-portal-interventions/),
  list the interventions that your account has recorded.
- [`GET /partner-portal/products/{identifier}`](/en/reference/get-partner-portal-products/),
  find a product of a brand that has accredited you.
- [`GET /partner-portal/me`](/en/reference/get-partner-portal-me/),
  read your partner profile and the brands that have accredited you.
