People
Accounts and signing in
auth-service
Signing up, signing in and out, and the OAuth 2.0 and OpenID Connect server every app signs in through (oauth.md); what proves who somebody is (passwords, codes, passkeys, two-factor authentication) and changing it; invitations and the approval queue; and the installation's description of itself (NodeInfo).
The keys tokens are signed with
/.well-known/jwks.jsonThe public halves, as a JSON Web Key Set (RFC 7517): what every service checks a token's signature with.
Who: Anyone, signed in or not
Answers
- 200OKauth.JWKSet application/json
Request
curl "https://api.example.org/.well-known/jwks.json"
const response = await fetch('https://api.example.org/.well-known/jwks.json', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/.well-known/jwks.json",
)
answer = response.json()
Answer · 200
{
"keys": [
{
"key": null
}
]
}
Where the NodeInfo document is
/.well-known/nodeinfoWho: Anyone, signed in or not
Answers
- 200OKauth.NodeInfoLinks application/json
Request
curl "https://api.example.org/.well-known/nodeinfo"
const response = await fetch('https://api.example.org/.well-known/nodeinfo', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/.well-known/nodeinfo",
)
answer = response.json()
Answer · 200
{
"links": [
{
"href": "string",
"rel": "string"
}
]
}
The OpenID Connect discovery document
/.well-known/openid-configurationWhere each endpoint is, and what is supported (OpenID Connect Discovery 1.0).
Who: Anyone, signed in or not
Answers
- 200OKauth.OpenIDConfiguration application/json
Request
curl "https://api.example.org/.well-known/openid-configuration"
const response = await fetch('https://api.example.org/.well-known/openid-configuration', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/.well-known/openid-configuration",
)
answer = response.json()
Answer · 200
{
"authorization_endpoint": "string",
"claims_supported": [
"string"
],
"code_challenge_methods_supported": [
"string"
],
"grant_types_supported": [
"string"
],
"id_token_signing_alg_values_supported": [
"string"
],
"introspection_endpoint": "string",
"issuer": "string",
"jwks_uri": "https://example.org/",
"response_types_supported": [
"string"
],
"revocation_endpoint": "string",
"scopes_supported": [
"string"
],
"subject_types_supported": [
"string"
],
"token_endpoint": "string",
"token_endpoint_auth_methods_supported": [
"string"
],
"userinfo_endpoint": "string"
}
People, groups, events or websites, for cleaning up a site
/accountsLeast recently active first, by default; purged ones are not listed. Removing them is the purge (DELETE /people, DELETE /groups, account-lifecycle.md).
Who: A super administrator, with actors:read
Parameters
kindstringin queryperson, the default,group,eventorwebsite. One ofperson,group,event,website.qstringin queryWords in the name or the username. At most 100 characters.tierstringin queryempty,dormantoractive. One ofempty,dormant,active.statestringin queryactive,disabled,archivedordeleted. One ofactive,disabled,archived,deleted.last_active_beforestringin queryDates, as YYYY-MM-DD. An account matches if it was last active, or created, before the start of that day.created_beforestringin queryemail_verifiedstringin queryfalsefor people who never confirmed their address. One offalse.onboardedstringin queryfalsefor people who never went through the walk-through after signing up. One offalse.has_adminstringin queryfalsefor groups with no administrator. One offalse.content_maxintegerin queryContentMax is a pointer so that 0, "has written nothing", can be told from not asking. At least 0.sortstringin querylast_active_at, the default,created_atorname. One oflast_active_at,created_at,name.dirstringin queryasc, the default, ordesc. One ofasc,desc.limitintegerin queryHow many on a page: 50 unless said. At least 0. At most 500.startintegerin queryHow many to skip. At least 0.
Answers
- 200OKobject application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the viewer may not do this
Request
curl "https://api.example.org/accounts" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/accounts', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/accounts",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"admin_count": 1,
"alias": "string",
"content_count": 1,
"created_at": "2026-10-27T09:30:00Z",
"deleted_at": "2026-10-27T09:30:00Z",
"email_verified": false,
"follower_count": 1,
"id": 42,
"kind": "string",
"last_active_at": "2026-10-27T09:30:00Z",
"leader_count": 1,
"name": "string",
"onboarded": false,
"person_type": "string",
"state": "string",
"tier": "string"
}
],
"pagination": {
"limit": 1,
"offset": 1,
"total": 1
}
}
Change a person's type
/accounts/{id}/person-typeRegistered, administrator or super administrator. An administrator may make somebody registered an administrator; making a super administrator, and taking a type away, are a super administrator's. Nobody changes their own, so the last super administrator cannot be demoted: one who wants to step down makes somebody else a super administrator first. Recorded with who did it (made-registered, made-admin, made-super-admin). It takes effect at the person's next token, within fifteen minutes.
Who: An administrator, with actors:write, who has just proved who they are again
Parameters
idintegerrequiredin pathThe person's id.
Body application/json
person_typestringrequiredregistered, administrator or super-administrator. One ofregistered,administrator,super-administrator.
Answers
- 200the type they have now and had before;
changedis false when they had it already, and no proof was neededauth.PersonTypeChanged application/json - 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth);person_type_not_allowed: an administrator asked for what is a super administrator's; or, without a body, the viewer is not an administratorRefusal application/json - 404
not_a_person: no person here has that id; a group, a profile from another server and a deleted person have no type to changeRefusal application/json - 409
own_person_type: it is the viewer's ownRefusal application/json
Request
curl -X PATCH "https://api.example.org/accounts/<id>/person-type" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"person_type": "registered"
}'
const response = await fetch('https://api.example.org/accounts/<id>/person-type', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"person_type": "registered"
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/accounts/<id>/person-type",
headers={"Authorization": f"Bearer {token}"},
json={
"person_type": "registered"
},
)
answer = response.json()
Answer · 200
{
"changed": false,
"id": 42,
"person_type": "string",
"previous": "string"
}
Make everything public registered-only
/accounts/make-registeredEvery public profile, group and post, for an installation that has set MIN_CONTENT_ACCESS=registered. It cannot be undone: the number is typed to confirm it.
Who: A super administrator, with actors:write, who has just proved who they are again
Body application/json
confirmstringrequiredThe number of things that will change, typed out: "CONVERT 8675". At most 30 characters.
Answers
- 200what was changedauth.PublicContent application/json
- 400
confirmation_mismatch: the number is not how much is public now, which it says; or, without a body, the request is not well formedauth.ConfirmationMismatch application/json - 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth); or, without a body, the viewer may notRefusal application/json - 409
public_content_allowed: new content may still be public hereRefusal application/json
Request
curl -X POST "https://api.example.org/accounts/make-registered" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"confirm": "string"
}'
const response = await fetch('https://api.example.org/accounts/make-registered', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"confirm": "string"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/accounts/make-registered",
headers={"Authorization": f"Bearer {token}"},
json={
"confirm": "string"
},
)
answer = response.json()
Answer · 200
{
"groups": 1,
"people": 1,
"posts": 1,
"public_allowed": false,
"total": 1
}
How much is public
/accounts/public-contentWhat making it all registered-only would change.
Who: A super administrator, with actors:read
Answers
- 200OKauth.PublicContent application/json
- 401no token, or one that has expired
- 403the viewer may not do this
Request
curl "https://api.example.org/accounts/public-content" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/accounts/public-content', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/accounts/public-content",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"groups": 1,
"people": 1,
"posts": 1,
"public_allowed": false,
"total": 1
}
What somebody has accepted
/agreements/{person_id}Their own, or anybody's, for an administrator.
Who: Somebody signed in, with account:read
Parameters
person_idintegerrequiredin pathThe person's id.
Answers
- 200OKobject application/json
- 400the id is not a number
- 401no token, or one that has expired
- 403it is somebody else, and the viewer is not an administrator
- 404there is no such person
Request
curl "https://api.example.org/agreements/<person_id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/agreements/<person_id>', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/agreements/<person_id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": {
"privacy": {
"accepted": false,
"accepted_at": "2026-10-27T09:30:00Z",
"version": "string"
},
"rules": {
"accepted": false,
"accepted_at": "2026-10-27T09:30:00Z",
"version": "string"
},
"tos": {
"accepted": false,
"accepted_at": "2026-10-27T09:30:00Z",
"version": "string"
}
}
}
Accept the Privacy Policy
/agreements/privacyThe viewer's own acceptance, of the version the web app shows.
Who: Somebody signed in
Body application/json or application/x-www-form-urlencoded or multipart/form-data
acceptedbooleanTrue.versionstringrequiredThe version accepted:1.0. At most 20 characters.
Answers
- 200OKauth.Accepted application/json
- 400
version_malformed: the version is not a dotted number;not_accepted:acceptedis false; or, without a body, the request is not well formedRefusal application/json - 401no token, or one that has expired
Request
curl -X PATCH "https://api.example.org/agreements/privacy" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accepted": false,
"version": "string"
}'
const response = await fetch('https://api.example.org/agreements/privacy', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"accepted": false,
"version": "string"
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/agreements/privacy",
headers={"Authorization": f"Bearer {token}"},
json={
"accepted": False,
"version": "string"
},
)
answer = response.json()
Answer · 200
{
"accepted": false,
"version": "string"
}
Agree to the site's rules
/agreements/rulesThe viewer's own agreement, each rule ticked, of the version GET /site has now (roadmap row 102).
Who: Somebody signed in
Body application/json or application/x-www-form-urlencoded or multipart/form-data
acceptedbooleanTrue.versionstringrequiredThe version accepted:1.0. At most 20 characters.
Answers
- 200OKauth.Accepted application/json
- 400
version_malformed: the version is not a dotted number;not_accepted:acceptedis false; or, without a body, the request is not well formedRefusal application/json - 401no token, or one that has expired
- 409
version_not_current: the site's rules are at another version nowRefusal application/json
Request
curl -X PATCH "https://api.example.org/agreements/rules" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accepted": false,
"version": "string"
}'
const response = await fetch('https://api.example.org/agreements/rules', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"accepted": false,
"version": "string"
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/agreements/rules",
headers={"Authorization": f"Bearer {token}"},
json={
"accepted": False,
"version": "string"
},
)
answer = response.json()
Answer · 200
{
"accepted": false,
"version": "string"
}
Accept the Terms of Service
/agreements/tosThe viewer's own acceptance, of the version the web app shows.
Who: Somebody signed in
Body application/json or application/x-www-form-urlencoded or multipart/form-data
acceptedbooleanTrue.versionstringrequiredThe version accepted:1.0. At most 20 characters.
Answers
- 200OKauth.Accepted application/json
- 400
version_malformed: the version is not a dotted number;not_accepted:acceptedis false; or, without a body, the request is not well formedRefusal application/json - 401no token, or one that has expired
Request
curl -X PATCH "https://api.example.org/agreements/tos" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accepted": false,
"version": "string"
}'
const response = await fetch('https://api.example.org/agreements/tos', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"accepted": false,
"version": "string"
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/agreements/tos",
headers={"Authorization": f"Bearer {token}"},
json={
"accepted": False,
"version": "string"
},
)
answer = response.json()
Answer · 200
{
"accepted": false,
"version": "string"
}
Where somebody is signed in
/auth-logs/{id}Each session: the device, roughly where, when it began, whether it is still live, and which is the viewer's own. Their own, or anybody's, for an administrator.
Who: Somebody signed in, with account:read
Parameters
idintegerrequiredin pathThe person's id.
Answers
- 200OKlist of auth.AuthLogItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403it is somebody else, and the viewer is not an administrator
- 404there is no such person
Request
curl "https://api.example.org/auth-logs/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/auth-logs/<id>', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/auth-logs/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
[
{
"city": "string",
"client_id": "string",
"country": "string",
"created_at": "2026-10-27T09:30:00Z",
"device": "string",
"id": 42,
"is_active": false,
"is_viewer": false,
"name": "string",
"os": "string"
}
]
Sign a session out
/auth-logs/{uid}/{id}Somebody whose session an administrator ended is told by mail.
Who: Somebody signed in, with account:write
Parameters
uidintegerrequiredin pathThe person's id.idintegerrequiredin pathThe session's id.
Answers
- 200signed out
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403it is somebody else's, and the viewer is not an administrator
- 404there is no such person, or no such session of theirs
Request
curl -X DELETE "https://api.example.org/auth-logs/<uid>/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/auth-logs/<uid>/<id>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/auth-logs/<uid>/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Whether an email address has an account
/auth/is/emailFor the forms that ask for one.
Who: Anyone, signed in or not
Parameters
emailstring (email)requiredin queryThe address. At least 10 characters. At most 100 characters.
Answers
- 200it has one
- 400the request is not well formed, or a value in it is not one it may be
- 404it has none
Request
curl "https://api.example.org/auth/is/email?email=<email>"
const response = await fetch('https://api.example.org/auth/is/email?email=<email>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/auth/is/email?email=<email>",
)
answer = response.json()
Whether an account has two-factor authentication
/auth/is/totpFor the sign-in page, before there is a session.
Who: Anyone, signed in or not
Parameters
emailstring (email)requiredin queryThe address. At least 10 characters. At most 100 characters.
Answers
- 200it has
- 400the request is not well formed, or a value in it is not one it may be
- 404it has not, or there is no such account
Request
curl "https://api.example.org/auth/is/totp?email=<email>"
const response = await fetch('https://api.example.org/auth/is/totp?email=<email>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/auth/is/totp?email=<email>",
)
answer = response.json()
Whether a username is taken
/auth/is/usernameBy a person, a group or an event: a username is the address of a profile, and anybody may see it. The signup form asks as the field is left.
Who: Anyone, signed in or not
Parameters
usernamestringrequiredin queryThe username. At least 3 characters. At most 30 characters.
Answers
- 200it is taken
- 400the request is not well formed, or a value in it is not one it may be
- 404it is free
Request
curl "https://api.example.org/auth/is/username?username=<username>"
const response = await fetch('https://api.example.org/auth/is/username?username=<username>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/auth/is/username?username=<username>",
)
answer = response.json()
The apps that may sign people in
/clientsEvery one, without its secret: a secret is shown once, when it is made.
Who: A super administrator, with oauth:read
Answers
- 200OKobject application/json
- 401no token, or one that has expired
- 403the viewer may not do this
Request
curl "https://api.example.org/clients" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/clients', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/clients",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"active": false,
"client_id": "string",
"confidential": false,
"created_at": "2026-10-27T09:30:00Z",
"first_party": false,
"grant_types": [
"string"
],
"id": 42,
"name": "string",
"redirect_uris": [
"string"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 1
}
]
}
Register an app
/clientsA confidential one is answered with its secret, this once.
Who: A super administrator, with oauth:write, who has just proved who they are again
Body application/json
activebooleanWhether it may sign people in.client_idstringrequiredWhat it calls itself when it signs people in: lower-case letters, digits,_and-. At least 2 characters. At most 64 characters.confidentialbooleanWhether it can keep a secret: an app's own server can, an app in a browser or on a phone cannot.first_partybooleanWhether it is one of the installation's own apps.grant_typeslist of stringrequiredHow it may get tokens:authorization_code,refresh_token,client_credentials. At least 1 of them.namestringrequiredWhat people are shown it is called. At least 3 characters. At most 128 characters.redirect_urislist of string (uri)requiredWhere people may be sent back to after signing in. At least 1 of them.scopeslist of stringrequiredWhat it may ask to do (oauth.md).skip_consentbooleanWhether people are not asked to allow it.token_expiryintegerHow long its access tokens last, in seconds: 300 to 86400. At least 300. At most 86400.
Answers
- 201with
client_secretwhen it is confidentialauth.OAuthClientCreatedItem application/json - 400
invalid_scope: a scope that does not exist; or, without a body, the request is not well formedRefusal application/json - 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth)Refusal application/json
Request
curl -X POST "https://api.example.org/clients" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"active": false,
"client_id": "string",
"confidential": false,
"first_party": false,
"grant_types": [
"authorization_code"
],
"name": "string",
"redirect_uris": [
"https://example.org/"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 300
}'
const response = await fetch('https://api.example.org/clients', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"active": false,
"client_id": "string",
"confidential": false,
"first_party": false,
"grant_types": [
"authorization_code"
],
"name": "string",
"redirect_uris": [
"https://example.org/"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 300
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/clients",
headers={"Authorization": f"Bearer {token}"},
json={
"active": False,
"client_id": "string",
"confidential": False,
"first_party": False,
"grant_types": [
"authorization_code"
],
"name": "string",
"redirect_uris": [
"https://example.org/"
],
"scopes": [
"string"
],
"skip_consent": False,
"token_expiry": 300
},
)
answer = response.json()
Answer · 201
{
"active": false,
"client_id": "string",
"client_secret": "string",
"confidential": false,
"created_at": "2026-10-27T09:30:00Z",
"first_party": false,
"grant_types": [
"string"
],
"id": 42,
"name": "string",
"redirect_uris": [
"string"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 1
}
One app
/clients/{id}Who: A super administrator, with oauth:read
Parameters
idintegerrequiredin pathThe app's id.
Answers
- 200OKauth.OAuthClientItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the viewer may not do this
- 404there is no such app
Request
curl "https://api.example.org/clients/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/clients/<id>', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/clients/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"active": false,
"client_id": "string",
"confidential": false,
"created_at": "2026-10-27T09:30:00Z",
"first_party": false,
"grant_types": [
"string"
],
"id": 42,
"name": "string",
"redirect_uris": [
"string"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 1
}
Change an app
/clients/{id}Everything but its id and secret: a new secret is rotate-secret.
Who: A super administrator, with oauth:write, who has just proved who they are again
Parameters
idintegerrequiredin pathThe app's id.
Body application/json
activebooleanWhether it may sign people in.confidentialbooleanWhether it can keep a secret: an app's own server can, an app in a browser or on a phone cannot.first_partybooleanWhether it is one of the installation's own apps.grant_typeslist of stringrequiredHow it may get tokens:authorization_code,refresh_token,client_credentials. At least 1 of them.namestringrequiredWhat people are shown it is called. At least 3 characters. At most 128 characters.redirect_urislist of string (uri)requiredWhere people may be sent back to after signing in. At least 1 of them.scopeslist of stringrequiredWhat it may ask to do (oauth.md).skip_consentbooleanWhether people are not asked to allow it.token_expiryintegerHow long its access tokens last, in seconds: 300 to 86400. At least 300. At most 86400.
Answers
- 200OKauth.OAuthClientItem application/json
- 400
invalid_scope: a scope that does not exist; or, without a body, the request is not well formedRefusal application/json - 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth)Refusal application/json - 404there is no such app
Request
curl -X PATCH "https://api.example.org/clients/<id>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"active": false,
"confidential": false,
"first_party": false,
"grant_types": [
"authorization_code"
],
"name": "string",
"redirect_uris": [
"https://example.org/"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 300
}'
const response = await fetch('https://api.example.org/clients/<id>', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"active": false,
"confidential": false,
"first_party": false,
"grant_types": [
"authorization_code"
],
"name": "string",
"redirect_uris": [
"https://example.org/"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 300
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/clients/<id>",
headers={"Authorization": f"Bearer {token}"},
json={
"active": False,
"confidential": False,
"first_party": False,
"grant_types": [
"authorization_code"
],
"name": "string",
"redirect_uris": [
"https://example.org/"
],
"scopes": [
"string"
],
"skip_consent": False,
"token_expiry": 300
},
)
answer = response.json()
Answer · 200
{
"active": false,
"client_id": "string",
"confidential": false,
"created_at": "2026-10-27T09:30:00Z",
"first_party": false,
"grant_types": [
"string"
],
"id": 42,
"name": "string",
"redirect_uris": [
"string"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 1
}
Remove an app
/clients/{id}Its tokens stop working.
Who: A super administrator, with oauth:write, who has just proved who they are again
Parameters
idintegerrequiredin pathThe app's id.
Answers
- 200the app as it wasauth.OAuthClientItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth)Refusal application/json - 404there is no such app
Request
curl -X DELETE "https://api.example.org/clients/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/clients/<id>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/clients/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"active": false,
"client_id": "string",
"confidential": false,
"created_at": "2026-10-27T09:30:00Z",
"first_party": false,
"grant_types": [
"string"
],
"id": 42,
"name": "string",
"redirect_uris": [
"string"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 1
}
Give an app a new secret
/clients/{id}/rotate-secretThe old one stops working, and so do the tokens issued with it.
Who: A super administrator, with oauth:write, who has just proved who they are again
Parameters
idintegerrequiredin pathThe app's id.
Answers
- 200with the new
client_secret, this onceauth.OAuthClientCreatedItem application/json - 400
public_client: the app is public and has no secret; or, without a body, the request is not well formedRefusal application/json - 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth)Refusal application/json - 404there is no such app
Request
curl -X POST "https://api.example.org/clients/<id>/rotate-secret" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/clients/<id>/rotate-secret', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/clients/<id>/rotate-secret",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"active": false,
"client_id": "string",
"client_secret": "string",
"confidential": false,
"created_at": "2026-10-27T09:30:00Z",
"first_party": false,
"grant_types": [
"string"
],
"id": 42,
"name": "string",
"redirect_uris": [
"string"
],
"scopes": [
"string"
],
"skip_consent": false,
"token_expiry": 1
}
Begin changing the viewer's email address
/email/changeMails a code to the address the account has now: that inbox approves the move, so a stolen session alone cannot.
Who: Somebody signed in, who has just proved who they are again
Body application/json
emailstring (email)requiredThe new address. At least 10 characters. At most 100 characters.
Answers
- 204the code is sent, or the address is the one the account has
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth)Refusal application/json - 409another account has that address
Request
curl -X PATCH "https://api.example.org/email/change" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "picard@example.org"
}'
const response = await fetch('https://api.example.org/email/change', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"email": "picard@example.org"
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/email/change",
headers={"Authorization": f"Bearer {token}"},
json={
"email": "picard@example.org"
},
)
answer = response.json()
Change the viewer's email address
/email/change/confirmWith the code mailed to the old address. Every session is signed out, this one too.
Who: Somebody signed in
Body application/json
codestringThe six digits mailed to the old address.
Answers
- 204changed
- 400it is not six digits; nothing was counted
- 401no token, or one that has expired
- 409another account has taken the address since
- 410no change is waiting, or its code ran out, was replaced or had too many wrong tries: start again
- 422the code is wrong; it says how many tries are leftauth.CodeMismatch application/json
Request
curl -X POST "https://api.example.org/email/change/confirm" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "string"
}'
const response = await fetch('https://api.example.org/email/change/confirm', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"code": "string"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/email/change/confirm",
headers={"Authorization": f"Bearer {token}"},
json={
"code": "string"
},
)
answer = response.json()
The invitations the viewer has sent
/invitesTheir own only, administrators' too: an invitation carries somebody's address. With how many are waiting; how many may is the viewer's entitlement, in userinfo.
Who: Somebody signed in, with actors:read
Parameters
startintegerin queryHow many to skip. At least 0.limitintegerin queryHow many on a page. At least 0. At most 100.10unless given.qstringin queryWords to look for, where a list looks. At most 100 characters.orderingstringin queryWhat to sort by, where a list sorts. At most 32 characters.
Answers
- 200OKauth.InviteList application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the viewer may not invite
Request
curl "https://api.example.org/invites" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/invites', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/invites",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"author": null,
"created_at": "2026-10-27T09:30:00Z",
"id": 42,
"recipient_email": "string",
"status": "string"
}
],
"pagination": {
"limit": 1,
"offset": 1,
"total": 1
},
"pending": 1
}
Invite somebody
/invitesMails them a link. Who may invite is the installation's choice (INVITES_FROM), and so is how many may wait at once (INVITE_PENDING_MAX).
Who: Somebody signed in, with actors:write
Body application/json
emailstring (email)requiredWho to invite. At least 10 characters. At most 100 characters.
Answers
- 200OKauth.InviteItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
invitations_closed: the site takes no one new; or, without a body, the viewer may not inviteRefusal application/json - 409
too_many_invitations: the viewer has as many waiting as they may, and it says how one frees; or, without a body, the address has an accountRefusal application/json - 429too many invitations, from the viewer or to the address, for now
Request
curl -X POST "https://api.example.org/invites" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "picard@example.org"
}'
const response = await fetch('https://api.example.org/invites', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"email": "picard@example.org"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/invites",
headers={"Authorization": f"Bearer {token}"},
json={
"email": "picard@example.org"
},
)
answer = response.json()
Answer · 200
{
"author": {
"alias": "string",
"avatar_urls": {
"key": null
},
"id": 42,
"name": "string",
"type": "string"
},
"created_at": "2026-10-27T09:30:00Z",
"id": 42,
"recipient_email": "string",
"status": "string"
}
Take an invitation back
/invites/{id}Its link stops working, and the person invited is told.
Who: Somebody signed in, with actors:write
Parameters
idintegerrequiredin pathThe invitation's id.
Answers
- 200the invitation as it wasauth.InviteItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403it is somebody else's, and the viewer is not an administrator
- 404there is no such invitation
Request
curl -X DELETE "https://api.example.org/invites/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/invites/<id>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/invites/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"author": {
"alias": "string",
"avatar_urls": {
"key": null
},
"id": 42,
"name": "string",
"type": "string"
},
"created_at": "2026-10-27T09:30:00Z",
"id": 42,
"recipient_email": "string",
"status": "string"
}
The page an invitation's link opens
/invites/acceptIt asks for a username, a name and a password.
Who: Anyone, signed in or not
Parameters
tokenstringin queryFrom the link: 128 characters.
Answers
- 200the page, or one that says why the link does not workstring text/html
Request
curl "https://api.example.org/invites/accept"
const response = await fetch('https://api.example.org/invites/accept', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/invites/accept",
)
answer = response.json()
Accept an invitation
/invites/acceptMakes the account; where registration is by approval, asks for it instead.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
namestringOptional, and offered rather than required. At most 64 characters.passwordstringrequiredAt least 15 characters. At least 15 characters. At most 150 characters.tokenstringrequiredFrom the link. At least 128 characters. At most 128 characters.usernamestringrequiredThe username, chosen rather than derived. At least 3 characters. At most 30 characters.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303on to the web app, signed in
Request
curl -X POST "https://api.example.org/invites/accept" \
--data-urlencode "name=string" \
--data-urlencode "password=string" \
--data-urlencode "token=string" \
--data-urlencode "username=string"
const form = new URLSearchParams();
form.append('name', "string");
form.append('password', "string");
form.append('token', "string");
form.append('username', "string");
const response = await fetch('https://api.example.org/invites/accept', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/invites/accept",
data={"name": "string", "password": "string", "token": "string", "username": "string"},
)
answer = response.json()
What this installation is (NodeInfo 2.1)
/nodeinfo/2.1The software, the protocols it speaks, whether it takes registrations, and how many people use it. Its names are NodeInfo's, in camelCase.
Who: Anyone, signed in or not
Answers
- 200OKauth.NodeInfoDocument application/json
Request
curl "https://api.example.org/nodeinfo/2.1"
const response = await fetch('https://api.example.org/nodeinfo/2.1', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/nodeinfo/2.1",
)
answer = response.json()
Answer · 200
{
"metadata": {
"key": null
},
"openRegistrations": false,
"protocols": [
"string"
],
"services": {
"inbound": [
"string"
],
"outbound": [
"string"
]
},
"software": {
"name": "string",
"repository": "string",
"version": "string"
},
"usage": {
"localPosts": 1,
"users": {
"activeHalfyear": null,
"activeMonth": null,
"total": null
}
},
"version": "string"
}
The keys tokens are signed with
/oauth-signing-keysEvery one, live or not, newest first.
Who: A super administrator, with oauth:read
Answers
- 200OKobject application/json
- 401no token, or one that has expired
- 403
insufficient_scope: the token has nooauth:read;forbidden: the viewer is not a super administratorRefusal application/json
Request
curl "https://api.example.org/oauth-signing-keys" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/oauth-signing-keys', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth-signing-keys",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"active": false,
"algorithm": "string",
"backend": "string",
"created_at": "2026-10-27T09:30:00Z",
"expires_at": "2026-10-27T09:30:00Z",
"id": 42,
"key_id": "string"
}
]
}
One signing key
/oauth-signing-keys/{kid}Who: A super administrator, with oauth:read
Parameters
kidstringrequiredin pathThe key'skid.
Answers
- 200OKauth.OAuthSigningKeyItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
insufficient_scope: the token has nooauth:read;forbidden: the viewer is not a super administratorRefusal application/json - 404there is no such key
Request
curl "https://api.example.org/oauth-signing-keys/<kid>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/oauth-signing-keys/<kid>', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth-signing-keys/<kid>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"active": false,
"algorithm": "string",
"backend": "string",
"created_at": "2026-10-27T09:30:00Z",
"expires_at": "2026-10-27T09:30:00Z",
"id": 42,
"key_id": "string"
}
Sign with a key, or stop
/oauth-signing-keys/{kid}A key no longer signed with still checks the tokens it signed.
Who: A super administrator, with oauth:write, who has just proved who they are again
Parameters
kidstringrequiredin pathThe key'skid.
Body application/json
activebooleanrequiredWhether to sign new tokens with it.
Answers
- 200OKauth.OAuthSigningKeyItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth);insufficient_scope: the token has nooauth:write;forbidden: the viewer is not a super administratorRefusal application/json - 404there is no such key
- 409
last_active_key: it is the only key signed with, and nothing could be signed without itRefusal application/json
Request
curl -X PATCH "https://api.example.org/oauth-signing-keys/<kid>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"active": false
}'
const response = await fetch('https://api.example.org/oauth-signing-keys/<kid>', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"active": false
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/oauth-signing-keys/<kid>",
headers={"Authorization": f"Bearer {token}"},
json={
"active": False
},
)
answer = response.json()
Answer · 200
{
"active": false,
"algorithm": "string",
"backend": "string",
"created_at": "2026-10-27T09:30:00Z",
"expires_at": "2026-10-27T09:30:00Z",
"id": 42,
"key_id": "string"
}
Ask to act for somebody (OAuth 2.0)
/oauth/authorizeThe authorization endpoint (RFC 6749, 4.1.1), with PKCE (RFC 7636). The person signs in first if they have to, and is asked whether to allow it unless the app is the installation's own.
Who: Anyone, signed in or not
Parameters
response_typestringrequiredin querycode. One ofcode.client_idstringrequiredin queryThe app'sclient_id.redirect_uristring (uri)requiredin queryRedirectURI uses theuritag, noturl. Theurltag in go-playground/validator rejects any URI whose scheme isn't http/https — but RFC 8252 (OAuth 2.0 for Native Apps) explicitly recommends custom schemes for desktop and mobile apps because they route the callback to the registered app via the OS rather than through a browser. Theuritag accepts any valid URI with a scheme, which is the right syntactic check here.scopestringrequiredin queryWhat the app asks to do, separated by spaces (oauth.md).statestringrequiredin queryAnything; it comes back unchanged with the code, for the app to check.code_challengestringin queryPKCE (RFC 7636): the hash of a secret only the app knows, given again with the code. At least 43 characters. At most 128 characters.code_challenge_methodstringin queryS256orplain. One ofS256,plain.noncestringin queryAnything; it is put in the ID token (OpenID Connect).
Answers
- 200a page that says why it cannot go on: no such app, or a redirect address it has not registered; or, for an app on a device, the page that hands it the codestring text/html
- 302back to the app's redirect address with
codeandstate, or witherroranderror_description; or on to sign in, or to the consent page - 400the request is not well formed, or a value in it is not one it may be
Request
curl "https://api.example.org/oauth/authorize?response_type=<response_type>&client_id=<client_id>&redirect_uri=<redirect_uri>&scope=<scope>&state=<state>"
const response = await fetch('https://api.example.org/oauth/authorize?response_type=<response_type>&client_id=<client_id>&redirect_uri=<redirect_uri>&scope=<scope>&state=<state>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/authorize?response_type=<response_type>&client_id=<client_id>&redirect_uri=<redirect_uri>&scope=<scope>&state=<state>",
)
answer = response.json()
The page that asks whether to allow an app
/oauth/consentWho: Anyone, signed in or not
Answers
- 200the page, or one that says why it cannot go onstring text/html
- 302on to sign in, when the browser is not signed in
Request
curl "https://api.example.org/oauth/consent"
const response = await fetch('https://api.example.org/oauth/consent', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/consent",
)
answer = response.json()
Allow an app, or not
/oauth/consentWho: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
approvedbooleanWhether the person allows it.csrf_tokenstringrequiredThe token the page was given, which proves the form is this site's.
Answers
- 200a page that says why it cannot go on; or, for an app on a device, the page that hands it the codestring text/html
- 302back to the app's redirect address with
codeandstate, or witherror=access_denied; or on to sign in
Request
curl -X POST "https://api.example.org/oauth/consent" \
--data-urlencode "approved=false" \
--data-urlencode "csrf_token=string"
const form = new URLSearchParams();
form.append('approved', "false");
form.append('csrf_token', "string");
const response = await fetch('https://api.example.org/oauth/consent', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/consent",
data={"approved": "false", "csrf_token": "string"},
)
answer = response.json()
What a token is (OAuth 2.0)
/oauth/introspectRFC 7662: whether it is live, whose it is, and what it may do.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
client_idstringThe app'sclient_id, unless it is in anAuthorization: Basicheader.client_secretstringThe app's secret, likewise; a public app has none.tokenstringrequiredThe token.token_type_hintstringaccess_tokenorrefresh_token, to look for it as first. One ofaccess_token,refresh_token.
Answers
- 200OKauth.OAuthIntrospectItem application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401
invalid_client: the app did not prove which it isauth.OAuthErrorResponse application/json
Request
curl -X POST "https://api.example.org/oauth/introspect" \
--data-urlencode "client_id=string" \
--data-urlencode "client_secret=string" \
--data-urlencode "token=string" \
--data-urlencode "token_type_hint=access_token"
const form = new URLSearchParams();
form.append('client_id', "string");
form.append('client_secret', "string");
form.append('token', "string");
form.append('token_type_hint', "access_token");
const response = await fetch('https://api.example.org/oauth/introspect', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/introspect",
data={"client_id": "string", "client_secret": "string", "token": "string", "token_type_hint": "access_token"},
)
answer = response.json()
Answer · 200
{
"active": false,
"client_id": "string",
"exp": 1,
"iat": 1,
"iss": "string",
"jti": "string",
"scope": "string",
"sub": "string"
}
The sign-in page
/oauth/loginWho: Anyone, signed in or not
Parameters
errorstringin queryWhy the page is shown again:session_expiredortoo_many_attempts.
Answers
- 200the pagestring text/html
- 302on, when the browser is signed in already
- 400the request is not well formed, or a value in it is not one it may be
Request
curl "https://api.example.org/oauth/login"
const response = await fetch('https://api.example.org/oauth/login', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/login",
)
answer = response.json()
Sign in with a password
/oauth/loginFor an account with two-factor authentication, on to the page for the passcode. For one being deleted, the page that offers to restore it.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
csrf_tokenstringThe token the page was given, which proves it was this site's page.identifierstring (email)requiredThe email address. At most 128 characters.passwordstringrequiredThe password. At least 8 characters. At most 72 characters.
Answers
- 200the page again, saying what was wrong, or the page that offers to restore the accountstring text/html
- 302on to where the person was going: back to the authorization that sent them to sign in, or to the web app; or on to the page for the passcode
Request
curl -X POST "https://api.example.org/oauth/login" \
--data-urlencode "csrf_token=string" \
--data-urlencode "identifier=picard@example.org" \
--data-urlencode "password=string"
const form = new URLSearchParams();
form.append('csrf_token', "string");
form.append('identifier', "picard@example.org");
form.append('password', "string");
const response = await fetch('https://api.example.org/oauth/login', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login",
data={"csrf_token": "string", "identifier": "picard@example.org", "password": "string"},
)
answer = response.json()
The page that asks who is signing in with a code
/oauth/login/codeWho: Anyone, signed in or not
Parameters
errorstringin queryWhy the page is shown again:identifier_required,session_expiredorcode_expired.
Answers
- 200the pagestring text/html
- 400the request is not well formed, or a value in it is not one it may be
Request
curl "https://api.example.org/oauth/login/code"
const response = await fetch('https://api.example.org/oauth/login/code', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/login/code",
)
answer = response.json()
Say who is signing in with a code
/oauth/login/codeAn email address or a username.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
csrf_tokenstringThe token the page was given, which proves the form is this site's.identifierstringAn email address or a username.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303on to choose where the code goes, or back when it was not given
Request
curl -X POST "https://api.example.org/oauth/login/code" \
--data-urlencode "csrf_token=string" \
--data-urlencode "identifier=string"
const form = new URLSearchParams();
form.append('csrf_token', "string");
form.append('identifier', "string");
const response = await fetch('https://api.example.org/oauth/login/code', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login/code",
data={"csrf_token": "string", "identifier": "string"},
)
answer = response.json()
The page that asks where the code should go
/oauth/login/code/methodWho: Anyone, signed in or not
Answers
- 200the pagestring text/html
- 302back to the start, or on, when there is only one place to send it
Request
curl "https://api.example.org/oauth/login/code/method"
const response = await fetch('https://api.example.org/oauth/login/code/method', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/login/code/method",
)
answer = response.json()
Send the code
/oauth/login/code/methodThe answer is the same whether or not there is such an account.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
channelstringemail.csrf_tokenstringThe token the page was given, which proves the form is this site's.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303on to the page for the code
Request
curl -X POST "https://api.example.org/oauth/login/code/method" \
--data-urlencode "channel=string" \
--data-urlencode "csrf_token=string"
const form = new URLSearchParams();
form.append('channel', "string");
form.append('csrf_token', "string");
const response = await fetch('https://api.example.org/oauth/login/code/method', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login/code/method",
data={"channel": "string", "csrf_token": "string"},
)
answer = response.json()
Send a new code
/oauth/login/code/resendWho: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
channelstringemail.csrf_tokenstringThe token the page was given, which proves the form is this site's.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303back to the page for the code
Request
curl -X POST "https://api.example.org/oauth/login/code/resend" \
--data-urlencode "channel=string" \
--data-urlencode "csrf_token=string"
const form = new URLSearchParams();
form.append('channel', "string");
form.append('csrf_token', "string");
const response = await fetch('https://api.example.org/oauth/login/code/resend', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login/code/resend",
data={"channel": "string", "csrf_token": "string"},
)
answer = response.json()
The page that asks for the code
/oauth/login/code/verifyWho: Anyone, signed in or not
Answers
- 200the pagestring text/html
- 302back to the start, when no code is waiting
Request
curl "https://api.example.org/oauth/login/code/verify"
const response = await fetch('https://api.example.org/oauth/login/code/verify', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/login/code/verify",
)
answer = response.json()
Sign in with the code
/oauth/login/code/verifyWho: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
codestringThe six digits that were sent.csrf_tokenstringThe token the page was given, which proves the form is this site's.
Answers
- 200the page again, saying what was wrong and how many tries are leftstring text/html
- 302on to where the person was going: back to the authorization that sent them to sign in, or to the web app; or on to the page for the passcode
- 303back to the start, when the code ran out
Request
curl -X POST "https://api.example.org/oauth/login/code/verify" \
--data-urlencode "code=string" \
--data-urlencode "csrf_token=string"
const form = new URLSearchParams();
form.append('code', "string");
form.append('csrf_token', "string");
const response = await fetch('https://api.example.org/oauth/login/code/verify', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login/code/verify",
data={"code": "string", "csrf_token": "string"},
)
answer = response.json()
Begin signing in with a passkey
/oauth/login/passkey/beginThe options to hand to navigator.credentials.get(). The browser offers whichever of its passkeys are for this site.
Who: Anyone, signed in or not
Answers
- 200the options (WebAuthn)anything application/json
Request
curl -X POST "https://api.example.org/oauth/login/passkey/begin"
const response = await fetch('https://api.example.org/oauth/login/passkey/begin', {
method: 'POST',
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login/passkey/begin",
)
answer = response.json()
Answer · 200
null
Sign in with a passkey
/oauth/login/passkey/finishWhat navigator.credentials.get() answered, as the browser gives it. No passcode follows: the gesture that unlocked the passkey is the second factor.
Who: Anyone, signed in or not
Body application/json
anythingAnswers
- 200OKauth.PasskeySignedIn application/json
- 400
ceremony_expired: it was begun too long ago, or not at allRefusal application/json - 401
assertion_failed: the passkey was not acceptedRefusal application/json
Request
curl -X POST "https://api.example.org/oauth/login/passkey/finish" \
-H "Content-Type: application/json" \
-d 'null'
const response = await fetch('https://api.example.org/oauth/login/passkey/finish', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(null),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login/passkey/finish",
json=None,
)
answer = response.json()
Answer · 200
{
"redirect_to": "string"
}
The page that offers to restore an account being deleted
/oauth/login/restoreWho: Anyone, signed in or not
Answers
- 200the pagestring text/html
- 302back to the sign-in page, when no password was just given for such an account
Request
curl "https://api.example.org/oauth/login/restore"
const response = await fetch('https://api.example.org/oauth/login/restore', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/login/restore",
)
answer = response.json()
Restore an account being deleted
/oauth/login/restoreThen the person signs in again.
Who: Anyone, signed in or not
Answers
- 200a page that says it was restored, or why notstring text/html
- 302back to the sign-in page, when no password was just given for such an account
Request
curl -X POST "https://api.example.org/oauth/login/restore"
const response = await fetch('https://api.example.org/oauth/login/restore', {
method: 'POST',
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login/restore",
)
answer = response.json()
The page that asks for the passcode
/oauth/login/totpThe second step of signing in, for an account with two-factor authentication.
Who: Anyone, signed in or not
Answers
- 200the pagestring text/html
- 302back to the sign-in page, when the first step ran out
Request
curl "https://api.example.org/oauth/login/totp"
const response = await fetch('https://api.example.org/oauth/login/totp', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/login/totp",
)
answer = response.json()
Sign in with the passcode
/oauth/login/totpWho: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
csrf_tokenstringThe token the page was given, which proves the form is this site's.totp_passcodestringThe six digits the authenticator shows now.
Answers
- 200the page again, saying what was wrongstring text/html
- 302on to where the person was going: back to the authorization that sent them to sign in, or to the web app; or back to the sign-in page, when the first step ran out or there were too many wrong passcodes
Request
curl -X POST "https://api.example.org/oauth/login/totp" \
--data-urlencode "csrf_token=string" \
--data-urlencode "totp_passcode=string"
const form = new URLSearchParams();
form.append('csrf_token', "string");
form.append('totp_passcode', "string");
const response = await fetch('https://api.example.org/oauth/login/totp', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/login/totp",
data={"csrf_token": "string", "totp_passcode": "string"},
)
answer = response.json()
Sign out
/oauth/logoutNavigated to, not called: it signs out the app's session, then the sign-in session, each on its own host, and ends at return_to.
Who: Anyone, signed in or not
Parameters
return_tostringin queryOne of the installation's own apps. Anything else, or nothing, is the web app.
Answers
- 302on to the next host, or to
return_to
Request
curl "https://api.example.org/oauth/logout"
const response = await fetch('https://api.example.org/oauth/logout', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/logout",
)
answer = response.json()
Revoke a token (OAuth 2.0)
/oauth/revokeRFC 7009. A refresh token is revoked; an access token is short-lived and runs out by itself. The answer is the same whether or not the token was known.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
client_idstringThe app'sclient_id, unless it is in anAuthorization: Basicheader.client_secretstringThe app's secret, likewise; a public app has none.tokenstringrequiredThe token.token_type_hintstringaccess_tokenorrefresh_token, to look for it as first. One ofaccess_token,refresh_token.
Answers
- 200done
- 400the request is not well formed, or a value in it is not one it may be
- 401
invalid_client: the app did not prove which it isauth.OAuthErrorResponse application/json
Request
curl -X POST "https://api.example.org/oauth/revoke" \
--data-urlencode "client_id=string" \
--data-urlencode "client_secret=string" \
--data-urlencode "token=string" \
--data-urlencode "token_type_hint=access_token"
const form = new URLSearchParams();
form.append('client_id', "string");
form.append('client_secret', "string");
form.append('token', "string");
form.append('token_type_hint', "access_token");
const response = await fetch('https://api.example.org/oauth/revoke', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/revoke",
data={"client_id": "string", "client_secret": "string", "token": "string", "token_type_hint": "access_token"},
)
answer = response.json()
Start an app's session
/oauth/sessionFor an app's own server (oauth.md): the authorization code is exchanged as /oauth/token would, and the tokens are kept in the session, not sent.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
client_idstringThe app'sclient_id, unless it is in anAuthorization: Basicheader.client_secretstringThe app's secret, likewise; a public app has none.codestringForauthorization_code: what the app was sent back with.code_verifierstringForauthorization_codewith PKCE: the secret the challenge was the hash of. At least 43 characters. At most 128 characters.grant_typestringrequiredauthorization_code,refresh_tokenorclient_credentials. One ofauthorization_code,refresh_token,client_credentials.redirect_uristring (uri)Forauthorization_code: the one the code was asked for with.refresh_tokenstringForrefresh_token.scopestringForclient_credentials: what the app asks to do, separated by spaces.
Answers
- 200OKauth.SessionStarted application/json
- 400
invalid_grant: the code is wrong, used or expired; or, without a body, the request is not well formedauth.OAuthErrorResponse application/json - 401
invalid_client: the app did not prove which it isauth.OAuthErrorResponse application/json
Request
curl -X POST "https://api.example.org/oauth/session" \
--data-urlencode "client_id=string" \
--data-urlencode "client_secret=string" \
--data-urlencode "code=string" \
--data-urlencode "code_verifier=string" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "redirect_uri=https://example.org/" \
--data-urlencode "refresh_token=string" \
--data-urlencode "scope=string"
const form = new URLSearchParams();
form.append('client_id', "string");
form.append('client_secret', "string");
form.append('code', "string");
form.append('code_verifier', "string");
form.append('grant_type', "authorization_code");
form.append('redirect_uri', "https://example.org/");
form.append('refresh_token', "string");
form.append('scope', "string");
const response = await fetch('https://api.example.org/oauth/session', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/session",
data={"client_id": "string", "client_secret": "string", "code": "string", "code_verifier": "string", "grant_type": "authorization_code", "redirect_uri": "https://example.org/", "refresh_token": "string", "scope": "string"},
)
answer = response.json()
Answer · 200
{
"status": "authenticated"
}
Get tokens (OAuth 2.0)
/oauth/tokenThe token endpoint (RFC 6749, 3.2): an authorization code, a refresh token or the app's own credentials for an access token. A refresh token is used once, and the answer has the next. A refusal of a refresh token says which in error_code: refresh_raced alone is not the end of the session, but another request refreshing first.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
client_idstringThe app'sclient_id, unless it is in anAuthorization: Basicheader.client_secretstringThe app's secret, likewise; a public app has none.codestringForauthorization_code: what the app was sent back with.code_verifierstringForauthorization_codewith PKCE: the secret the challenge was the hash of. At least 43 characters. At most 128 characters.grant_typestringrequiredauthorization_code,refresh_tokenorclient_credentials. One ofauthorization_code,refresh_token,client_credentials.redirect_uristring (uri)Forauthorization_code: the one the code was asked for with.refresh_tokenstringForrefresh_token.scopestringForclient_credentials: what the app asks to do, separated by spaces.
Answers
- 200OKauth.OAuthTokenResponse application/json
- 400
invalid_grant: the code or refresh token is wrong, used or expired;unsupported_grant_type: the app may not use this grant;invalid_scope: it asked for a scope it does not have; or, without a body, the request is not well formedauth.OAuthErrorResponse application/json - 401
invalid_client: the app did not prove which it isauth.OAuthErrorResponse application/json - 403
unauthorized_client: the app may not act for itselfauth.OAuthErrorResponse application/json
Request
curl -X POST "https://api.example.org/oauth/token" \
--data-urlencode "client_id=string" \
--data-urlencode "client_secret=string" \
--data-urlencode "code=string" \
--data-urlencode "code_verifier=string" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "redirect_uri=https://example.org/" \
--data-urlencode "refresh_token=string" \
--data-urlencode "scope=string"
const form = new URLSearchParams();
form.append('client_id', "string");
form.append('client_secret', "string");
form.append('code', "string");
form.append('code_verifier', "string");
form.append('grant_type', "authorization_code");
form.append('redirect_uri', "https://example.org/");
form.append('refresh_token', "string");
form.append('scope', "string");
const response = await fetch('https://api.example.org/oauth/token', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/oauth/token",
data={"client_id": "string", "client_secret": "string", "code": "string", "code_verifier": "string", "grant_type": "authorization_code", "redirect_uri": "https://example.org/", "refresh_token": "string", "scope": "string"},
)
answer = response.json()
Answer · 200
{
"access_token": "string",
"expires_in": 1,
"id_token": "string",
"refresh_token": "string",
"scope": "string",
"token_type": "string"
}
Who the token is for (OpenID Connect)
/oauth/userinfoThe claims its scopes allow: sub always; with profile, name, preferred_username, alias, avatar_urls, id, type, enabled, person_type, totp_enabled, last_visited_at and the rest of what the web app reads of the viewer; with email, email and email_verified. With profile, also entitlements: what the person may do, and how much, where the installation sets a limit; entitlements.invites.max_pending is how many invitations may wait at once, and invites is null for somebody who may not invite.
Who: Somebody signed in, with openid
Answers
- 200OKmap of anything application/json
- 401
invalid_token: no token, or one that has expiredauth.OAuthErrorResponse application/json - 403
insufficient_scope: the token has noopenidauth.OAuthErrorResponse application/json
Request
curl "https://api.example.org/oauth/userinfo" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/oauth/userinfo', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/userinfo",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"key": null
}
The access token of the session
/oauth/validateFor the gateway, on every request: the token is in the X-OAuth-Token header of the answer, refreshed when it had expired, and missing when there is none. Always 200.
Who: Anyone, signed in or not
Answers
- 200the token, in a header, or none
Request
curl "https://api.example.org/oauth/validate"
const response = await fetch('https://api.example.org/oauth/validate', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/oauth/validate",
)
answer = response.json()
What the walk-through after signing up needs
/onboardingWho: Somebody signed in
Answers
- 200OKauth.OnboardingState application/json
- 401no token, or one that has expired
Request
curl "https://api.example.org/onboarding" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/onboarding', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/onboarding",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"invited_by": "string"
}
Say the viewer has been through the walk-through
/onboardingWhether they finished it or skipped it: either way they are not sent back. Only the first time is kept.
Who: Somebody signed in
Answers
- 200OKauth.Onboarded application/json
- 401no token, or one that has expired
Request
curl -X PATCH "https://api.example.org/onboarding" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/onboarding', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/onboarding",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"onboarded_at": "2026-10-27T09:30:00Z"
}
Change the viewer's password
/password/changeSigns out every other session. Passkeys are kept.
Who: Somebody signed in, who has just proved who they are again
Body application/json
passwordstringrequiredThe new password: at least 15 characters. At least 15 characters. At most 150 characters.
Answers
- 200OKauth.PasswordChanged application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth)Refusal application/json - 409it is the password the account has
- 429too many tries; wait
Request
curl -X PATCH "https://api.example.org/password/change" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"password": "string"
}'
const response = await fetch('https://api.example.org/password/change', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"password": "string"
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/password/change",
headers={"Authorization": f"Bearer {token}"},
json={
"password": "string"
},
)
answer = response.json()
Answer · 200
{
"revoked_sessions": 1
}
The page that asks for a reset code
/password/forgotWhere somebody who has forgotten their password starts.
Who: Anyone, signed in or not
Parameters
errorstringin queryexpired, when the code ran out.
Answers
- 200the pagestring text/html
- 400the request is not well formed, or a value in it is not one it may be
Request
curl "https://api.example.org/password/forgot"
const response = await fetch('https://api.example.org/password/forgot', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/password/forgot",
)
answer = response.json()
Send a reset code
/password/forgotMails a six-digit code to the address. The answer is the same whether or not it has an account.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
csrf_tokenstringThe token the page was given, which proves the form is this site's.emailstring (email)requiredThe account's address.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303on to the page for the code
Request
curl -X POST "https://api.example.org/password/forgot" \
--data-urlencode "csrf_token=string" \
--data-urlencode "email=picard@example.org"
const form = new URLSearchParams();
form.append('csrf_token', "string");
form.append('email', "picard@example.org");
const response = await fetch('https://api.example.org/password/forgot', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/password/forgot",
data={"csrf_token": "string", "email": "picard@example.org"},
)
answer = response.json()
The page that asks for a new password
/password/resetReached from a reset link, or after the code.
Who: Anyone, signed in or not
Parameters
tokenstringin queryFrom the link: 128 characters.
Answers
- 200the pagestring text/html
- 302back to the start, when there is neither a link nor a code
- 303back to the start, when the code ran out
Request
curl "https://api.example.org/password/reset"
const response = await fetch('https://api.example.org/password/reset', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/password/reset",
)
answer = response.json()
Set a new password
/password/resetSigns the account out everywhere, and removes its passkeys.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
passwordstringrequiredThe new password: at least 15 characters. At least 15 characters. At most 150 characters.tokenstringFrom the link. None after the code: the session says whose it is. At least 128 characters. At most 128 characters.
Answers
- 200a page that says it was changed, or what was wrongstring text/html
- 303back to the start, when the code ran out
- 400the request is not well formed, or a value in it is not one it may be
Request
curl -X POST "https://api.example.org/password/reset" \
--data-urlencode "password=string" \
--data-urlencode "token=string"
const form = new URLSearchParams();
form.append('password', "string");
form.append('token', "string");
const response = await fetch('https://api.example.org/password/reset', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/password/reset",
data={"password": "string", "token": "string"},
)
answer = response.json()
The page that asks for the reset code
/password/reset/verifyWho: Anyone, signed in or not
Answers
- 200the pagestring text/html
- 302back to the start, when no code is waiting, or on, when it was given
Request
curl "https://api.example.org/password/reset/verify"
const response = await fetch('https://api.example.org/password/reset/verify', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/password/reset/verify",
)
answer = response.json()
Check the reset code
/password/reset/verifyWho: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
codestringThe six digits that were mailed.csrf_tokenstringThe token the page was given, which proves the form is this site's.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303on to the new password, or back to the start when the code ran out
Request
curl -X POST "https://api.example.org/password/reset/verify" \
--data-urlencode "code=string" \
--data-urlencode "csrf_token=string"
const form = new URLSearchParams();
form.append('code', "string");
form.append('csrf_token', "string");
const response = await fetch('https://api.example.org/password/reset/verify', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/password/reset/verify",
data={"code": "string", "csrf_token": "string"},
)
answer = response.json()
Begin proving who the viewer is, with a passkey
/reauth/passkey/beginThe options to hand to navigator.credentials.get(): one of the viewer's own passkeys.
Who: Somebody signed in
Answers
- 200the options (WebAuthn)anything application/json
- 401no token, or one that has expired
- 409the viewer has no passkeys: use the password
Request
curl -X POST "https://api.example.org/reauth/passkey/begin" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/reauth/passkey/begin', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/reauth/passkey/begin",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
null
Prove who the viewer is, with a passkey
/reauth/passkey/finishWhat navigator.credentials.get() answered, as the browser gives it.
Who: Somebody signed in
Body application/json
anythingAnswers
Request
curl -X POST "https://api.example.org/reauth/passkey/finish" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d 'null'
const response = await fetch('https://api.example.org/reauth/passkey/finish', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify(null),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/reauth/passkey/finish",
headers={"Authorization": f"Bearer {token}"},
json=None,
)
answer = response.json()
Prove who the viewer is, with their password
/reauth/passwordAnd their passcode, when they have two-factor authentication. Good for five minutes, and for one change.
Who: Somebody signed in
Body application/json
passcodestringThe passcode, when they have two-factor authentication. At most 64 characters.passwordstringrequiredThe viewer's password. At most 150 characters.
Answers
- 204proved
- 400the request is not well formed, or a value in it is not one it may be
- 401the password is wrong
- 403the passcode is wrong, or missing
- 429too many tries; wait
Request
curl -X POST "https://api.example.org/reauth/password" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"passcode": "string",
"password": "string"
}'
const response = await fetch('https://api.example.org/reauth/password', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"passcode": "string",
"password": "string"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/reauth/password",
headers={"Authorization": f"Bearer {token}"},
json={
"passcode": "string",
"password": "string"
},
)
answer = response.json()
The signup page
/signupOr a page that says the site takes no registrations, or only by invitation.
Who: Anyone, signed in or not
Answers
- 200the pagestring text/html
Request
curl "https://api.example.org/signup"
const response = await fetch('https://api.example.org/signup', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/signup",
)
answer = response.json()
Sign up
/signupMails a code to confirm the address. Where registration is by approval, the request waits for an administrator once it is confirmed.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
bodystringAt most 500 characters.csrf_tokenstringrequiredThe CSRF token, matched against the one put in the session when the form was rendered.emailstring (email)requiredTheir address: a code is mailed to it. At least 3 characters. At most 64 characters.namestringCollected inapprovalmode only, and required there. At most 64 characters.passwordstringrequiredAt least 15 characters. At least 15 characters. At most 150 characters.usernamestringrequiredThe username: letters, digits,-and_. It is the address of their profile. At least 3 characters. At most 30 characters.website_urlstringAt most 255 characters.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303on to the page for the code
Request
curl -X POST "https://api.example.org/signup" \
--data-urlencode "body=string" \
--data-urlencode "csrf_token=string" \
--data-urlencode "email=picard@example.org" \
--data-urlencode "name=string" \
--data-urlencode "password=string" \
--data-urlencode "username=string" \
--data-urlencode "website_url=https://example.org/"
const form = new URLSearchParams();
form.append('body', "string");
form.append('csrf_token', "string");
form.append('email', "picard@example.org");
form.append('name', "string");
form.append('password', "string");
form.append('username', "string");
form.append('website_url', "https://example.org/");
const response = await fetch('https://api.example.org/signup', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/signup",
data={"body": "string", "csrf_token": "string", "email": "picard@example.org", "name": "string", "password": "string", "username": "string", "website_url": "https://example.org/"},
)
answer = response.json()
The requests to join waiting for an answer
/signup-requestsWhere registration is by approval: oldest first, each with whether the username asked for is still free.
Who: An administrator, with actors:read
Parameters
limitintegerin queryHow many on a page: 50 unless said. At least 0. At most 100.startintegerin queryHow many to skip. At least 0.
Answers
- 200OKobject application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the viewer may not do this
Request
curl "https://api.example.org/signup-requests" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/signup-requests', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/signup-requests",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"body": "string",
"created_at": "2026-10-27T09:30:00Z",
"created_ip": "string",
"email": "picard@example.org",
"id": 42,
"invited_by": 1,
"name": "string",
"status": "string",
"username": "string",
"username_taken": false,
"website_url": "https://example.org/"
}
],
"pagination": {
"limit": 1,
"offset": 1,
"total": 1
}
}
Approve a request to join
/signup-requests/{id}/approveMakes the account, and tells them by mail.
Who: An administrator, with actors:write
Parameters
idintegerrequiredin pathThe request's id.
Body application/json
notestringWhy, for the record: the administrators see it, the person does not.
Answers
- 200OKauth.SignupDecided application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the viewer may not do this
- 404there is no such request
- 409
already_decided: it has been answered;not_verified: they have not confirmed their address;username_taken: somebody has the username nowRefusal application/json
Request
curl -X POST "https://api.example.org/signup-requests/<id>/approve" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"note": "string"
}'
const response = await fetch('https://api.example.org/signup-requests/<id>/approve', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"note": "string"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/signup-requests/<id>/approve",
headers={"Authorization": f"Bearer {token}"},
json={
"note": "string"
},
)
answer = response.json()
Answer · 200
{
"person_id": 42,
"status": "string"
}
Turn down a request to join
/signup-requests/{id}/rejectThey are told by mail; the note is not sent.
Who: An administrator, with actors:write
Parameters
idintegerrequiredin pathThe request's id.
Body application/json
notestringWhy, for the record: the administrators see it, the person does not.
Answers
- 200OKauth.SignupDecided application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the viewer may not do this
- 404there is no such request
- 409
already_decided: it has been answered;not_verified: they have not confirmed their addressRefusal application/json
Request
curl -X POST "https://api.example.org/signup-requests/<id>/reject" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"note": "string"
}'
const response = await fetch('https://api.example.org/signup-requests/<id>/reject', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"note": "string"
}),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/signup-requests/<id>/reject",
headers={"Authorization": f"Bearer {token}"},
json={
"note": "string"
},
)
answer = response.json()
Answer · 200
{
"person_id": 42,
"status": "string"
}
The page that asks for the signup code
/signup/confirmWho: Anyone, signed in or not
Answers
- 200the pagestring text/html
- 302on to ask for a new code, when none is waiting
Request
curl "https://api.example.org/signup/confirm"
const response = await fetch('https://api.example.org/signup/confirm', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/signup/confirm",
)
answer = response.json()
Confirm the address
/signup/confirmMakes the account, or sends the request to the administrators.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
codestringThe six digits that were mailed.csrf_tokenstringThe token the page was given, which proves the form is this site's.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303on to ask for a new code, when this one ran out
Request
curl -X POST "https://api.example.org/signup/confirm" \
--data-urlencode "code=string" \
--data-urlencode "csrf_token=string"
const form = new URLSearchParams();
form.append('code', "string");
form.append('csrf_token', "string");
const response = await fetch('https://api.example.org/signup/confirm', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/signup/confirm",
data={"code": "string", "csrf_token": "string"},
)
answer = response.json()
The page that asks for a new signup code
/signup/confirm/resendWho: Anyone, signed in or not
Parameters
errorstringin queryexpired, when the code ran out.
Answers
- 200the pagestring text/html
- 400the request is not well formed, or a value in it is not one it may be
Request
curl "https://api.example.org/signup/confirm/resend"
const response = await fetch('https://api.example.org/signup/confirm/resend', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/signup/confirm/resend",
)
answer = response.json()
Send a new signup code
/signup/confirm/resendThe answer is the same whether or not a signup is waiting.
Who: Anyone, signed in or not
Body application/x-www-form-urlencoded or multipart/form-data
csrf_tokenstringThe token the page was given, which proves the form is this site's.emailstring (email)requiredThe address signed up with. At most 255 characters.
Answers
- 200the page again, saying what was wrong, or a page that says what happenedstring text/html
- 303on to the page for the code
Request
curl -X POST "https://api.example.org/signup/confirm/resend" \
--data-urlencode "csrf_token=string" \
--data-urlencode "email=picard@example.org"
const form = new URLSearchParams();
form.append('csrf_token', "string");
form.append('email', "picard@example.org");
const response = await fetch('https://api.example.org/signup/confirm/resend', {
method: 'POST',
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/signup/confirm/resend",
data={"csrf_token": "string", "email": "picard@example.org"},
)
answer = response.json()
What this site says about itself
/siteIts rules, which everybody agrees to by ticking each one, with the version agreed to; its promises, for its About page; and the rules a new group is offered to start from. Every text in every language it is written in, by the web app's language names. The operator's, in site.json (configuration.md, SITE_DEFAULTS_PATH).
Who: Anyone, signed in or not
Answers
- 200OKauth.SiteAbout application/json
Request
curl "https://api.example.org/site"
const response = await fetch('https://api.example.org/site', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/site",
)
answer = response.json()
Answer · 200
{
"promises": [
{
"body": {
"key": null
},
"title": {
"key": null
}
}
],
"rule_suggestions": [
{
"body": {
"key": null
},
"title": {
"key": null
}
}
],
"rules": {
"items": [
null
],
"version": "string"
}
}
The pages' styles, scripts and pictures
/static/{path}Who: Anyone, signed in or not
Parameters
pathstringrequiredin pathThe rest of the address: the file's path under the directory.
Answers
- 200the filefile application/octet-stream
- 404there is no such file
Request
curl "https://api.example.org/static/<path>"
const response = await fetch('https://api.example.org/static/<path>', {
method: 'GET',
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/static/<path>",
)
answer = response.json()
Whether the viewer has two-factor authentication
/totpWho: Somebody signed in
Answers
- 200OKobject application/json
- 401no token, or one that has expired
Request
curl "https://api.example.org/totp" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/totp', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/totp",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": {
"enabled": false,
"expired": false
}
}
Begin setting up two-factor authentication
/totpAnswers the QR code to pair an authenticator with. It is not on until /totp/recovery-codes and PATCH /totp.
Who: Somebody signed in
Body application/x-www-form-urlencoded or multipart/form-data
passwordstringrequiredThe viewer's password. At least 15 characters. At most 150 characters.
Answers
- 201the QR codefile image/png
- 400the request is not well formed, or a value in it is not one it may be
- 401the password is wrong; or no token
- 404the viewer has no account
- 409it is on: turn it off first
Request
curl -X POST "https://api.example.org/totp" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "password=string"
const form = new URLSearchParams();
form.append('password', "string");
const response = await fetch('https://api.example.org/totp', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/totp",
headers={"Authorization": f"Bearer {token}"},
data={"password": "string"},
)
answer = response.json()
Turn two-factor authentication on
/totpOnce it is set up: paired, and the recovery codes handed out.
Who: Somebody signed in
Answers
- 200on
- 401no token, or one that has expired
- 404the viewer has no account
- 409it is not set up
Request
curl -X PATCH "https://api.example.org/totp" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/totp', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/totp",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Whether somebody has two-factor authentication
/totp/{person_id}For an administrator about to turn it off.
Who: Somebody signed in, with account:read
Parameters
person_idintegerrequiredin pathThe person's id.
Answers
- 200OKobject application/json
- 400the id is not a number
- 401no token, or one that has expired
- 403it is somebody else, and the viewer is not an administrator
- 404there is no such person
Request
curl "https://api.example.org/totp/<person_id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/totp/<person_id>', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/totp/<person_id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": {
"enabled": false,
"expired": false
}
}
Turn two-factor authentication off
/totp/{person_id}The viewer's own; or, for an administrator, somebody's who has lost their authenticator. They are told by mail.
Who: Somebody signed in, with actors:write, who has just proved who they are again
Parameters
person_idintegerrequiredin pathThe person's id.
Answers
- 204off, or it was
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth);forbidden: the viewer may not act on this accountRefusal application/json - 404there is no such person
Request
curl -X DELETE "https://api.example.org/totp/<person_id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/totp/<person_id>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/totp/<person_id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Whether the viewer's last passcode is too long ago
/totp/is/expiredWho: Somebody signed in
Answers
- 200it is
- 204it is not
- 401no token, or one that has expired
- 404the viewer has no account
Request
curl "https://api.example.org/totp/is/expired" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/totp/is/expired', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/totp/is/expired",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Get recovery codes
/totp/recovery-codesWith a passcode from the authenticator just paired, which proves it works. Each code signs in once in place of a passcode. A new set replaces the old, and the owner is told by mail.
Who: Somebody signed in
Body application/x-www-form-urlencoded or multipart/form-data
passcodestringrequiredThe six digits the authenticator shows now. At least 6 characters. At most 8 characters.
Answers
- 200the codes, shown this oncelist of string application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the passcode is wrong
- 404the viewer has no account
Request
curl -X POST "https://api.example.org/totp/recovery-codes" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "passcode=string"
const form = new URLSearchParams();
form.append('passcode', "string");
const response = await fetch('https://api.example.org/totp/recovery-codes', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/totp/recovery-codes",
headers={"Authorization": f"Bearer {token}"},
data={"passcode": "string"},
)
answer = response.json()
Answer · 200
[
"string"
]
Check a passcode
/totp/verifySigning in asks for one again once the last is too long ago; this is that.
Who: Somebody signed in
Body application/x-www-form-urlencoded or multipart/form-data
passcodestringThe six digits the authenticator shows now.
Answers
- 200it is right
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403it is wrong, or the viewer has no two-factor authentication
- 404the viewer has no account
Request
curl -X POST "https://api.example.org/totp/verify" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "passcode=string"
const form = new URLSearchParams();
form.append('passcode', "string");
const response = await fetch('https://api.example.org/totp/verify', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: form,
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/totp/verify",
headers={"Authorization": f"Bearer {token}"},
data={"passcode": "string"},
)
answer = response.json()
Change the viewer's username
/username/changeWhich is also the address of their profile.
Who: Somebody signed in, who has just proved who they are again
Body application/json
usernamestringrequiredThe new username: letters, digits,-and_. At least 3 characters. At most 30 characters.
Answers
- 200OKauth.UsernameChanged application/json
- 204it is the username they have
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth)Refusal application/json - 409it is taken
Request
curl -X PATCH "https://api.example.org/username/change" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "string"
}'
const response = await fetch('https://api.example.org/username/change', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"username": "string"
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/username/change",
headers={"Authorization": f"Bearer {token}"},
json={
"username": "string"
},
)
answer = response.json()
Answer · 200
{
"username": "string"
}
The viewer's passkeys
/webauthn/credentialsEnough to tell them apart: never the keys.
Who: Somebody signed in, with account:read
Answers
- 200OKobject application/json
- 401no token, or one that has expired
- 403the viewer may not do this
Request
curl "https://api.example.org/webauthn/credentials" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/webauthn/credentials', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/webauthn/credentials",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": [
{
"clone_warning": false,
"created_at": "2026-10-27T09:30:00Z",
"id": 42,
"last_used_at": "2026-10-27T09:30:00Z",
"nickname": "string",
"synced": false,
"transports": [
"string"
]
}
]
}
Rename a passkey
/webauthn/credentials/{id}Who: Somebody signed in, with account:write
Parameters
idintegerrequiredin pathThe passkey's id.
Body application/json or application/x-www-form-urlencoded or multipart/form-data
nicknamestringrequiredWhat to call it. At least 1 characters. At most 64 characters.
Answers
- 200OKobject application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the viewer may not do this
- 404the viewer has no such passkey
Request
curl -X PATCH "https://api.example.org/webauthn/credentials/<id>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nickname": "string"
}'
const response = await fetch('https://api.example.org/webauthn/credentials/<id>', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
"nickname": "string"
}),
});
const answer = await response.json();
import requests
response = requests.patch(
"https://api.example.org/webauthn/credentials/<id>",
headers={"Authorization": f"Bearer {token}"},
json={
"nickname": "string"
},
)
answer = response.json()
Answer · 200
{
"data": {
"clone_warning": false,
"created_at": "2026-10-27T09:30:00Z",
"id": 42,
"last_used_at": "2026-10-27T09:30:00Z",
"nickname": "string",
"synced": false,
"transports": [
"string"
]
}
}
Remove a passkey
/webauthn/credentials/{id}The owner is told by mail.
Who: Somebody signed in, with account:write
Parameters
idintegerrequiredin pathThe passkey's id.
Answers
- 200removed
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403the viewer may not do this
- 404the viewer has no such passkey
Request
curl -X DELETE "https://api.example.org/webauthn/credentials/<id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/webauthn/credentials/<id>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/webauthn/credentials/<id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Begin adding a passkey
/webauthn/credentials/register/beginThe options to hand to navigator.credentials.create().
Who: Somebody signed in, with account:write
Answers
- 200the options (WebAuthn)anything application/json
- 401no token, or one that has expired
- 403the viewer may not do this
- 409
credential_limit_reached: remove a passkey firstRefusal application/json
Request
curl -X POST "https://api.example.org/webauthn/credentials/register/begin" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/webauthn/credentials/register/begin', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/webauthn/credentials/register/begin",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
null
Add a passkey
/webauthn/credentials/register/finishWhat navigator.credentials.create() answered, as the browser gives it. It is named after the device; the owner is told by mail.
Who: Somebody signed in, with account:write
Body application/json
anythingAnswers
- 201Createdobject application/json
- 400
ceremony_expired: it was begun too long ago, or not at all;registration_failed: the passkey could not be checkedRefusal application/json - 401no token, or one that has expired
- 403the viewer may not do this
Request
curl -X POST "https://api.example.org/webauthn/credentials/register/finish" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d 'null'
const response = await fetch('https://api.example.org/webauthn/credentials/register/finish', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify(null),
});
const answer = await response.json();
import requests
response = requests.post(
"https://api.example.org/webauthn/credentials/register/finish",
headers={"Authorization": f"Bearer {token}"},
json=None,
)
answer = response.json()
Answer · 201
{
"data": {
"clone_warning": false,
"created_at": "2026-10-27T09:30:00Z",
"id": 42,
"last_used_at": "2026-10-27T09:30:00Z",
"nickname": "string",
"synced": false,
"transports": [
"string"
]
}
}
How many passkeys somebody has
/webauthn/credentials/user/{user_id}A number only: which devices they hold is theirs. Their own, or anybody's, for an administrator.
Who: Somebody signed in, with account:write
Parameters
user_idintegerrequiredin pathThe person's id.
Answers
- 200OKobject application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403it is somebody else, and the viewer is not an administrator
- 404there is no such person
Request
curl "https://api.example.org/webauthn/credentials/user/<user_id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/webauthn/credentials/user/<user_id>', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.get(
"https://api.example.org/webauthn/credentials/user/<user_id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": {
"count": 1
}
}
Remove all of somebody's passkeys
/webauthn/credentials/user/{user_id}Their own, or anybody's, for an administrator, who must have just proved who they are; the owner is told by mail. They still sign in with their password.
Who: Somebody signed in, with account:write, who has just proved who they are again
Parameters
user_idintegerrequiredin pathThe person's id.
Answers
- 200OKobject application/json
- 400the request is not well formed, or a value in it is not one it may be
- 401no token, or one that has expired
- 403
step_up_required: the viewer must prove who they are again first (/reauth); or, without a body, it is somebody else, and the viewer is not an administratorRefusal application/json - 404there is no such person
Request
curl -X DELETE "https://api.example.org/webauthn/credentials/user/<user_id>" \
-H "Authorization: Bearer $TOKEN"
const response = await fetch('https://api.example.org/webauthn/credentials/user/<user_id>', {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
const answer = await response.json();
import requests
response = requests.delete(
"https://api.example.org/webauthn/credentials/user/<user_id>",
headers={"Authorization": f"Bearer {token}"},
)
answer = response.json()
Answer · 200
{
"data": {
"revoked": 1
}
}
Written by Claude, reviewed by Rastin Mehr · About this