One page to get a token, learn what a credential unlocks, check a passport before writing it, and see which calls cannot be undone. The full reference, every field of every endpoint, is the API documentation.
Exchange the client id and secret for a token, then ask what the token allows:
curl -X POST https://<host>/api/oauth/token \
-d grant_type=client_credentials \
-d client_id=<client id> \
-d client_secret=<client secret>
transpareo auth login --host <host> --client-id <client id>
curl https://<host>/api/me -H "Authorization: Bearer <token>"
transpareo me
The five calls most integrations need:
# 1. What a passport of this product would need
curl "https://<host>/api/dpps/requirements?productId=<id>&granularity=item" \
-H "Authorization: Bearer <token>"
transpareo dpps requirements --product-id <id> --granularity item
# 2. Check a passport without writing anything
curl -X POST https://<host>/api/dpps/validate \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"dpp":{"unlockableId":<id>,"granularity":"item","batchIdentifier":"<lot>","serialIdentifier":"000412"}}'
transpareo dpps validate --file passport.json
# 3. Write it
curl -X POST https://<host>/api/dpps \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-H "Idempotency-Key: create-000412" \
-d '{"dpp":{"unlockableId":<id>,"granularity":"item","batchIdentifier":"<lot>","serialIdentifier":"000412"}}'
transpareo dpps create --file passport.json
# 4. Publish it
curl -X POST https://<host>/api/dpps/<code>/publish \
-H "Authorization: Bearer <token>"
transpareo dpps publish <code>
# 5. Follow what happened
curl "https://<host>/api/events?since=<cursor>" \
-H "Authorization: Bearer <token>"
transpareo events tail --since <cursor>
The same API answers a terminal, a script and an assistant
through transpareo, the open source command line client and
MCP server. Install it with the script on Linux or macOS, or
with Homebrew on macOS. The rest is one login, and for an
assistant one registration:
curl -fsSL https://transpareo.com/cli/install.sh | sh
brew install transpareo/tap/transpareo
transpareo auth login --host <workspace host> --client-id <key>
transpareo me
transpareo setup claude
The login reads the secret from standard input or from
TRANSPAREO_CLIENT_SECRET, never from an option, and keeps it
in the operating system’s keyring with the token beside it, so
later commands reuse the token for its hour. Every endpoint is a
command; transpareo api <METHOD> <path> reaches one that has
no command of its own.
The setup line installs the agent skill and registers the MCP server, whose configuration holds no secret, only the profile name:
{ "mcpServers": { "transpareo": { "command": "transpareo",
"args": ["mcp", "--profile", "acme"] } } }
The repository at https://github.com/transpareo/transpareo-cli documents every command with its example, the MCP tools and the credential store.
Every workspace answers the Model Context Protocol at
https://<host>/mcp, so an assistant running in a browser
reaches the same tools with nothing installed. ChatGPT and
Claude connect by that address alone: paste it as a connector,
sign in to the workspace, and approve the consent screen, which
names every permission the assistant asks for one by one. The
OpenAI Agents SDK’s hosted tool takes the same address with a
token instead. The tools are the ones the command line client
offers, under the same names. The connection appears on the API
consumers page of the application manager, with the name and
the date of the person who approved it, and is switched off
there.
This guide describes the calls a program makes. The help articles describe the screens a person uses: where a setting lives, what a button does, which step comes before which. They are the same articles the application manager shows, written for the operator and translated into every language the platform carries.
GET /api/help searches them and GET /api/help/{key} reads
one; as tools they are search_help and read_help. Both need
a valid token and no permission key.
term the question in the reader’s own words. Every
word is matched on its own and the words a question is
phrased with are dropped, so a whole sentence finds what a
keyword would. Hits come back best first.locale is the reader’s language. Without one the answer is
in the workspace’s default language, and an article with no
copy in the language asked for answers its English one and
says so in locale.term reports the whole
set, which tells that apart from a workspace whose help
content has not arrived yet.Every endpoint lives under /api on the workspace’s own host.
The version rides in a header, described under Versions, so no
address carries it.
The API is at version 2. A request names its version in the
Accept header, application/vnd.api.v2+json, or in the v
parameter, ?v=2. A request that names none is answered by the
current version.
application/vnd.api.v1+json is answered as one sending
version 2.info.version), whose minor part rises with each.406 and the
code API_VERSION_UNKNOWN.A credential is a machine account with its own permissions, rate limits and audit trail. It is created in the application manager, which shows the secret once.
POST /api/oauth/token, the OAuth 2.0
client credentials grant, so any OAuth client library can use
it without custom code.scope narrows a token to a subset of the permissions the
credential holds. Without it the token carries all of them.GET /api/me answers what the presented token allows.POST /api/grant turns a scanned passport code into a child
credential for that one passport, valid for a day, so a
workshop or a recycler can record an event without an account
of its own.An assistant or a third-party application asks a person for
access instead of holding a credential of its own. It registers
itself at POST /api/oauth/register, sends the person to
GET /api/oauth/authorize with a PKCE challenge, and the
person approves the request in the application manager, where
the requested permissions are named one by one. The result is a
credential of its own, owned by that person, that can never
reach further than they can, and that stops working when they
lose access or revoke it on the API consumers page. Tokens
obtained this way behave like any other: one hour, bound to
this host, narrowed by scope. A refresh token keeps the
connection alive without asking again and rotates on every
use, and POST /api/oauth/revoke ends either token on
request.
Suspending or revoking a credential, and regenerating its secret, invalidates every token already issued to it.
The storefront is the other kind of caller: a person signs in
at POST /api/session and the answer carries a token for that
session. Everything below is about machine credentials.
A credential reads every record its permissions cover, and the records it creates belong to it. It may change and delete its own records, and only those, unless it holds one of the workspace-wide write keys. A person signing in sees their own records and those of their group; an administrator sees all of them.
The product, component and brand lists of a credential carry
every record of the workspace, drafts included, with rejected
products left out; published=false selects the drafts and
published=true the published records. The storefront’s own
readers keep the published lists.
Publication is the storefront’s concept: it says whether the
catalogue shows a record. Read GET /me for
workspace.publishesOnCreate before you treat publishing as a
step.
Where it is true, the workspace has no storefront. Every product and every component is published as it is created, the publish and unpublish operations answer 403, and the screens the person works on carry no publish control, so ask them to publish nothing and leave a storefront, catalogue visibility and a frontend app out of what you tell them.
Where it is false, a record is published on demand. Publish a component before products can carry it, and publish a product to make it visible. Publication decides one link either way: a product’s component rows name their components, and a name links to a component only while that component is published.
A credential issued for a scanned passport code carries a scope: a list of passports it is confined to. Anything outside that list answers as though it did not exist.
A permission grants a verb. A call the credential has no permission for is refused, and the answer says which key it would need.
brand_access - DELETE /brands/{id}, POST /brands, PUT /brands/{id}brand_write - DELETE /brands/{id}, POST /brands, PUT /brands/{id}component_access - DELETE /components/{id}, POST /components, PUT /components/{id}, PUT /components/{id}/publish, PUT /components/{id}/unpublishcomponent_write - DELETE /components/{id}, POST /components, PUT /components/{id}, PUT /components/{id}/publish, PUT /components/{id}/unpublishdpp_bulk_write - GET /dpps/bulk/{taskId}, POST /dpps/bulk, POST /dpps/bulk/validatedpp_dynamic - PATCH /dpps/{id}/dynamic_data, POST /grantdpp_events - POST /dpps/{id}/events, POST /grantdpp_history - GET /dpps/{id}/events, GET /events, POST /grantdpp_lifecycle - POST /dpps/{id}/reissue, POST /dpps/{id}/supersede, POST /dpps/{id}/voiddpp_publish - POST /dpps/bulk (beside dpp_bulk_write), POST /dpps/{code}/publish (beside dpp_write), POST /dpps/{id}/correct (beside dpp_write)dpp_read - GET /dpps, GET /dpps/requirements, GET /dpps/{code}/private_dynamic_data, GET /dpps/{code}/private_properties, GET /dpps/{code}/private_properties/{version}, GET /dpps/{id}, GET /dpps/{id}/stats, GET /dpps/{id}/versions, GET /lots, GET /lots/{id}dpp_write - DELETE /dpps/{id}, POST /dpps, POST /dpps/validate, POST /dpps/{code}/publish, POST /dpps/{id}/correct, PUT /dpps/{id}export_access - GET /exports, GET /exports/{id}, GET /exports/{id}/download, POST /exportsimport_access - GET /imports, GET /imports/example, GET /imports/supplier_form, GET /imports/{id}, POST /imports, POST /imports/{id}/execute, POST /imports/{id}/revert, POST /imports/{id}/validate, PUT /imports/{id}/mappingsproduct_access - DELETE /products/{id}, POST /products, PUT /products/{id}, PUT /products/{id}/mediafiles, PUT /products/{id}/publish, PUT /products/{id}/restore, PUT /products/{id}/unpublishtask_access - GET /tasks, GET /tasks/assignees, GET /tasks/{id}, GET /tasks/{id}/attachments/{attachmentId}, POST /tasks, POST /tasks/{id}/approve, POST /tasks/{id}/cancel, POST /tasks/{id}/claim, POST /tasks/{id}/complete, POST /tasks/{id}/reject, POST /tasks/{id}/release, POST /tasks/{id}/reopen, PUT /tasks/{id}webhook_access - DELETE /webhooks/{id}, GET /webhooks, GET /webhooks/{id}, POST /webhooks, POST /webhooks/{id}/regenerate_secret, POST /webhooks/{id}/test, PUT /webhooks/{id}Lists take page and per_page. The answer carries the total
and the page size in headers, and a Link header with
rel="next" while a further page exists. Follow that header
rather than counting pages.
Send an Idempotency-Key header with a creating request. The
same key and body within a day replays the stored answer and
carries Idempotent-Replayed: true. The same key with a
different body is refused. A key whose first request is still
running answers 409, so wait and retry.
Bulk passport creation deduplicates differently: rows are matched by the identifiers that name the unit, so resubmitting a batch after a timeout creates nothing twice.
Two shapes come up. Both start by asking rather than guessing.
A product that does not exist yet. GET /api/products/new
answers the property types the workspace defines, which of them
are mandatory, their units and their allowed values. Fill those
in and POST /api/products. A product belongs to a brand,
named beside it under brand, and is made of components, so
the body names at least one under componentsInput, a list of
names or of objects naming a component. A product missing a
mandatory property is refused, and the answer names the
properties under fields.properties.missing.
A variant of a product. Name the product under parentId
in POST /api/products, with a name of its own. The variant
starts as a copy of its parent and takes the parent’s later
changes. Whatever the body names besides, and whatever a later
update changes, becomes the variant’s own; a value sent back
as it was read goes on following. The variant’s
GET /api/products/{id} marks each value follows, pinned
or parent_moved, the last where the parent changed a value
the variant holds of its own. PUT /api/products/{id}/restore
has a value follow the parent again.
Passports for a product that exists. GET
/api/dpps/requirements answers which identifiers the chosen
granularity needs, which properties the passport inherits, what
a publish would say about it today, and a body ready to fill.
Then POST /api/dpps/validate to see the result without
writing, POST /api/dpps to write, and the publish endpoint to
seal it.
Validation is worth the extra call: it runs the same check a publish runs, so a passport that validates is one that publishes.
A passport is a draft until it is published. Publishing signs a snapshot and registers a version; every later change publishes a new version, and the old ones stay readable. A draft takes the product as it stands at every validate and every publish, and the copy freezes when the passport is published. A unit that leaves circulation is voided; a unit replaced by another is superseded, naming its successor; a passport reissued for the same unit records that too. Each of those is an event on the passport’s own log.
Publishing is irreversible by design: the signed snapshot goes into the ten-year archive and its URL may already be printed on a label. These calls cannot be undone either:
Bulk creation, exports and imports answer 202 with the
address to poll. Poll it until the status is final, then read
the result from the same document. Nothing is lost if the
polling stops; the work continues.
A run whose id the caller no longer holds is found again in
the listings. GET /api/imports and GET /api/exports answer
the caller’s own runs, newest first, each as a summary naming
the statusUrl the whole run is read from.
GET /api/events answers every event of every passport in the
order the events were recorded. Keep the nextCursor of each
answer and send it as since on the next call. An empty answer
between polls is normal, and the cursor comes back unchanged.
Events younger than five seconds are held back, so nothing can
appear behind a cursor already handed out. types and
dppCode narrow the feed.
A credential registers its own endpoints under /api/webhooks
and receives the same events as a delivery. Each delivery
carries X-Transpareo-Signature as t=<unix time>,v1=<hex>,
an HMAC-SHA256 over <t>.<body> with the subscription’s
secret. Verify it by recomputing the digest over the timestamp,
a full stop and the raw body, comparing in constant time, and
rejecting a timestamp too far from now.
POST /api/webhooks/{id}/test delivers a synthetic ping and
answers what the endpoint did, so a subscription can be checked
before it is relied on. After eight failed deliveries in a row
a subscription switches itself off; switching it back on clears
the count.
A task is something one person asks of another, assigned to
one person or to one access group. A member claims a group task
before working on it. A task ends done or cancelled and stays
on record. The calls live under /api/tasks and need
task_access.
A credential that acts for a person, a grant the person approved or their assistant, works with tasks as that person: it reads their lists, claims and finishes tasks and asks others in their name. A credential an operator set up by hand acts as itself: it asks people for things and looks after what it asked for, and it claims and finishes nothing, since only a person works on a task.
GET /api/tasks reads one list: mine (the default) for what
waits on the person, groups for every open task of their
groups, requested for the open tasks the caller asked of
others, done for the finished and cancelled ones it took part
in, and all for an administrator. A list the caller may not
read answers 422 and names the ones it may.
Each task names in moves what the caller may do with it now.
Whoever the task is for, or a member of its group, claims and
completes it; completing an unclaimed group task claims it in
the same call. Whoever holds a task, whoever asked for it, or an
administrator releases it. Whoever asked for it, or an
administrator, changes, cancels and reopens it. A move the task
refuses answers 422 with the reason in the code, such as
TASK_ALREADY_CLAIMED.
POST /api/tasks takes an assigneeKey, User:<id> or
AccessGroup:<id>. GET /api/tasks/assignees?term= finds the
key by name.
The hourly allowance belongs to the credential, not to the
address, so every location using the same credentials shares
it. Every answer carries X-RateLimit-Limit, -Remaining and
-Reset, the Unix time at which the current window ends. Bulk
rows count against a separate per-minute allowance. A refusal
answers 429 with Retry-After in seconds; wait that long
rather than retrying at once.
The token exchange and the sign-in attempts are throttled separately, per address, to bound guessing.
Read answers are cached for ten minutes by default, and the
Cache-Control header of each answer says what applies to it.
Append ?reload=1 to a read to go past the cache when a fresh
answer matters more than a fast one.
Every error has the same shape: a stable error code, a
message in the workspace’s language, fields when individual
attributes failed, and a hint saying what to do next. The
hint is the part worth reading first; it names the permission
to ask for, the URL to poll, or the option that overrides the
refusal.
Every error also carries docsUrl, the address of the section
of this guide that explains the rule the request ran into.
A write answers the record it wrote, so there is nothing to wait for on the API itself. The public passport page is served through a content delivery network: publishing purges the document that lists the versions, and every snapshot URL names the bytes it holds, so a reader never sees old content under a fresh address.