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 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:read is 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:write for a PATCH /v1/passports/:id that releases a passport or takes one out of released, and on top of lab:write for a Lab transform or rollback that assigns publish_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:write for 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.

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:

RoleHolds
AdministratorEvery permission, including members, roles, API keys and team settings.
EditorEvery 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.
ViewerEvery 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:

TierRefused
WriteEvery 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.
CreateEvery 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.
FullEvery 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:

AbilityRequires
ontologies:writeontologies:read
passports:read, passports:writeontologies:read
compositions:read, compositions:writeontologies:read
imports:read, imports:writeontologies:read
releases:writepassports: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 403 ability_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 sees 404 for another team's ontology.
  • Lists filter, never refuse. GET /v1/ontologies is the one team-wide list a scoped key may call: it returns exactly the ontologies in the scope, with created[gte], created[lte], limit and cursor working over that list.
  • Edits. A scoped ontologies:write opens, 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/ontologies under an ontologies:write restricted to specific ontologies is 403 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:

CodeMeaning
insufficient_permissionsThe key was never granted the ability, or its owner no longer holds it.
ability_not_scoped_to_resourceThe grant is there, but its ontology scope does not cover this resource.
ability_missingThe operation stands in for a passport write — opening or applying a data request — and the key holds no passports:write at all.
team_suspendedThe key's team is suspended.
company_requiredThe 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.


GET/v1/me

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

GET
/v1/me
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"] }
  }
}

GET/v1/ping

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

GET
/v1/ping
curl https://api.synexcloud.com/v1/ping

Response

{
  "object": "api.ping",
  "message": "pong",
  "request_id": "req_9f2c1ab34d5e6f70"
}

Was this page helpful?