Partner API, overview

Base address, key authentication, permissions, call limits, replaying a call without doubling its effect, pagination and versioning. The page to read before opening the reference.

On this page

By the time you leave this page, you will know how to authenticate a call to the partner API, choose the right address, read the headers that tell you how many calls you have left, replay a request without doubling its effect, and recognize the three limits that can refuse you. This is the page to read once, before opening the reference endpoint by endpoint.

#What this API does

The key-authenticated partner API is there to drive SealTrust from your own system, without going through the console. It has twenty-two endpoints, split across five uses.

UseEndpoints
Create products in batches, track the progress of the processing, and read the pieces created3
Report a sale to the end customer1
Manage your notification subscriptions5
Pre-fill your catalog: models, batches, draft passports, completeness score7
Show the passport in your own page: passport, proof and signature in one call, your visitors' scans and NFC reads6

The catalog has its own page: Fill the catalog through the API. So does the display: Show the passport in your page.

Submitting a batch only creates products identified by QR. The server itself sets, on each line of the batch, the identification method and the technical identifier of the item. Your lines carry neither of them, and you cannot ask for any others.

The pieces a batch created are read row by row with GET /partner/mint/batch/{job_id}/items: for each row, your external_ref, its state, the serial, the token number and the link to print in the QR code. A row that failed says so, without the technical detail: give us the job_id and the row_index. The token number, token_id, arrives after the other fields: it stays null until the chain confirms the mint, even on a batch already finished, and is usually filled a few seconds later. Read the list again to get it. The serial and the link to print are there as soon as the row is created: you can print without waiting for the token. The list is paginated by limit (100 by default, 500 at most) and offset, and needs the mint:batch scope. A batch of another brand and an unknown id get the same 404. On a batch minted with issue_purchase_codes=true, each row also carries the piece's purchase code, once (issue purchase codes).

Your models are read through the catalog. The published passport of a product, with its proof and its signature, is read in one call with GET /partner/passports/{identifier}. The public verification endpoints, which require no key, stay open to everyone.

The partner portal, at the address /partner-portal, is a different surface. It authenticates with the session of a repairer or recycler partner account, and an API key gives no access to it.

#What only the console does

An API key does not set up your account. The actions below have no endpoint in this API: do them once in the console, with an administrator account, then drive the rest through the API.

ActionWhere, in the console
Create a client brandYour client brands
Invite someone to a brand's teamThe team and roles
Create or revoke an API key, including the key of a client brandAPI keys and webhooks
Declare your page's addresses, the page that shows a passport and sending scans to itAPI keys and webhooks
Set the logo and the colours, hide our mentionThe brand and its look
Add a custom domain and have it verifiedThe custom domain
Read the monthly usage statement of your client brandsYour client brands' usage statement

A key that calls the address of one of these screens is refused: those addresses need a console session, not a key.

#The OpenAPI contract

The contract of the partner endpoints can be downloaded in OpenAPI format: openapi-partner.json. It is produced from the API code with every version of this site, declares key authentication (Authorization: Bearer) on every endpoint, and describes both forms of the body of POST /v1/partner/mint/batch: the JSON list and the CSV file. Use it to generate a client. The guides of this site remain the reference for the rules and the refusals. The API serves no Swagger page in production.

#The base address and the /v1 prefix

You call the API at https://api.sealtrust.io.

Every endpoint described on this site exists at two addresses that call exactly the same code: with the /v1 prefix, and with no prefix at all.

HTTP
POST https://api.sealtrust.io/v1/partner/mint/batch
POST https://api.sealtrust.io/partner/mint/batch

Use the /v1 form for any new integration.

The reference pages on this site title each endpoint with its path without a prefix, for example POST /partner/mint/batch. Add /v1 in front of that path when you write your call.

The addresses without a prefix are permanent aliases. We attach no end-of-service date to them, and we send no removal announcement header on those responses. If your integration already calls them, it will keep working.

#Authenticating a call

You send your key in the Authorization header, in Bearer format.

HTTP
Authorization: Bearer votre_clef

That is the only authentication mode of this API. There is no URL parameter and no cookie. Two endpoints, which record a scan made by one of your visitors, also ask for four signed headers that name that visitor: they are described in Show the passport in your page.

#Getting a key

You create your keys from your brand console, under Settings then the Developers tab. The tab stays visible whatever your plan. If your plan does not include API access, the screen is shown locked and you cannot create any key there.

Three points matter at creation time.

  • The full secret is displayed only once, in the window that follows creation. Copy it at that moment. No screen and no endpoint lets you read it again afterwards. We keep only a fingerprint of it.
  • A key belongs to a single brand. We attach every operation made with it to that brand, and to no other.
  • Your key starts with st_live_. There is no test key and no sandbox: every call acts for real, a mint is anchored and counts against your quota. The examples in this documentation show st_live_ followed by zeros, which is nobody's key.
  • A key always expires. You choose a lifetime at creation. If you do not choose one, it is 365 days. Note the date in your calendar: on the day, your calls stop.
  • Your plan caps the number of usable keys. A revoked key, or one past its expiry date, no longer counts. To change keys without a break, create the new one before revoking the old one: meanwhile, both take a seat. If your plan allows only one, the rotation goes through a break, between revoking the old key and putting the new one into service.

