Authentication and scopes
Sending the key
Every request carries the key as a bearer token:
Authorization: Bearer <your key>
There is no other accepted form — no query parameter, no custom header, no cookie. The key is shown once, when it is created. It is not recoverable afterwards; if it is lost, a new one is issued.
People sign in; apps hold credentials
A key (a credential) is your app's own identity, for work no person is doing. When a person uses your app, they can sign in with their ReSync account instead, and your app then acts as them, with their access and never more than the app is registered for — see sign-in.md. Both are sent the same way, as the bearer token.
What a key is bound to
A key belongs to exactly one organization, called its owner. That binding is fixed for the life of the key: it cannot be moved to another organization, and a key cannot be made to act for two.
Everything the key can reach is owned by that organization. There is no shared or public pool of resources, so a resource belonging to another organization is refused whether you reach for it by id, by list, or through a related record.
The scope sets
A key's scopes are lists. Three type sets — read_types, operate_types and attest_types — govern most doors
(the table below). find_or_create_types is a fourth type set that governs one door, and bind_systems lists
external systems, not types.
| Set | Grants | Applies to |
|---|---|---|
read_types |
list and fetch | GET doors |
operate_types |
change | PATCH, enrol, exit, attest, status |
attest_types |
record and advance, never change | attest, status — and the GET doors |
Operating implies reading, and so does attesting. A type in operate_types or attest_types does not need
repeating in read_types; the readable set is the union of the three. attest_types lets a key record attestations
and move status on a type without being able to edit, enrol or exit anything of that type (see
Attesting without changing).
Which endpoints need which:
- Read scope —
GET /v1/resources(list),GET /v1/resources/:id,GET /v1/resources/search, and the per-resource reads:/events,/advancement,/members,/containers,/capabilities,/requirements,/ports,/ports/:portId/legal-targets. - Attest scope (
attest_typesoroperate_types) —POST /v1/resources/:id/attestationsandPATCH /v1/resources/:id/status. - Operate scope —
PATCH /v1/resources/:id,POST /v1/resources/:id/members,POST /v1/resources/:id/exit,POST /v1/resources/:id/move,POST /v1/resources/:id/attestations,PATCH /v1/resources/:id/status, theenrollInto/moveOf/moveFromfilters of search, andPOST/DELETE /v1/resource-interactions— operate on the side whose port points out (the side making the choice), read on the side it points to. bind_systems— the external systems your key may bind identifiers under:POST /v1/aliases/bindandPOST /v1/identity-claims/redeem.
find_or_create_types governs POST /v1/resources/find-or-create only. It is separate on
purpose: being able to change a bid is not the same as being able to bring one into existence.
The general-reference lists — types, statuses, lifecycles, units and so on — are not type-scoped. Any
valid key may read them. A by-id read of one of them (a type, a lifecycle, its graph, its transitions) answers 404
for anything the key's own list would not show.
POST /v1/ingest also authenticates an API key. Its contract is not yet part of this pack.
Attesting without changing
attest_types is a list of types on which the key may record attestations and move status, and nothing more. It reads
them too. It does not let the key edit, enrol or exit: those still need operate_types, and a key without it is
refused 403 operate_types_required. Use it for a client whose job is to report what happened — a scanner, a
checklist app — rather than to change records.
Device credentials
A device credential is a key that acts at one resource — a workstation, a counter, a door. An administrator at the owning organization issues it like any key, with up to three more scope entries:
| Scope | Meaning |
|---|---|
acts_at_resource_id |
The resource the device acts at. The key may read it and everything inside it, and record attestations and move status there. It never edits, enrols or exits. |
actor_source |
presence_session — the device acts for whoever has checked in at it, never for itself. |
presence_session_ttl_s |
Seconds of inactivity before a check-in ends: 30–3600, 300 when absent. |
A device credential may also hold the other scope sets; attest_types is the usual one, for types it records on
outside its own resource.
Checking people in. A device credential with actor_source opens a presence session when a person presents an
identifier to the device (endpoints): POST /v1/presence-sessions with the
identifier's system and code. One person is checked in at a time; a different person's check-in ends the previous one.
The session ends on its own after presence_session_ttl_s seconds without activity; every write made under it, and
POST /v1/presence-sessions/current/touch, counts as activity.
Acting for the person. Every write by a device credential with actor_source must carry the presenceSessionId
it received at check-in. The write is recorded as that person's act. It cannot name an actor any other way:
actorResourceId and actorAlias are refused (403 actor_source_presence_session_only), a missing id is
400 presence_session_required, and an ended or expired session is 401 presence_session_expired — check the person in
again. The id is returned as a string; a string or a number is accepted back.
Linking an identifier. An administrator issues a one-time enrolment code for a person in ReSync; the person
presents the code and the identifier to the device, which calls POST /v1/alias-enrollments/redeem. The enrolment must
belong to the key's own organization.
Live updates. A device credential may hold the live-update stream, GET /v1/live — for its own resource and what it
can read. Any other key is refused (below).
What it checks every time. On every request a device credential's resource must still exist
(403 acts_at_resource_missing) and still belong to the key's organization (403 acts_at_outside_owner_grant).
GET /v1/api-keys/self returns these entries inside scopes, and actsAt names the resource:
From device-self.json — status 200:
{
"apiKeyId": 785,
"name": "device-capture",
"scopes": {
"actor_source": "presence_session",
"attest_types": [
"person"
],
"acts_at_resource_id": 24914,
"presence_session_ttl_s": 300,
"created_owner_resource_id": 5
},
"actsAt": {
"resourceId": 24914,
"name": "Capture Station"
},
"owner": {
"resourceId": 5,
"name": "Popular Design Mill & Cabinet, Inc."
}
}
Telling ReSync which device. A device credential's app may send x-device-id on its requests — an id the app makes
once per install (letters, digits, ., _, : and -; up to 128 characters). The first request from a new device
is recorded on the credential's history with the device's user-agent, so an administrator can see where a credential
is in use. Nothing else changes; a request without the header works the same.
Changing scopes
You cannot change your own scopes. An administrator at the owning organization does it, through a
surface your key cannot reach. Ask them; do not build a retry around a 403.
Every scope change is recorded on the ledger, with the complete before and after. So is the creation of the key and its revocation. If you need to know why a call that worked last week fails today, that record is the answer, and your administrator can read it.
Revocation
A key is revoked, not deleted. Once revoked it stops authenticating immediately and permanently; there is no un-revoke. Everything it wrote stays on the ledger and stays attributed to it.
Keys do not expire on their own. A key is valid until somebody revokes it — so treat it like a password that never rotates itself, and ask for a new one if you suspect it has leaked.
A machine key that belongs to a connected app (sign-in.md) is rotated by the organization's administrator from the app's page: a new key under a new name is minted and shown once, then the old key is revoked. Revoking the app revokes every key that belongs to it.
Rate limit
300 requests per minute per key by default. The budget is counted per credential, so another integration cannot spend yours and you cannot spend theirs. ReSync may set a different limit for your key — for a large integration, ask your administrator; it is not something a key can change for itself. Whatever your key's limit is, every response says so:
x-ratelimit-limit: 300
x-ratelimit-remaining: 297
x-ratelimit-reset: 59
The window is fixed, not rolling: it starts with your first request and lasts one minute; when it ends, the whole
budget is back. x-ratelimit-reset is the seconds left in the current window.
When you exhaust the budget the answer is 429, with retry-after (seconds left in the window) beside the same
headers. Captured on a key whose own limit is 3, at its fourth request:
x-ratelimit-limit: 3
x-ratelimit-remaining: 0
x-ratelimit-reset: 60
retry-after: 60
From rate-limited-429.json — status 429:
{
"error": "rate_limited",
"message": "rate limit exceeded — 3 requests per 1 minute",
"retryAfterSeconds": 60
}
message reads "…
What a key cannot do yet
Hold a live stream. GET /v1/live — the live-update stream ReSync's web app and device credentials use — refuses
an integration key (device credentials may hold it). Poll instead (events.md).
From live-key-not-supported.json — status 403:
{
"error": "live_key_not_supported",
"message": "Live updates are available to sessions and device credentials only; poll the events read with ?after= instead"
}
Request ids
Every response carries x-request-id.
If you send one, it is echoed back, so you can use your own correlation id end to end:
curl -s -H "Authorization: Bearer $TOKEN" -H "x-request-id: order-sync-8842" "$BASE/v1/resources/<customerId>"
Ids are sanitised: letters, digits, ., _, : and - survive, everything else is stripped, and the
result is truncated to 64 characters. If you send nothing, one is generated for you.
Quote it when you report a problem. It is how a specific request of yours is found in the server logs.