Changelog
What changed in the partner API and in notifications, by date, with the exact action to take when an entry breaks a call that used to work.
By the end of this page, you will know what changed in the partner API and in notifications, on what date, and what you have to do on your side when a change rejects a call that used to work. You will also know why the version number you call does not move while these entries pile up.
Read this page in full the first time. After that, come back to it before every change to your integration and stop at the first date you already know.
#How this page is built
Entries run from the most recent to the oldest. The date on an entry is the date the change went live.
An entry starts with Breaking change when a call that used to work is rejected afterward. That is the only case where you have work to do. The other entries describe behavior that is added or a response that becomes more precise, and your integration keeps working untouched.
Each entry says three things: what changes, the exact response you now receive, and the action to take. No entry sends you elsewhere for the action to take.
This page covers every endpoint this site documents: the key-authenticated partner API, the public endpoints that need no key, authenticated passport reads, the notifications sent to your addresses, and the partner portal. It does not cover the console, whose screens change without any contract to keep toward your code.
#Why the version number does not change
The GET /v1/version endpoint always answers the same thing.
{
"version": "1.0.0",
"api_version": "v1"
}Every entry below fits inside version 1. The day a change cannot fit there, it
will be added under /v2, and version 1 will keep its behavior.
The business endpoints exist at two addresses that call the same code: with
the /v1 prefix, and with no prefix at all. Call GET /v1/version when you
want the contract version. It is the only version endpoint this site
documents.
Both addresses call the same code, so they receive every behavior change listed here at the same time. One entry is the exception, the one dated August 16, 2026, which concerned only the addresses without a prefix.
On this page
- How this page is built
- Why the version number does not change
- August 26, 2026
- Breaking change. The value_chain access tier no longer exists
- The partner portal announces the trade it served
- A unit's public image falls back to its model
- August 22, 2026
- The public certificate names the issuing brand
- Authentication refusals answer in English
- The partner portal has a rate limit
- August 21, 2026
- Sending a batch now creates products
- August 20, 2026
- Breaking change. The API rejects the owner_email and contract_address fields
- Breaking change. Replaying an idempotency key with a different batch answers 409
- Breaking change. Batch tracking requires the mint:batch scope
- Breaking change. Changing a notification subscription requires your plan to include them
- An unreadable JSON body answers 400
- The eight key-authenticated endpoints are rate limited
- Your notification subscriptions stay readable and deletable whatever your plan
- August 16, 2026
- Prefix-free addresses become permanent aliases
- August 14, 2026
- A damaged gzip file answers 400
- Batch tracking returns four more fields when the queue has forgotten your batch
- August 13, 2026
- A partner portal intervention carries a proof level
- August 11, 2026
- Breaking change. Deleting a subscription requires retyping its address
- Every delivery carries the event type
- July 14, 2026
- Two headers are added to every delivery
- The signature covers the exact bytes sent to you
- July 12, 2026
- Subscription endpoints require scopes
- March 15, 2026
- The /v1 prefix appears
- What this page does not do
Three changes. The first is a breaking change, and it concerns every accredited partner.
- Breaking change
#The
value_chainaccess tier no longer existsThe
value_chaintier was granted to any partner holding any accreditation on the brand, and it returned the union of what the repairer, the recycler and the upstream supplier see. An accredited recycler could therefore read manufacturing data and the supply chain, which therecyclertier denies. That door is closed.Details
Six tiers remain:
public,end_user,repairer,recycler,upstreamandauthority. The three trade tiers are distinct audiences: holding one accreditation opens none of the others.The exact response.
?access_tier=value_chainnow answers 422, onGET /v1/passport/{identifier}, onGET /v1/passport/01/{gtin}and on the three verifiable credential endpoints.detailis the usual validation list, each entry carryingloc,typeandmsg, andlocnamesaccess_tier.What you do. Ask for your own trade's tier,
repairerorrecycler. You will receive fewer fields than before, and they are precisely the fields that are not part of your trade. - Change
#The partner portal announces the trade it served
In
GET /v1/partner-portal/products/{identifier}, thepassport.access_tierfield always readvalue_chain. It now carries the trades actually served, joined by a+and sorted alphabetically:recycler, orrepairer+recyclerfor an account that holds both accreditations on that brand. It readspublicwhen the brand has recognized none of them, and the passport is then filtered at the public tier.Details
What you do. If your code compares this value to
value_chain, revisit the comparison. And expect narrower content than before: it is now filtered on your accreditations alone. - Change
#A unit's public image falls back to its model
In
GET /v1/products/{uid_hash}/public, theimage_urlfield only looked at media attached directly to the unit, and readnullas soon as it had none. It now follows the same fallback chain as the rest of the site: an image of the unit, then its model's main image, then the model's media.Details
What you do. Nothing. A field that used to read
nullmay now carry an address.
Three changes shipped together. None is a breaking change.
- Change
#The public certificate names the issuing brand
The
issuer_namefield carries the name of the brand that issued the certificate. The server computes it on read, and it readsnullwhen the certificate is attached to no brand.Details
Three surfaces are affected:
GET /v1/certificate/{identifier}, the PDF returned byGET /v1/certificate/{identifier}/download, and thecertificateblock ofGET /v1/resolve/{identifier}.What you do. Nothing in your calls. If you copied the value of
issuer_nameinto your own database, replace it with what the API returns today. - Change
#Authentication refusals answer in English
The API's front door returned its messages in two languages depending on the path called. The messages it puts in
detailare now in English:Not authenticated,Invalid token,Invalid token: missing email,Token has been revoked,Invalid JWT token,Account disabled,User not foundandPartner account required (repairer or recycler).Details
Two refusals from
POST /v1/originality/read-sig/verifyfollow the same rule and now readInvalid uid_hexandInvalid signature_hex.What you do. Nothing, if you key your code on the HTTP status code, as the errors page recommends. A client that compares these sentences character by character must revisit those comparisons, or better, remove them.
- Change
#The partner portal has a rate limit
The four
/partner-portalendpoints accept 30 calls per 60 second window, counted per calling address. All four share that counter, and the/v1/partner-portal/...form shares it with the prefix-free form.Details
Every accepted response carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset. Going over returns 429, withdetailreadingRate limit exceeded: 30 requests per 60sand aRetry-Afterheader reading60.What you do. Space out your calls to the portal and drive your pace from
X-RateLimit-Remaining. A human journey, searching for a product then reporting an intervention, stays well below this limit.
- Change
#Sending a batch now creates products
POST /v1/partner/mint/batchanswered200, its processing ran to completion, and no product was created. Every row was missing two values that the submission contract does not let you provide, and every row was rejected for that reason.Details
The server now sets those two values itself, on every row: the identification method
qrand the product's technical identifier.What you do. Nothing in your submissions. A row's contract does not change, and a row that carried the identification method or the technical identifier is rejected with
400, like any field the contract does not name.Products created this way are identified by QR. For NFC items, go through the console.
Seven changes shipped together. The first four are breaking changes.
- Breaking change
#The API rejects the
owner_emailandcontract_addressfieldsA batch row that carries either of these two fields fails the whole request with
400. The response names the rejected field.Details
The API used to accept these two fields and never read them. The value you put in them had no effect.
A batch row now accepts five fields and nothing else:
product_name,brand_id,category_idandmetadata_uri, which are required, andexternal_ref, which is optional.What you do. Remove these two columns from your CSV exports and these two keys from your JSON objects. If you need to tie a row to a reference on your side, use
external_ref: it is the contract's only free field. We keep it on the batch row and we return it in the result read byGET /v1/partner/mint/batch/status/{job_id}. No product field picks it up.Removing them makes validation pass. Since August 21, 2026, it also makes the products get created, see the entry for that date.
- Breaking change
#Replaying an idempotency key with a different batch answers 409
The
Idempotency-Keyheader remains optional and is still remembered for 24 hours.Details
Replaying the same value with the same batch still returns the first call's response, without minting anything again. The order of the checks changed the same day. A replay now consumes a token from your rate limit, and the API re-reads your request body before returning the cached response. A replay beyond the limit therefore receives
429. A replay whose body has become unreadable receives400.Replaying the same value with a different batch now answers
409. The API does not queue your second batch, and the response tells you so.JSON { "detail": "Idempotency-Key 'lot-2026-08-20-a' was already used with a different request body" }The same
409code also signals that a call carrying this key is still being processed. The message is thenA request with this Idempotency-Key is already being processed. Read the message before deciding what to do: the first case asks you to change your key, the second asks you to wait for the processing under way to finish.Two batches are compared on their content once read. Row order, column order, the choice between CSV and JSON, and empty cells make no difference.
What you do. Build one
Idempotency-Keyvalue per distinct batch. If your code reuses a fixed value, or a value derived from the time to the minute, change it now. - Breaking change
#Batch tracking requires the
mint:batchscopeGET /v1/partner/mint/batch/status/{job_id}requires themint:batchscope, the same one as sending the batch. A key that does not carry it receives403, and the message names the missing scope.Details
What you do. Check that the key querying the tracking endpoint carries
mint:batch. A key's scopes are set when it is created and no screen changes them afterward, so you need a new key.First check your key limit, which counts only active keys. The Essentiel plan allows only one: revoke the old key, then create the new one, accepting the outage between the two. The Prestige plan allows five: create the new key, deploy it, then revoke the old one. Revoking a key frees a slot immediately. A creation beyond the limit answers
403with theQUOTA_EXCEEDEDcode. - Breaking change
#Changing a notification subscription requires your plan to include them
PUT /v1/partner/webhooks/{webhook_id}now looks at your brand's plan. A call that worked the day before for a brand whose plan does not include notifications is rejected with403.Details
JSON { "detail": { "code": "FEATURE_NOT_AVAILABLE", "feature": "webhooks" } }Turning one off is the one exception: sending
is_activeatfalseand nothing else remains possible without the plan. As soon as another field accompanies that switch off, the API requires the plan.Creating a subscription,
POST /v1/partner/webhooks, answers the same thing and already required it before this change.What you do. Nothing if your plan includes notifications. If it no longer includes them, stop changing your subscriptions: turn them off with
is_activeatfalsealone, or delete them. - Change
#An unreadable JSON body answers 400
A malformed body sent to
POST /v1/partner/mint/batchnow answers400, with a message that recalls the two accepted formats.Details
JSON { "detail": "Corps de requete illisible : le JSON envoye est mal forme. Envoyez une liste d'objets produit en application/json, ou un fichier CSV en multipart/form-data." }What you do. If your code automatically replays responses in the
5xxfamily, this case no longer triggers it. A request rejected with400must be fixed, and replaying it as is will give the same refusal. - Change
#The eight key-authenticated endpoints are rate limited
The rate limit now applies to all eight endpoints of the partner API, including batch tracking and the five notification subscription endpoints.
Details
Two visible consequences. Beyond the limit, you receive
429with theRetry-Afterheader, expressed in seconds. When the rate limiting service is unavailable, you receive503, and the call has no effect: a deletion refused this way deletes nothing.Responses that go through carry
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetandX-RateLimit-Scope.What you do. Read
X-RateLimit-Remainingand slow down before you reach zero. If you poll a batch's tracking endpoint in a loop, space out your calls: it is the endpoint most exposed to this limit. Treat the503as a temporary outage and retry after a delay. - Change
#Your notification subscriptions stay readable and deletable whatever your plan
Reading the list of your subscriptions, reading one subscription and deleting a subscription work whatever your brand's plan. These three calls do not look at your plan.
Details
Sending notifications follows another rule: a brand whose plan no longer includes notifications receives no more deliveries, whatever subscriptions are still recorded.
What you do. Nothing. If your plan no longer includes notifications, you keep control to inventory your subscriptions and to delete them.
- Change
#Prefix-free addresses become permanent aliases
The copies without the
/v1prefix used to answer with two headers announcing an end of service on September 1, 2026,DeprecationandSunset. The API no longer sends these two headers. The/v1addresses never carried them.Details
No end-of-service date is attached to these addresses. If your integration already calls them, it keeps working.
What you do. Nothing right now. Use the
/v1form for any new integration: it is the form documented everywhere on this site.
- Change
#A damaged gzip file answers 400
POST /v1/partner/mint/batchaccepts a CSV compressed with gzip or zlib.Details
A gzip file the server cannot decompress now answers
400, with the reason in the message. It is a request error, and your code can treat it as such.A file whose decompressed content exceeds the limit answers
413, and the message states that limit in MiB.What you do. Nothing if your submissions are valid. If you compress on the fly, check that the stream is complete before sending it, and split your batches to stay under the limit. A batch carries at most 500 items.
- Change
#Batch tracking returns four more fields when the queue has forgotten your batch
GET /v1/partner/mint/batch/status/{job_id}used to answerunknownto the owner of an old batch, once the queue's retention period had elapsed. It now returns the real status, rebuilt from the reservation recorded when the batch was sent, and it adds four fields:batch_status,items_count,success_countanderror_count.Details
An identifier that designates no batch of your brand still answers
unknown, revealing nothing more.What you do. Nothing, these fields are additions.
- Change
#A partner portal intervention carries a proof level
POST /v1/partner-portal/interventionsnow records a proof level with every intervention, in theproof_levelfield.Details
The request body accepts an optional
proof_codefield. Without it, the level readsdeclared. With a valid code, it readscustomer_codewhen the code was read by the customer, orwork_orderwhen it comes from the brand's work order. A rejected code answers422and records nothing at all: neither the intervention, nor the consumption of the code.Interventions recorded before this date return
proof_levelempty. An empty value does not meandeclared: the question had not been asked.What you do. If you display the intervention history, treat the empty value as a separate case and do not display "declared" in its place.
- Breaking change
#Deleting a subscription requires retyping its address
DELETE /v1/partner/webhooks/{webhook_id}requires aconfirmquery parameter containing the subscription's exact address.Details
Without that parameter, a subscription that belongs to you answers
400with theCONFIRMATION_REQUIREDcode. With a value that does not match, the response is400with theCONFIRMATION_MISMATCHcode, and the API changes nothing. A successful deletion answers204.An identifier that designates no subscription of your brand answers
404, whether you sentconfirmor not: the API looks for your subscription before looking at your confirmation.A call that sent only the identifier is therefore rejected.
What you do. Read the address with
GET /v1/partner/webhooks/{webhook_id}, then send it back inconfirm.curl curl -X DELETE \ "https://api.sealtrust.io/v1/partner/webhooks/1?confirm=https%3A%2F%2Fexemple-sas.example%2Fwebhooks%2Fsealtrust" \ -H "Authorization: Bearer st_test_0000000000000000000000000000000000000000000000" - Change
#Every delivery carries the event type
Every delivery now carries
X-Webhook-Event, which names the event type. The signature does not cover this header.Details
What you do. Nothing, this header is an addition.
- Change
#Two headers are added to every delivery
Every delivery now carries
X-Webhook-IdandX-Webhook-Timestamp, in addition toX-Webhook-Signature.Details
X-Webhook-Idis the hash of the delivery body. Retries of the same delivery carry the same value, which lets you discard a retry. Two distinct events whose body is identical also carry the same value.What you do. Deduplicate on the pair formed by the event name, carried by
X-Webhook-Eventsince August 11, 2026, andX-Webhook-Id. Never discard a delivery on the value ofX-Webhook-Timestamp: that timestamp changes with every retry, so it would make you process the same delivery twice. - Change
#The signature covers the exact bytes sent to you
X-Webhook-Signaturehas the formt=<timestamp>,v1=<hash>. The hash is an HMAC-SHA256 computed with your subscription's secret, over the string formed by the timestamp, a period, and the request body.Details
The signed body is the byte sequence received on the wire. Decoding the JSON then re-encoding it before verifying breaks the signature, because re-encoding does not produce the same bytes.
What you do. Verify the signature on the raw request body, before any decoding.
- Change
#Subscription endpoints require scopes
Listing your subscriptions and reading one require the
webhooks:readscope. Creating, changing and deleting a subscription require thewebhooks:writescope.Details
A key that does not carry the scope receives
403, and the message names the missing scope.What you do. Check the scopes of the key that manages your subscriptions. If it was created with the
mint:batchscope alone, create a new key that also carries the notification scopes.
- Change
#The
/v1prefix appearsEvery business endpoint becomes reachable at two addresses, with the
/v1prefix and with no prefix at all.Details
The prefix-free addresses additionally answer two headers,
Deprecation: trueandSunset: 2026-09-01. The/v1addresses do not carry them. The August 16, 2026 entry removes these two headers and no end-of-service date was ever applied.What you do. Nothing. Both addresses call the same code.
#What this page does not do
It does not announce upcoming changes. An entry lands here when the behavior described is live.
It does not replace a test. We open no test environment to partners, and no sandbox address exists. After reading an entry marked as a breaking change, replay your real call on a product or a subscription of your brand that you can sacrifice, and compare the response code to the one you expected.
Was this page helpful?
Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.