API DocumentationReference

API guide

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.

Start here

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 command line

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.

Assistants

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.

Help

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.

The base URL

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.

Versions

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.

Credentials

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.

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.

What a credential may see and change

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.

Permissions

A permission grants a verb. A call the credential has no permission for is refused, and the answer says which key it would need.

Pagination

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.

Repeating a request safely

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.

Writing a passport

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.

The passport lifecycle

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.

Calls that cannot be undone

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:

Work that runs in the background

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.

The event feed

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.

Webhooks

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.

Tasks

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.

Rate limits

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.

Caching

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.

When something is refused

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.

Reading after writing

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.