Authentication
Every request to the Synex API authenticates with a secret API key sent as a bearer token. Keys are pinned to a team, carry a set of abilities, never reach further than the team member who minted them, and can be restricted to specific ontologies — so you can hand an integration exactly the access it needs and nothing more.
API keys
Keys are created and revoked from API Keys in the Synex application, and over the API itself at /v1/api_keys. Send the secret in the Authorization header on every request:
Authorization header
curl https://api.synexcloud.com/v1/me \
-H "Authorization: Bearer synex_sk_..."
The secret (synex_sk_...) is shown once, when it is issued — only a hash
is stored. Treat keys like passwords: keep them out of client-side code,
repositories, and logs. If a key leaks, roll
or revoke it.
The key object carries id, name, abilities, scopes, metadata, last_used_at, expires_at, the rotation lineage (rolled_at, rolled_to, rolled_from) and created_at. The team a key is pinned to is not a field on it over /v1 — there is only ever one answer, and GET /v1/team is where to read it.
last_used_at is the field to sweep for: a key with a null or long-stale value is one nobody is using, and a key nobody is using is a credential you are carrying for no reason.
Expiry
A key may be given an optional expires_at at creation — any future instant, up to two years out. Past its expiry it stops authenticating; nothing warns you first, so treat the date as a deadline you schedule against rather than a notification you will receive.
Expiry is set once and cannot be pushed back. A deadline that can be extended is not a deadline, and rotating rather than extending is what rolling is for.
Over the API there is one extra rule: a key may not outlive the key that created it. See API keys.
Rolling a key
Rolling issues a new secret while the old one keeps working, so a deploy never lands in a window where neither secret authenticates. The old secret keeps working for 24 hours; redeploy inside that window and there is nothing to clean up.
Three details worth knowing before you build a rotation job on it:
- The replacement does not inherit the rolled key's expiry. From the Synex application it is minted perpetual. Over the API the replacement instead inherits the calling key's expiry, so a self-roll cannot turn an expiring key perpetual — the one place the two surfaces deliberately disagree.
- Rolling never extends a key. If the old key was already due to expire inside the next 24 hours, it keeps its own earlier deadline.
- Rolling twice does not restart the window. Rotate at the pace of your deploys, not faster.
Rolling by id requires api_keys:write and that the target reaches no further than the caller. Rolling yourself through POST /v1/api_keys/self/roll needs no ability at all and is the recommended hygiene path.
Team pinning
Every key is pinned to the team it was created in, and all requests act on that team. Switching teams in the web app never changes what a key can see. If the key's creator leaves the team, the key stops working and requests fail with 401 api_key_team_revoked.
Abilities
A key carries a set of abilities, and each endpoint requires one. A request without the required ability fails with 403 insufficient_permissions, and the message names the ability.
- Name
templates:read / templates:write- Type
- ability
- Description
List and read templates · create, edit, fork, complete, and delete them.
- Name
ontologies:read / ontologies:write- Type
- ability
- Description
List and read ontologies, their versions and open draft · instantiate templates, edit and publish drafts, and update metadata.
ontologies:readis the root every ontology-bound ability sits inside — see below.
- Name
passports:read / passports:write- Type
- ability
- Description
Read, list, and download QR codes, and follow release runs · create, update, and delete passports — but not release them; see the next row.
- Name
releases:write- Type
- ability
- Description
Release and un-release passports — on top of
passports:writefor aPATCH /v1/passports/:idthat releases a passport or takes one out ofreleased, and on top oflab:writefor a Lab transform or rollback that assignspublish_status. Not granted by default.
- Name
compositions:read / compositions:write- Type
- ability
- Description
Read presentation layouts · create and update them and their translations.
- Name
files:read / files:write- Type
- ability
- Description
Read file metadata, list directories, mint download links, preview spreadsheets · upload files, run multipart uploads, create folders.
- Name
imports:read / imports:write- Type
- ability
- Description
Inspect imports, their rows and the failed-row export · start imports and retry failed rows.
- Name
team:read- Type
- ability
- Description
Read the team's identity and member roster. Not granted by default — the roster is personal data.
- Name
lab:read / lab:write- Type
- ability
- Description
Lab queries, DSL tools, runs, usage, catalog and exports · transforms, rollbacks, and enabling or disabling Lab.
- Name
events:read- Type
- ability
- Description
Read the change feed — the team's record of who changed what. Not granted by default.
- Name
api_keys:read- Type
- ability
- Description
List and read the team's API keys, secrets excluded. Not granted by default.
- Name
api_keys:write- Type
- ability
- Description
Mint, patch, revoke and roll the team's API keys. Not granted by default, and sensitive.
- Name
design:read / design:write- Type
- ability
- Description
Read the public-passport theme · update it. Not granted by default.
- Name
data_requests:read / data_requests:write- Type
- ability
- Description
List and read data requests and bulk data requests from either side · as the customer, open, review, apply, return and cancel them; as the supplier, prefill, draft and submit. Opening and applying also need
passports:writefor the passport's ontology. Not granted by default.
- Name
suppliers:read / suppliers:write- Type
- ability
- Description
Read supplier links from either side · invite, rename, revoke, and resend the invitation. Not granted by default.
- Name
webhook_endpoints:read- Type
- ability
- Description
List and read webhook endpoints and their delivery records. Not granted by default.
- Name
webhook_endpoints:write- Type
- ability
- Description
Create, update, delete endpoints and roll signing secrets. Not granted by default, and sensitive.
The default set is only four
templates:read, ontologies:read, passports:read and compositions:read. Every other ability above — every write, and every read marked not granted by default — has to be asked for explicitly when the key is minted. A key gets what you name and nothing more.
A key holds the abilities it was minted or edited with. An ability the
catalog gains later is not added to existing keys — team:read is a separate
grant even for a key that has been calling GET /v1/me for months. Edit the
key in API Keys, or patch it, to add what
you need; no secret is rotated by doing so.
Each of the non-default ones is out of the default for its own concrete reason. team:read returns member names and email addresses. design:write restyles every public passport the team has released. releases:write is split from passports:write because a release is what the team is billed for and what a regulator reads — a key that fills in values should not be able to release them by accident. data_requests:write opens requests whose answers are written onto your passports with your authority — which is why opening one also needs passports:write — and, held by a supplier team, submits those answers. suppliers:write invites other companies into a relationship with your team and can end one, cancelling every request in flight. api_keys:read exposes the whole team's credential inventory, and api_keys:write can revoke any of it — including the calling key.
One operation needs no ability at all: POST /v1/api_keys/self/roll rotates the calling key, and a replacement with identical abilities escalates nothing. A key holding none of the above can still rotate itself, which is the point.
The two sensitive abilities
webhook_endpoints:write lets a key point a destination at your team's data and roll a secret out from under a running consumer. api_keys:write lets a key mint, narrow, revoke and rotate the team's credentials — including the one making the call.
Minting is subset-constrained, which is what makes the second one safe to offer at all: a key may only grant abilities it already holds, may only narrow an ontology scope, may not outlive its parent, and inherits its team rather than choosing one. Rolling carries the same constraint, because rolling returns a secret. A key therefore cannot widen its own reach. See the security model.
Abilities a key cannot hold
team:write, members:write, roles:write and releases:read govern the team's settings, membership, roles and the Reporting page in the Synex application. No /v1 endpoint is gated by them, so they are not offered when a key is minted, and naming one is 400 parameter_invalid — as is naming any ability outside the list above.
Keys are capped by their owner
A key's reach is the intersection of what it was granted and what the member who minted it may currently do on the team. Minting — in the Synex application as much as over /v1/api_keys — refuses any ability or ontology scope the minting member does not hold, and every request afterwards re-reads the member's permissions and caps the key with them.
A member whose role is narrowed takes every key they minted down with them the moment the change lands. A key whose owner has lost an ability fails with the same 403 insufficient_permissions as one that was never granted it, and a key whose owner was restricted to fewer ontologies fails with 403 ability_not_scoped_to_resource outside them. A key whose owner leaves the team stops authenticating altogether (401 api_key_team_revoked).
The key object — and GET /v1/me — reports what the key was granted. What it can do is that list capped by its owner's permissions, and GET /v1/team/members says which roles the owner holds.
Team roles
A member's permissions come from the roles they hold on the team. Every team starts with three presets, and may rename them, edit their permissions, or add roles of its own:
| Role | Holds |
|---|---|
Administrator | Every permission, including members, roles, API keys and team settings. |
Editor | Every read except api_keys:read, plus the content writes: templates, ontologies, passports, releases, compositions, imports, files, design, data requests and suppliers. Not api_keys:write, webhook_endpoints:write or lab:write. |
Viewer | Every read except api_keys:read; no writes. |
The team owner holds everything. Anyone else holds the union of their roles and any permissions granted to them individually — so a key minted by a member who holds only Editor can never write webhook endpoints or run Lab transforms, whatever it was granted.
Suspended teams
The team is the outer bound. A suspended team's keys are refused with 403 team_suspended regardless of what they were granted or what their owner may do. A suspension has three tiers:
| Tier | Refused |
|---|---|
| Write | Every endpoint that requires a *:write ability. POST /v1/api_keys/self/roll needs none and stays available, so a compromised key can always be rotated. |
| Create | Every endpoint that creates something new — a template or fork, ontology, draft, passport, composition, import, file, upload, folder, supplier link, data request, bulk data request, webhook endpoint or API key. |
| Full | Every authenticated endpoint, GET /v1/me included. |
Public passport pages are unaffected.
Ontologies are the root
A passport, a composition and an import each belong to an ontology, and an ontology's draft is edited by someone who can read it. The abilities that reach them therefore presuppose ontologies:read, and the API enforces it:
| Ability | Requires |
|---|---|
ontologies:write | ontologies:read |
passports:read, passports:write | ontologies:read |
compositions:read, compositions:write | ontologies:read |
imports:read, imports:write | ontologies:read |
releases:write | passports:write |
ontologies:read is the root: it requires nothing. A key's grants are consistent when every ability in that table is held together with the one it requires and — for the scopable ones — reaches no ontology its requirement does not. With ontologies:read restricted to ontology A, passports:write may be restricted to A or left off the key, but not left unrestricted (that reaches every ontology) and not restricted to A and B. With ontologies:read unrestricted, a dependent may be scoped however you like. releases:write is not scopable, so holding passports:write at all is enough for it.
Every place a grant is written refuses an inconsistent map. On POST /v1/api_keys and PATCH /v1/api_keys/:id that is 400 grant_inconsistent, with param set to the dependent ability and a message naming both sides:
passports:write requires ontologies:read.passports:write cannot reach every ontology while ontologies:read is restricted to some.passports:write reaches ontologies outside ontologies:read: ontology_…— the ids to remove.
The subset checks above run first, so a body that is both wider than the caller and inconsistent with itself reports ability_not_grantable or scope_not_grantable. The role and key editors in the Synex application refuse the same map.
The rule also holds on every request. A key's effective abilities are capped by it: a dependent whose requirement is missing is dropped, and a scoped dependent is narrowed to its requirement's ontologies. That matters because the owner cap intersects two maps that are each consistent, and an intersection need not be — so when a demotion leaves releases:write with no passports:write to stand on, the cap drops it too. A key minted consistently is never narrowed by this; it only ever removes what could not have worked.
Ontology scopes
Eight abilities can be restricted to specific ontologies when the key is created: ontologies:read, ontologies:write, and the six that touch passport data — passports:*, compositions:* and imports:*. A scoped key:
- can act only on resources inside its listed ontologies; anything else fails with
403ability_not_scoped_to_resource; - cannot use list endpoints that span the whole team (for example
GET /v1/imports) — fetch those resources by id instead — with the one exception below; - cannot reach team-global resources that have no ontology to match against.
Scoping ontologies:read — the root — restricts the team as the key sees it:
- Reads. An ontology in the scope is served, with its versions and its draft. Any other id is
403 ability_not_scoped_to_resource— whether it names another of the team's ontologies, another team's, or nothing at all. The scope is decided from the path before the ontology is looked up, so a scoped key never learns whether an id outside its list exists. An unscoped key sees404for another team's ontology. - Lists filter, never refuse.
GET /v1/ontologiesis the one team-wide list a scoped key may call: it returns exactly the ontologies in the scope, withcreated[gte],created[lte],limitandcursorworking over that list. - Edits. A scoped
ontologies:writeopens, saves, discards and publishes the draft of an ontology in its scope, and patches its metadata; outside the scope,403. - Creation needs an unscoped write. A new ontology is in no scope yet, so
POST /v1/ontologiesunder anontologies:writerestricted to specific ontologies is403 ability_not_scoped_to_resource.
Because every dependent ability sits inside ontologies:read, scoping the root is how a key is restricted to a product line as a whole: scope ontologies:read to the line's ontologies and no passport, composition or import ability on the key can be granted beyond them.
files:* is deliberately not scopable: a file belongs to the team, not to an ontology, so there is nothing for a scope to match. The same reasoning rules out the rest — templates:*, releases:write, team:read, lab:*, events:read, design:*, data_requests:*, suppliers:*, webhook_endpoints:* and api_keys:*. A template exists before any ontology is made from it; the design is a team singleton; the member roster is the team; the event feed is team-global; data requests, suppliers and webhook endpoints belong to the team rather than any one ontology; and a key is a team credential whose own scopes are the thing being described. Naming any of them in scopes is 400 parameter_invalid.
Use scoped keys for integrations that should only ever see one product line.
No role gates
No endpoint asks anything of the key's owner beyond the ability itself — enabling and disabling Lab, and Lab transforms and rollbacks, are gated by lab:write with no role check on top. What a role gate would protect, that a demoted person's keys stop reaching a sensitive surface the moment they are demoted, is what the owner cap does for every ability.
So a 403 that means "not allowed" carries one of these codes, beside the few operation-specific ones listed under errors:
| Code | Meaning |
|---|---|
insufficient_permissions | The key was never granted the ability, or its owner no longer holds it. |
ability_not_scoped_to_resource | The grant is there, but its ontology scope does not cover this resource. |
ability_missing | The operation stands in for a passport write — opening or applying a data request — and the key holds no passports:write at all. |
team_suspended | The key's team is suspended. |
company_required | The key's team is a supplier team with no company, so it cannot issue passports. |
One place where a stricter gate might reasonably be expected and deliberately is not: PATCH /v1/design. In the Synex application a member edits the team's branding if their role grants design:write — the default Editor does — and making the API stricter than the surface it mirrors would leave an integration unable to do what the person configuring it can already do by hand. The non-default design:write ability, capped by the key owner's own, is the gate here.
Inspect the current key
Returns the context the key is acting in — its pinned team, the abilities it carries, and its ontology scopes. Call this to verify a key before relying on it, or to discover what an integration has been granted.
abilities and scopes are what the key was granted. On every request they are additionally capped by its owner's current permissions, so a key whose owner has since been demoted can do less than this lists — see keys are capped by their owner. scopes is keyed by ability, or null when the key is unscoped.
This endpoint requires only a valid key (no specific ability). Under a full team suspension it answers 403 team_suspended.
Request
curl https://api.synexcloud.com/v1/me \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "api.key",
"team": { "id": 42, "name": "Kestrel Energy Systems" },
"abilities": [
"templates:read",
"ontologies:read",
"passports:read",
"passports:write",
"imports:read",
"imports:write",
"files:read",
"files:write"
],
"scopes": {
"passports:read": { "ontologies": ["ontology_7d1f4a2b9c3e5081"] },
"passports:write": { "ontologies": ["ontology_7d1f4a2b9c3e5081"] },
"imports:read": { "ontologies": ["ontology_7d1f4a2b9c3e5081"] },
"imports:write": { "ontologies": ["ontology_7d1f4a2b9c3e5081"] }
}
}
Check connectivity
An unauthenticated connectivity check — the one endpoint in v1 that needs no key at all. Use it to confirm reachability, DNS and TLS before you start debugging credentials.
The response echoes the request_id, which is the same value the X-Request-Id header carries and the one to quote in a support request.
Request
curl https://api.synexcloud.com/v1/ping
Response
{
"object": "api.ping",
"message": "pong",
"request_id": "req_9f2c1ab34d5e6f70"
}