Credential
GET /v1/credential describes the credential presented on the request: who
issued it, which application holds it, what it grants, how that application was
resolved, and when it was issued and expires. It requires no scope — a
credential may always describe itself — but a revoked or expired one is refused
403 like anywhere else, which makes this a liveness check.
curl https://api.dfos.com/v1/credential \
-H 'Authorization: DFOS <request-proof>' \
-H 'X-Credential: <credential>'
{
"subjectDid": "did:dfos:z8zt7ecn9h8n782kae3k796crva2c73",
"clientDid": "did:dfos:7ken2dr4ahc9vtfz3n8ce6ad2kr7h9t",
"scopes": ["read:profile", "read:email", "read:memberships", "read:posts", "write:posts"],
"attenuation": [
{ "resource": "api:api.dfos.com", "action": "read:profile,read:email,read:memberships" },
{
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
"action": "read:posts,write:posts"
}
],
"tier": "jit",
"domain": "fieldnotes.example",
"issuedAt": "2026-09-01T16:20:00.000Z",
"expiresAt": "2026-11-30T16:20:00.000Z"
}
const res = await fetch('https://api.dfos.com/v1/credential', { headers });
if (res.status === 403) return promptUserToReconnect();
const { scopes } = await res.json();
if (!scopes.includes('read:memberships')) return promptUserForMoreAccess();
There is no path parameter: the subject is always the credential that authenticated the request.
attenuation is the grant itself
scopes says what the grant carries; attenuation says what it carries
where, echoed back exactly as the credential was signed. One entry per
resource:
resource | What it covers |
|---|---|
api:<host> | The API itself. For a space-level action that means every space the user belongs to, including ones they join later. |
api:<host>/spaces/<space DID body> | One named space: its did without the did:dfos: prefix. |
A grant narrowed to two spaces carries one entry per space and may carry no
api:<host> entry at all.
Read the grant; do not re-derive it — a user can narrow whatever your app
asked for at consent. Treat an unrecognized resource form as an opaque string
rather than inferring coverage from its shape: which entry admits which request
is specified in API-AUTH, and a route's
403 is the definitive answer.
A null domain means a local application
domain is the bare hostname the grant was issued to, and it is null on the
loopback tier.
tier | What it means | domain |
|---|---|---|
jit | Resolved live from the application's /.well-known/dfos-app.json. Every hosted app is this. | present |
loopback | The key-proven local tier: a client on someone's machine that proved it holds the audience key. | null |
A loopback client — a CLI, an agent, a script — proved a key, not an origin, so
identify the holder by clientDid. tier is an open enum: treat an
unrecognized value as an opaque string.
Fields
| Field | Notes |
|---|---|
subjectDid | Protocol DID of the user who issued the credential — the subject every gated route serves. |
clientDid | Protocol DID of the application the credential was issued to. Canonical when domain is null. |
scopes | The actions this grant carries, in DFOS's canonical order. Authoritative — a route 403s when its action is absent. |
attenuation | The grant as signed: one {resource, action} entry per resource. Says what the grant covers where. |
tier | How the application was resolved at consent time. |
domain | Hostname the grant was issued to, or null for a local application. |
issuedAt | When the user approved the grant (ISO 8601 UTC). |
expiresAt | When the grant lapses on its own (ISO 8601 UTC). A revocation ends it on your next request, long before this date. |
Calling it
The same two headers as every gated route — see
Authentication and
Scopes and credentials.
401 means your proof was wrong, so sign a fresh one and retry. 403 means the
credential is revoked, expired, or not one DFOS issued; ask the user for a new
grant. 413 means the request body exceeded what this endpoint will
authenticate. 429 means the per-grant budget is exhausted; the body names the
scope and a retryAfterMs, and
Rate limits and caching has the budgets. 503
means verification could not complete; retry with backoff.
Cache-Control: no-store, never shared-cached: the response names a specific
user and a specific grant.