The console then displays the first characters of each key, so that you can recognize it in the list without ever showing its secret again. It also displays the date of last use. We refresh that date at most once a minute, so it can lag one minute behind your last call.

#Revoking a key

You revoke a key from the same page, and the revocation takes effect immediately. The first call that follows is refused. Revoking a key frees a slot if your plan limits the number of active keys.

#Authentication refusals

CodeWhat happenedWhat to do
401the Authorization header is missingadd it, the response also carries WWW-Authenticate: Bearer
401the header does not start with Bearer followed by a spacecorrect the form of the header
401the key is malformed or unknownsend the full secret back exactly as the console displayed it, with no space and no line break
403the key has been revokedcreate a new key in the console
403the key has reached its expiry datecreate a new key, the old one will never become valid again
403the key does not have the permission required by this endpointthe message names the missing permission, see the permissions attached to a key

#Your first call

Check that a key works with the list of your notification subscriptions. This endpoint changes nothing and consumes no quota. It requires the webhooks:read permission.

curl -i https://api.sealtrust.io/v1/partner/webhooks \
  -H "Authorization: Bearer st_live_0000000000000000000000000000000000000000000000"

Response, HTTP code 200, for a brand that has no subscription yet:

JSON
{
  "items": [],
  "total": 0
}

The TypeScript SDK gives you the body of the response, never its headers. To read the limit headers described in The rate limit, in calls per minute, call the API over direct HTTP from your server, or go through curl -i. A browser cannot read them: the only header we expose to the browser is X-Total-Count.

#The permissions attached to a key

Every key carries a list of permissions. A missing permission gets the call refused with a 403, and the refusal message names the permission that is missing.

Nine permissions govern access to the endpoints of this API. Each one opens at least one endpoint, and the creation form offers no other.

PermissionWhat it opens
mint:batchsubmit a batch of products, and read the progress of a batch
sellout:writereport a sale to the end customer
dispatch:writetie each unit your logistics provider picks to its B2B order (tie each unit to its order)
catalog:readread your models, their completeness score and their draft passport
catalog:writecreate or complete models, batches and draft passports
passport:readread the published passport of a product, a model or a batch, with its proof and signature, in one call
scans:writerecord a scan made by one of your visitors
nfc:verifyverify a chip read for one of your visitors, or read a verification receipt
webhooks:readlist your subscriptions, and read a single one
webhooks:writecreate, modify and delete a subscription
claim_codes:issueissue the purchase codes of your pieces, each shown once, and read their status (issue purchase codes)

Tick only those your integration actually uses.

#One key for several brands: the reseller key

A key belongs to one brand, and it acts for that brand only.

A reseller under contract can ask for a reseller key. It acts for its own brand and for each of the client brands attached to its contract, on every endpoint: catalog, offer, codes to print, passport display, minting, sale declarations and webhooks. You do not need one key per client brand. A brand outside your contract, or a code that names no brand, gets the same 403. The details are in Fill the catalog through the API and in Show the passport in your page.

With your reseller key, you mint for a client brand by putting its public code in the brand_code of every row of POST /v1/partner/mint/batch, one batch per brand. The sale and the webhooks take a brand_code too. Without it, the key acts for your own brand. The offer and the allowance applied are the client brand's; API access comes from your reseller offer.

#The three limits

Three different counters can refuse a call. They are not alike and they are not read in the same place. Treat them separately.

#The rate limit, in calls per minute

We count your calls over a fixed 60-second window. This limit applies to all twenty-two endpoints, including reading the status of a batch and deleting a subscription.

Two counters run at the same time: one for your key, one for the sum of all the keys of your brand. The limit is the same on both sides.

Your limit comes from a value set on your account, or failing that from your plan. When neither of the two is defined, you get 120 calls per window. Do not guess this value: you read it on every response.

Every response that passes the limit carries the following four headers.

HeaderWhat it contains
X-RateLimit-Limitthe applicable limit, in calls per window
X-RateLimit-Remainingwhat is left to you in the current window
X-RateLimit-Resetthe timestamp, in seconds since 1970, at which the window restarts
X-RateLimit-Scopekey or brand, depending on which of the two counters is the more constraining

These headers always describe the tighter of the two counters. Drive your pace from them, and slow down before reaching zero.

Going over returns a 429 with the same headers, plus Retry-After. Retry-After counts the seconds left in the current window, and is at least 1. Respect it. X-RateLimit-Scope tells you which of the two counters refused, which saves you from looking on the key side when it is the brand total that is full.

One last case, a rare one: if the service that keeps these counters is momentarily unavailable, all twenty-two endpoints reply 503. A 503 means that the call did nothing at all. Try again later.

#The daily quota of the key

Every key can carry a daily quota. A key without a quota is unlimited on that side. The counter restarts from zero when midnight passes in universal time. Your time zone does not come into it.

