Method POST/partner/sellout

Declare that a product has been handed over to the end customer, from a distributor's system. Requires the sellout:write scope.

On this page

You declare that a product has left the shelf and has been handed over to the end customer, naming the point of sale that sold it.

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

This call can be replayed. A product that is already activated returns the existing activation with status set to already_activated, and fills in only the information that was missing: the attachment to the point of sale, the country, the city. Nothing is overwritten. The Idempotency-Key header is not read by this endpoint.

#Authorization

API key in the Authorization header, in Bearer format, with the sellout:write scope.

HTTP
Authorization: Bearer votre_clef

A key without that scope receives a 403 whose message names the missing scope.

Your brand's plan must also include API access. Otherwise you receive a 403 whose detail field carries the code FEATURE_NOT_AVAILABLE.

#Rate limit

Two distinct limits apply.

LimitValueWhat triggers it
Rate120 calls per 60 second window by default, the actual value depends on your planone counter per key and one counter for the sum of your brand's keys, with the same limit
Daily quotathe one attached to your key, empty means unlimited1 unit per call, deducted once the point of sale has been validated, before the product is resolved

The rate counter starts again from zero at each new 60 second window. The daily quota starts again from zero at midnight universal time.

Every response that passes the rate limit carries the headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and X-RateLimit-Scope. X-RateLimit-Reset gives the instant of the reset, in seconds since January 1, 1970 in universal time. X-RateLimit-Scope equals key or brand, and designates the more constraining of the two counters. Creating extra keys does not increase the total rate allowed to your brand.

#Path and query parameters

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

#Request body

Format application/json. Any field absent from this table causes the request to be refused with 422.

NameTypeRequiredDescription
identifierstringyesThe product sold. From 1 to 200 characters. We accept four forms, see below.
retailer_codestringyesThe code of the point of sale, from 1 to 64 characters. It is the one you defined in your console, Distribution section.
countrystringnoCountry of the sale, two-letter ISO code. We uppercase the value. An empty string counts as absent.
citystringnoCity of the sale, 100 characters maximum.

#The four accepted forms for identifier

We try them in this order, and we always restrict the lookup to your brand.

FormExampleRecognized by
Label hash0x00000000000000000000000000000000000000000000000000000000000000000x followed by 64 hexadecimal characters, that is 66 characters in all. It is the value our responses return in the uid_hash field
Token identifier10000000000000000000000000000000000000000000000000000000000000000000000000000a string of digits, often very long. It is the value our responses return in the token_id field
Certificate numberST-CERT-000000000000starts with ST-CERT-, followed by 12 characters. It is the number displayed on the certificate
Serial number printed on the product00000000000012 characters of the Crockford Base32 alphabet

The serial number tolerates the common reading confusions. We read the letters I and L as the digit 1, the letter O as the digit 0, and we ignore case. We never resolve a product that has been destroyed or withdrawn.

#What country and city are worth when you omit them

If you do not send country, the country recorded is the one of the point of sale. Same rule for city. Fill in these two fields when the sale did not take place at the usual address of the point of sale.

#Example request

curl -i -X POST https://api.sealtrust.io/v1/partner/sellout \
  -H "Authorization: Bearer st_test_0000000000000000000000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "000000000000",
    "retailer_code": "BTQ-EXEMPLE-01",
    "country": "FR",
    "city": "Lyon"
  }'

#Example response

HTTP code 200, first declaration for this product.

JSON
{
  "status": "activated",
  "activation_id": 1024,
  "product_id": 4096,
  "product_name": "Sac de voyage Exemple SAS",
  "retailer_code": "BTQ-EXEMPLE-01",
  "source": "sellout_declared",
  "activated_at": "2026-08-20T14:32:07.512430+00:00"
}

HTTP code 200 as well if the product had already been activated, for example by a scan from the end customer before your declaration.

JSON
{
  "status": "already_activated",
  "activation_id": 1024,
  "product_id": 4096,
  "product_name": "Sac de voyage Exemple SAS",
  "retailer_code": "BTQ-EXEMPLE-01",
  "source": "qr",
  "activated_at": "2026-08-19T09:14:55.201884+00:00"
}

The seven fields of the response.

FieldTypeDescription
statusstringactivated if this call created the activation, already_activated if it already existed.
activation_idintegerInternal identifier of the activation. Use it to reconcile your records with ours. Do not display it to a third party.
product_idintegerIdentifier of the product resolved from identifier.
product_namestring or nullName of the product as it is recorded.
retailer_codestringThe point of sale code you sent, after resolution. On an already_activated response, if the activation already carried another point of sale, we keep that original attachment and this field does not reflect it.
sourcestringWhat created the activation: sellout_declared for a declaration through this route, nfc or qr for a first scan.
activated_atstring or nullDate and time of the activation, in ISO 8601 format with time zone. For an activation that already existed, it is the original date, never that of your call.

#Errors

CodeConditionWhat to do
401Authorization header absent. The response carries WWW-Authenticate: Bearer.Add the header Authorization: Bearer <your key>.
401Header present but not starting with Bearer followed by a space.Correct the format of the header.
401Unknown key, or key shorter than 40 characters.Check that you are sending the whole key. It is readable only once, when it is created; if it is lost, create a new one from the console.
403Key revoked, or already marked as expired. The message names the state.Create a new key. A revoked key is refused on every subsequent call, and the console offers no way to put it back in service.
403Key that has reached its expiry date. Message API key has expired.Create a new key. Expiry is observed on the first call after the deadline, and it is final.
403The key does not have the sellout:write scope. Message Missing required scopes: sellout:write.Create a key that carries that scope. The scopes of an existing key cannot be changed.
403Your brand's plan does not include API access. detail equals {"code": "FEATURE_NOT_AVAILABLE", "feature": "api_access"}.Contact us to change plan. Retrying will change nothing.
404No active point of sale carries this retailer_code in your brand. Message Retailer '<code>' not found for this brand.Check the code in your console, Distribution section, and check that the point of sale is active.
404No product of your brand matches identifier. Message Product not found for this identifier.Check the identifier and its form. A product of another brand, destroyed or withdrawn, answers the same thing.
422Invalid body: required field absent, unknown field, country that is not two letters, or length exceeded.The body of the response lists the offending fields and the reason for each refusal. Correct and call again.
429Rate limit reached, by your key or by the sum of your brand's keys.Wait the number of seconds given by the Retry-After header. X-RateLimit-Scope tells you which of the two counters refused.
429Daily quota of the key exhausted. Message Quota exceeded. Remaining today: <restant>/<limite>.The headers X-Quota-Limit and X-Quota-Remaining give the state of the counter. X-Quota-Reset gives the date of the last reset, a date in the past. The next one happens at midnight universal time. Wait for midnight universal time, or use a key with a wider quota.
500Internal error while processing the call.Retry. If the error persists, contact us, giving the time of the call and the retailer_code used.
503The service that counts the calls is momentarily unavailable. Message Rate limiting temporarily unavailable, please retry shortly.Retry in a few moments. No declaration has been recorded.

#The order of the checks, and what it changes for your quota

The checks run in this order: rate limit, brand's plan, existence of the point of sale, daily quota of the key, then resolution of the product.

One useful consequence: an unknown retailer_code does not consume your daily quota, whereas an identifier that cannot be found does consume it, because we resolve the product after the deduction. If you reconcile sales in batches, validate your point of sale codes once and for all, and expect unknown identifiers to cost one unit each.

#See also

Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.

Suggest a correctionReport a problem