API keys and audit log
API keys and the audit log.
The ApiKey object
Attributes
idrequired Type:stringdisplay_prefixrequired Type:stringThe key's first characters, to recognise it.
namerequired Type:stringpermissionsrequired Type:Permission[]created_atrequired Type:stringlast_used_atrequired Type:stringnullrevoked_atrequired Type:stringnullcreated_byoptional Type:stringnullWho made the key, e.g.
user:<id>.owneroptional Type:KeyOrgThe sub-organisation that owns a key you issued for it.
issued_byoptional Type:KeyOrgThe parent organisation that issued this key for you.
List API keys
GET /keys
Needs org:keys:manage: Create/revoke API keys (within own permissions)
Your keys, and the keys you issued for your sub-organisations, each with its owner.
Parameters
org_idqueryoptional Type:stringOnly the keys owned by this organisation (yours, or a sub-organisation's you issued keys for).
Returns
curl "https://api.unforged.sh/keys" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/keys",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/keys', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Issue an API key
POST /keys
Needs org:keys:manage: Create/revoke API keys (within own permissions)
The key is shown once. No Idempotency-Key: a retry issues another key.
Request body
namerequired Type:stringpermissionsrequired Type:Permission[]The key's permissions, at most your own.
org_idoptional Type:stringA sub-organisation that will own the key, so requests made with it run there with no
Unforged-Orgheader. Defaults to your own organisation. Needsorg:children:access, isn't available while acting in another organisation, and the key can't holdorg:keys:manage,org:billing:manageororg:children:manage.
Returns
201Type:ApiKeyCreatedCreated400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized402Type:Problembilling_required403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/keys" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{
"name": "backend",
"permissions": [
"nib:checks:create",
"nib:checks:read",
"nib:subjects:read"
]
}' import requests
r = requests.post(
"https://api.unforged.sh/keys",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={
"name": "backend",
"permissions": [
"nib:checks:create",
"nib:checks:read",
"nib:subjects:read"
]
},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/keys', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
body: JSON.stringify({
"name": "backend",
"permissions": [
"nib:checks:create",
"nib:checks:read",
"nib:subjects:read"
]
}),
});
console.log(await res.json()); {
"id": "key_01j9x3m8v7k2q4r5s6t7u8v9w0",
"display_prefix": "unf_live_01AbCdEf",
"name": "backend",
"permissions": [
"nib:checks:create",
"nib:checks:read",
"nib:subjects:read"
],
"created_at": "2026-09-25T10:15:30.123Z",
"last_used_at": null,
"revoked_at": null,
"key": "unf_live_01AbCdEf…EXAMPLE"
} Revoke an API key
DELETE /keys/{key_id}
Needs org:keys:manage: Create/revoke API keys (within own permissions)
Takes effect on the next request; 204 also when already revoked. 409 conflict for the last key with org:keys:manage.
Parameters
key_idpathrequired Type:string
Returns
204Done401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X DELETE "https://api.unforged.sh/keys/{key_id}" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.delete(
"https://api.unforged.sh/keys/{key_id}",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/keys/{key_id}', {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); The audit log, newest first
GET /audit
Needs org:audit:read: Audit log
Team and invitation changes are recorded with these actions (addresses are stored hashed in detail, never in clear): team.invite, team.invite_resend, team.invite_revoke, team.invite_accepted, team.invite_declined, team.role_change, team.member_remove, team.domain_add, team.domain_verify, team.domain_set, team.domain_remove, team.domain_join and team.admin_portal. Filter by one of these exact names with action=<name>.
Parameters
cursorqueryoptional Type:stringThe previous page's
next_cursor.limitqueryoptional Type:integeractionqueryoptional Type:stringAn exact action, or a prefix ending in
*.
Returns
200Type:AuditPageA page of entries400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/audit" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/audit",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/audit', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); {
"entries": [
{
"id": 42,
"actor": "api_key:key_01j9x3m8v7k2q4r5s6t7u8v9w0",
"action": "policy.create",
"target": "strict@2",
"detail": {},
"at": "2026-09-25T10:15:30.123Z"
}
],
"next_cursor": "42"
}