API reference
Teams
A shared account for several people. Off unless the operator enables it; every route answers 404 feature_disabled until then.
GET/v1/orgsPOST/v1/orgsGET/v1/orgs/{org_id}POST/v1/orgs/{org_id}/acceptPOST/v1/orgs/{org_id}/inviteGET/v1/orgs/{org_id}/keysGET/v1/orgs/{org_id}/members
GET /v1/orgs
My teams
Teams the caller owns or has accepted membership of, with the caller's role in each.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/orgs" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
r = requests.get(f"{API}/v1/orgs", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"orgs": [
{
"created_at": "2026-09-07T13:00:00+00:00",
"name": "Acme Robotics",
"org_id": "org-3f8a1c9e2b7d4056",
"owner_user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14",
"role": "owner"
}
]
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks orgs; the body names it. |
| 404 | feature_disabled | teams are not enabled on this deployment |
What would show this is false
An invited but not yet accepted team does not appear here. Accept first.
POST /v1/orgs
Create a team
Creates a team owned by the caller. Requires the operator to have enabled teams on this deployment.
Parameters
None.
Request body
namestring required- No description in the schema.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/orgs" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Robotics"
}'
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
"name": "Acme Robotics"
}
r = requests.post(f"{API}/v1/orgs", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 201
{
"org": {
"created_at": "2026-09-07T13:00:00+00:00",
"name": "Acme Robotics",
"org_id": "org-3f8a1c9e2b7d4056",
"owner_user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14",
"role": "owner"
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks orgs; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
| 404 | feature_disabled | teams are not enabled on this deployment |
What would show this is false
GET /v1/orgs lists it with role owner. A second account cannot read it until invited and accepted.
GET /v1/orgs/{org_id}
One team
The team record with member and pending-invite counts.
Parameters
org_idpath, string required- No description in the schema.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/orgs/org-3f8a1c9e2b7d4056" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
r = requests.get(f"{API}/v1/orgs/org-3f8a1c9e2b7d4056", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"invited_count": 0,
"member_count": 2,
"org": {
"created_at": "2026-09-07T13:00:00+00:00",
"name": "Acme Robotics",
"org_id": "org-3f8a1c9e2b7d4056",
"owner_user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14",
"role": "owner"
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks orgs; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
| 404 | not a member, or teams disabled |
What would show this is false
A non-member receives 404, not 403: the team's existence is not disclosed.
POST /v1/orgs/{org_id}/accept
Accept an invitation
The invited person, signed in with the invited email, joins the team.
Parameters
org_idpath, string required- No description in the schema.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/orgs/org-3f8a1c9e2b7d4056/accept" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
-H "Content-Type: application/json" \
-d '{}'
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {}
r = requests.post(f"{API}/v1/orgs/org-3f8a1c9e2b7d4056/accept", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"member": {
"created_at": "2026-09-07T13:05:00+00:00",
"email": "reviewer@example.com",
"org_id": "org-3f8a1c9e2b7d4056",
"role": "member",
"status": "active",
"user_id": "9c4b2a1d-7e6f-4a3b-8c5d-1e2f3a4b5c6d"
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks orgs; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
| 404 | no pending invitation for the caller's email |
What would show this is false
After accepting, GET /v1/orgs on the invited account lists the team with role member.
POST /v1/orgs/{org_id}/invite
Invite by email
Owners invite an email address with a role. Nothing is granted until that person signs in and accepts.
Parameters
org_idpath, string required- No description in the schema.
Request body
emailstring required- No description in the schema.
rolestring required- owner, member or viewer
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/orgs/org-3f8a1c9e2b7d4056/invite" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
-H "Content-Type: application/json" \
-d '{
"email": "reviewer@example.com",
"role": "member"
}'
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
"email": "reviewer@example.com",
"role": "member"
}
r = requests.post(f"{API}/v1/orgs/org-3f8a1c9e2b7d4056/invite", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 201
{
"member": {
"created_at": "2026-09-07T13:05:00+00:00",
"email": "reviewer@example.com",
"org_id": "org-3f8a1c9e2b7d4056",
"role": "member",
"status": "invited",
"user_id": null
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks orgs; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
| 403 | caller is a viewer or member, not an owner | |
| 422 | role outside owner, member, viewer |
What would show this is false
GET members shows status invited with user_id null until accepted.
GET /v1/orgs/{org_id}/keys
Keys issued against the team
Metadata of member keys issued against this team: prefix, machine, label, expiry. Never the token.
Parameters
org_idpath, string required- No description in the schema.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/orgs/org-3f8a1c9e2b7d4056/keys" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
r = requests.get(f"{API}/v1/orgs/org-3f8a1c9e2b7d4056/keys", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"keys": [
{
"created_at": "2026-09-07T13:10:00+00:00",
"expires_at": "2026-10-07T13:10:00+00:00",
"id": "7a1c3e5b-2d4f-4a6b-9c8e-0f1a2b3c4d5e",
"label": "ci",
"machine": "worker-host",
"prefix": "afa-beta-",
"revoked_at": null,
"scopes": [
"events:write",
"grants"
]
}
],
"org_id": "org-3f8a1c9e2b7d4056"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks orgs; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
What would show this is false
No field here can be used to sign in. A key is shown once, at creation, to the account that created it.
GET /v1/orgs/{org_id}/members
Members
Everyone on the team, active and invited, with roles.
Parameters
org_idpath, string required- No description in the schema.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/orgs/org-3f8a1c9e2b7d4056/members" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
r = requests.get(f"{API}/v1/orgs/org-3f8a1c9e2b7d4056/members", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"members": [
{
"created_at": "2026-09-07T13:00:00+00:00",
"email": "owner@example.com",
"org_id": "org-3f8a1c9e2b7d4056",
"role": "owner",
"status": "active",
"user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14"
},
{
"created_at": "2026-09-07T13:05:00+00:00",
"email": "reviewer@example.com",
"org_id": "org-3f8a1c9e2b7d4056",
"role": "member",
"status": "active",
"user_id": "9c4b2a1d-7e6f-4a3b-8c5d-1e2f3a4b5c6d"
}
],
"org_id": "org-3f8a1c9e2b7d4056"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks orgs; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
What would show this is false
The owner is always present with role owner and status active.