Authentication
Authentication
A secret key in the Authorization header is how you reach your own records. There is no other way in, and no request is ever authenticated by a session cookie. The category endpoints are the exception: they describe the fields we ask for rather than anybody's data, and they answer without a key.
Keys
A key is shown once, at the moment it is created. Keep it somewhere the servers can read it and a browser cannot.
- pc_sk_live_…
- pc_sk_test_…
Test and live stay separate
The mode is bound into the credential itself rather than passed as a parameter, so a test key can neither read nor change your real passports. A misrouted request fails instead of reaching the wrong catalogue. Test passports are deleted 90 days after the passport itself was last edited.
Sending the key
Bearer authentication over HTTPS. Nothing else is accepted, and a request without the header is refused before it reaches any of your records.
curl "https://passportcraft.com/api/v1/whoami" \
-H "Authorization: Bearer pc_sk_live_…"Browser credentials are deliberately unsupported. A key belongs on a server, and a key in front-end code is a key that has been published.
Scopes
A key carries a fixed set of scopes, chosen when it is created. A request that lacks the scope its endpoint requires is refused, and the refusal names the permission that was missing.
documents:read
documents:write
filings:write
keys:admin
passports:read
- get
/organizations/{organization}/capabilitiesDescribe what this credential may do for this organization. - get
/organizations/{organization}/passportsList passports. - get
/organizations/{organization}/publish-approvals/{id}Fetch one publish approval. - get
/organizations/{organization}/passports/{id}Fetch one passport. - post
/organizations/{organization}/passports/{id}/validateCheck a passport against its category schema without saving. - get
/organizations/{organization}/passports/{id}/linksFetch public and GS1 Digital Link URLs. gs1_digital_links includes every variant with variant_id, size and colour. The own-lead URI appears once with its variant metadata; a serialized lead URI remains separate. - get
/organizations/{organization}/passports/{id}/affichageRead the French affichage environnemental state. - get
/organizations/{organization}/passports/{id}/variantsList size and colour variants. - get
/organizations/{organization}/passports/{id}/variants/reviewRead the sizes and colours a person reviews before publishing, with their fingerprint. - get
/organizations/{organization}/importsList import jobs. - get
/organizations/{organization}/imports/{id}Fetch one import job and its progress.
passports:write
- post
/organizations/{organization}/passportsCreate a passport. - patch
/organizations/{organization}/passports/{id}Apply a JSON Merge Patch to a passport. - post
/organizations/{organization}/passports/{id}/publishPublish a passport. - post
/organizations/{organization}/passports/{id}/unpublishUnpublish a passport. - post
/organizations/{organization}/passports/{id}/trashMove a passport to trash. - post
/organizations/{organization}/passports/{id}/restoreRestore a passport from trash. - post
/organizations/{organization}/passports/{id}/variantsCreate size and colour variants. - delete
/organizations/{organization}/passports/{id}/variants/{variantId}Delete one variant. Connector credentials cannot delete variants. - post
/organizations/{organization}/passports/{id}/variants/removeRemove variants by GTIN. - post
/organizations/{organization}/importsStart an asynchronous bulk import.
units:read
units:write
No scope required
A valid credential is enough. These endpoints return nothing that belongs to a single tenant.
No credential required
Readable without a key. These endpoints describe the product itself — which categories exist, and which fields each one asks for — so the answer is the same for every reader. A key is still accepted, and one that has been revoked or has expired is still refused. Without a key you are measured against the anonymous counter of 60 requests a minute per client address.
One key for several organizations
A key belongs to one organization, and it can act on another organization only where it holds a grant. An organization that manages clients can grant its keys access to each client, with permissions chosen per client. Each request names one organization in its path and acts on that organization alone.
Giving a key access to a client
Call POST /organizations/{organization}/grants with the client's id in the path, and the key's key_id and the scopes to grant in the body. The key that makes the call needs keys:admin on its own organization. A client can be granted only scopes the key already holds on its own organization, and filings:write also needs the client's written authorisation to be confirmed first.
curl "https://passportcraft.com/api/v1/organizations/{client}/grants" \
-X POST \
-H "Authorization: Bearer $PASSPORTCRAFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key_id": "K3n9Qw2P", "scopes": ["passports:read", "passports:write"]}'When access ends
DELETE /organizations/{organization}/grants/{keyId} ends one grant, and the key keeps working on every other organization it holds a grant on. A client can also remove a key's access in its own Settings. Ending the arrangement between a client and your organization ends every grant on that client at once. After that, each request for the client answers 404 organization_not_found_or_not_granted, the same answer as for an organization that does not exist. To tell the two apart, call GET /organizations: an organization you lost access to is no longer listed.
Each entry in GET /organizations has display_name, the organization's name, and link_status, which is "accepted" for a client your organization manages and null for your own organization.
Live keys for an organization that manages clients
An organization that manages clients can create a live key when its own plan, or the plan of at least one of its clients, is a paid Pro or Scale plan. A plan on a trial, or with a failed payment, does not count. Each request is still checked against the plan of the organization named in its path.
Example: one scheduler, ten clients
A nightly job calls GET /organizations to list the organizations its key can act on, then syncs each client separately. A client that leaves drops out of the list, so the job stops calling it instead of logging a 404 every night.
const base = 'https://passportcraft.com/api/v1'
const headers = { 'Authorization': 'Bearer ' + process.env.PASSPORTCRAFT_API_KEY }
const { data } = await (await fetch(base + '/organizations', { headers })).json()
const clients = data.filter((connection) => connection.link_status === 'accepted')
for (const client of clients) {
// One organization per request, always named in the path.
await syncCatalogue(client.organization_id, client.display_name)
}