This quota is counted per line submitted.

  • A batch of 100 lines consumes 100 units.
  • A sale report consumes 1 unit.
  • A catalog write that creates or changes something consumes 1 unit.
  • The other endpoints consume nothing: the five notification subscription endpoints, reading the progress of a batch, the catalog reads and the six passport display endpoints.

Going over returns a 429 with three headers of its own.

HeaderWhat it contains
X-Quota-Limitthe daily quota of the key
X-Quota-Remainingwhat is left for today
X-Quota-Resetthe date of the last reset of the counter

The response also carries the four X-RateLimit-* headers of the rate check, which the call had just passed before being stopped by the quota.

#The pieces of your plan

Every plan sold counts your pieces per calendar year, in universal time. A piece is a product unit created. Your plan includes 6,000 pieces per year. Each piece beyond is priced per piece. Beyond 1,000,000 pieces in the year, the price is set on quote. No piece is refused for a volume, even beyond 1,000,000. This holds for a brand on the Compliance + Identity offer: on the Compliance offer, a brand creates no piece, see Compliance, or Compliance + Identity. Only the trial account keeps a monthly cap: 10 products per billing period. Your plan also decides whether API access is open to you, and whether notification subscriptions are open to you.

Three 403 refusals follow from it, all carrying a code readable by your program.

Your plan does not include API access:

JSON
{
  "detail": {
    "code": "FEATURE_NOT_AVAILABLE",
    "feature": "api_access"
  }
}

Your plan does not include notification subscriptions:

JSON
{
  "detail": {
    "code": "FEATURE_NOT_AVAILABLE",
    "feature": "webhooks"
  }
}

That refusal only touches the creation and the modification of a subscription. You keep reading and deletion whatever your plan. You also keep switching a subscription off, on one condition: send is_active to false and nothing else. As soon as a second field accompanies that value, the call counts as a modification and the refusal applies.

On a trial account, you have reached the products of your billing period:

JSON
{
  "detail": {
    "code": "QUOTA_EXCEEDED",
    "resource": "products",
    "current": 8,
    "additional": 5,
    "max": 10,
    "period": "monthly"
  }
}

This one reads as follows: you have already created 8 products in the period, you are asking for 5 more, the maximum is 10. Split your batch, wait for the next period or subscribe to a plan. A plan sold never returns this refusal for pieces.

#Replaying a call without doubling its effect

Submitting a batch accepts an Idempotency-Key header. This header carries an idempotency key, that is, a value that guarantees that one and the same submission sent twice produces only one processing run. You choose its value, and you keep it for the time of your attempts.

HTTP
Idempotency-Key: batch-example-0001

This header answers the case where you get no response: network cut, timeout, restart on your side. You do not know whether the batch went out. Send the same request again with the same value, and you get the response of the first call without a second batch going into processing.

Three behaviors to know.

  • Same idempotency key, same batch: you get back the response of the first call. We do not start a second processing run.
  • Same idempotency key, different batch: the response is 409. We do not give you the response of the first call, because it does not describe what you have just sent.
  • Same idempotency key while the first call is still being processed: the response is 409 as well. Wait for the first call to finish, then try again.

We keep a value for 24 hours. After that delay, the same value becomes a fresh request again, and sending it again would put a second batch into processing. So never use a fixed value: draw a new value per batch, and keep it for the time of the attempts for that batch.

The comparison between two batches is made on their content once normalized. The order of the lines, the order of the columns, the format chosen between CSV and JSON and the cells left empty change nothing: we recognize two submissions of the same content as identical.

A resend that returns the cached response still consumes one call against your rate limit. It does not consume your daily quota, and counts no piece.

#Pagination

Only one endpoint of this API returns a list: GET /v1/partner/webhooks.

ParameterTypeDefault valueDescription
skipinteger0number of elements to skip, starting from 0
limitinteger20number of elements to return, between 1 and 100

The response contains two fields: items, the requested page, and total, the total number of subscriptions of your brand. So you walk the list by increasing skip by the value of limit until you cover total.

You receive the subscriptions from the most recent to the oldest.

#What to remember before opening the reference

  • The address is https://api.sealtrust.io, and the form to use is /v1.
  • The key goes in Authorization: Bearer, and nowhere else.
  • The secret is displayed only once. A key always expires.
  • A 401 speaks about the key itself. A 403 speaks about a permission, a status, or what your plan allows.
  • A 429 comes either from the rate or from the daily quota. Retry-After points to the rate, X-Quota-Limit points to the daily quota.
  • A 503 comes from the service that keeps the rate counters, and means that the call did nothing.
  • The status of a batch replies finished for a wholly successful batch and for a partly successful one alike: read success_count and error_count.
  • A batch request sent again after a cut must carry the same Idempotency-Key as the first attempt.

Every endpoint has its own page, with its parameters, its real response and its full table of errors. You find them in the sidebar, arranged by task: "Create products", "Report a sale", "Receive events".

The Errors page gathers the codes common to the whole API.

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