Skip to content

Accounts

Accounts

An account is the tenancy boundary: it owns workspaces, members, invitations, and billing. Every route below requires the authenticated user to be a member of the account, except POST /accounts.

GET /accounts/me

Get the current authenticated user's account profile.

Authentication: JWT Bearer token

Request

No path or query parameters. The account is resolved from the authenticated session (req.user.chainerId).

Response

Response Example
json
{
  "data": {
    "id": "acc-1234-5678",
    "slug": "alices-team",
    "legalName": "Alice's Team, Inc.",
    "displayName": "Alice's Team",
    "status": "active",
    "website": "https://alicesteam.example.com",
    "industry": "Software",
    "billingEmail": "billing@alicesteam.example.com",
    "defaultTimezone": "America/New_York",
    "avatarUrl": null,
    "createdAt": "2026-01-15T08:00:00.000Z",
    "updatedAt": "2026-03-10T14:30:00.000Z"
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
idstringAccount ID
slugstringURL-safe account identifier, unique
legalNamestringLegal/organisation name
displayNamestring | nullDisplay name shown in UI, falls back to legalName when unset
statusstring | nullactive, suspended, closed, deletion_approved, or hard_deleted
websitestring | nullOrganisation website URL
industrystring | nullFree-text industry label
billingEmailstring | nullEmail address used for billing notices
defaultTimezonestring | nullIANA timezone used for scheduling defaults
avatarUrlstring | nullAccount avatar/logo URL
createdAtstring | nullISO 8601 timestamp of account creation
updatedAtstring | nullISO 8601 timestamp of last update

Code Examples

bash
curl "https://api.chainabit.com/api/v1/accounts/me" \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(`${BASE_URL}/accounts/me`, {
  headers: {
    Authorization: `Bearer ${TOKEN}`,
  },
});
const data = await response.json();
python
import requests

response = requests.get(
    f"{BASE_URL}/accounts/me",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()

POST /accounts

Create a new organisation account owned by the authenticated user. Unlike every other route in this section, this one has no membership requirement -- there is no account yet to be a member of. The response includes the id of a workspace created for you by default, so a client always has somewhere to land immediately after creation.

Authentication: JWT Bearer token

Request

FieldTypeRequiredConstraintsDescription
slugstringYes3-64 chars, ^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$URL-safe account identifier, unique
legalNamestringYes2-180 charsLegal/organisation name
displayNamestringNoMax 180 charsDisplay name shown in UI
websitestringNoValid URLOrganisation website
industrystringNoMax 120 charsFree-text industry label
billingEmailstringNoValid emailEmail address used for billing notices
defaultTimezonestringNoMax 120 charsIANA timezone used for scheduling defaults
avatarUrlstringNoValid URLAccount avatar/logo URL

There is deliberately no status field: a newly created account always starts active.

Response

Response Example
json
{
  "data": {
    "id": "acc-1234-5678",
    "slug": "alices-team",
    "legalName": "Alice's Team, Inc.",
    "displayName": "Alice's Team",
    "status": "active",
    "website": null,
    "industry": null,
    "billingEmail": null,
    "defaultTimezone": null,
    "avatarUrl": null,
    "accountType": "organisation",
    "defaultWorkspaceId": "wksp-0001",
    "ownerChainerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "createdAt": "2026-03-17T11:00:00.000Z",
    "updatedAt": "2026-03-17T11:00:00.000Z"
  },
  "meta": null,
  "error": null
}
Response Fields

All fields from the account object above, plus:

FieldTypeDescription
accountTypestringType of account created (e.g. organisation)
defaultWorkspaceIdstringID of the workspace created by default for this account
ownerChainerIdstringUser ID of the account owner

Code Examples

bash
curl -X POST "https://api.chainabit.com/api/v1/accounts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "alices-team",
    "legalName": "Alice'"'"'s Team, Inc."
  }'
javascript
const response = await fetch(`${BASE_URL}/accounts`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    slug: "alices-team",
    legalName: "Alice's Team, Inc.",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/accounts",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"slug": "alices-team", "legalName": "Alice's Team, Inc."},
)
data = response.json()

GET /accounts/:id

Get an account by ID. The authenticated user must be a member of the account.

Authentication: JWT Bearer token

Request

Use the account id from GET /accounts/me -- don't copy the example value below.

The account returned is always the one the caller is actually a member of, never an arbitrary account looked up by the id you pass. Passing an id you are not a member of returns a 403 rather than someone else's account data.

Response

Response Example
json
{
  "data": {
    "id": "acc-1234-5678",
    "name": "Alice's Team",
    "slug": "alices-team",
    "role": "owner",
    "createdAt": "2026-01-15T08:00:00.000Z",
    "updatedAt": "2026-03-10T14:30:00.000Z"
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
idstringAccount ID
namestringAccount display name
slugstringURL-safe account identifier
rolestringAuthenticated user's role in this account (owner, admin, member)
createdAtstringISO 8601 timestamp of account creation
updatedAtstringISO 8601 timestamp of last update

Code Examples

bash
curl "https://api.chainabit.com/api/v1/accounts/$ACCOUNT_ID" \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(`${BASE_URL}/accounts/${ACCOUNT_ID}`, {
  headers: {
    Authorization: `Bearer ${TOKEN}`,
  },
});
const data = await response.json();
python
import requests

response = requests.get(
    f"{BASE_URL}/accounts/{ACCOUNT_ID}",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()

PATCH /accounts/:id

Update an account's name. Requires owner or admin role.

Authentication: JWT Bearer token + Owner/Admin role required

Request

All fields are optional; send only what changes.

FieldTypeRequiredConstraintsDescription
slugstringNo3-64 chars, ^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$URL-safe account identifier
legalNamestringNo2-180 charsLegal/organisation name
displayNamestringNoMax 180 charsDisplay name shown in UI
statusstringNo"active" or "suspended" onlyAccount status. Terminal states (closed, deletion_approved, hard_deleted) cannot be set here -- they belong to the account deletion flow
websitestringNoValid URLOrganisation website
industrystringNoMax 120 charsFree-text industry label
billingEmailstringNoValid emailEmail address used for billing notices
defaultTimezonestringNoMax 120 charsIANA timezone used for scheduling defaults
avatarUrlstringNoValid URLAccount avatar/logo URL

Response

Response Example
json
{
  "data": {
    "id": "acc-1234-5678",
    "slug": "alices-team",
    "legalName": "Alice and Team, Inc.",
    "displayName": "Alice and Team",
    "status": "active",
    "website": null,
    "industry": null,
    "billingEmail": null,
    "defaultTimezone": null,
    "avatarUrl": null,
    "createdAt": "2026-01-15T08:00:00.000Z",
    "updatedAt": "2026-03-17T11:00:00.000Z"
  },
  "meta": null,
  "error": null
}
Response Fields

Same shape as the account object returned by GET /accounts/me.

Code Examples

bash
curl -X PATCH "https://api.chainabit.com/api/v1/accounts/$ACCOUNT_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": "Alice and Team"
  }'
javascript
const response = await fetch(`${BASE_URL}/accounts/${ACCOUNT_ID}`, {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    displayName: "Alice and Team",
  }),
});
const data = await response.json();
python
import requests

response = requests.patch(
    f"{BASE_URL}/accounts/{ACCOUNT_ID}",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"displayName": "Alice and Team"},
)
data = response.json()

DELETE /accounts/:id

Soft delete an account. This action is reversible within the retention period. Requires owner role.

Authentication: JWT Bearer token + Owner role required

Request

No path or query parameters beyond the account id.

Response

Response Example
json
{
  "data": {
    "id": "acc-1234-5678",
    "deletedAt": "2026-03-17T11:00:00.000Z"
  },
  "meta": null,
  "error": null
}
Response Fields
FieldTypeDescription
idstringAccount ID
deletedAtstringISO 8601 timestamp of when the account was soft-deleted

Code Examples

bash
curl -X DELETE "https://api.chainabit.com/api/v1/accounts/$ACCOUNT_ID" \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(`${BASE_URL}/accounts/${ACCOUNT_ID}`, {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
  },
});
const data = await response.json();
python
import requests

response = requests.delete(
    f"{BASE_URL}/accounts/{ACCOUNT_ID}",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()

Account Members

An account member is a chainer who already has a Chainabit user account and has been attached directly to this account. To bring in someone by email address who may not have a Chainabit account yet, use Account Invitations instead -- accepting an invitation is what creates the membership row these endpoints operate on.

Assignable roles are admin, analyst, billing, viewer, member. owner cannot be assigned through these endpoints -- ownership transfer requires a dedicated flow.

GET /accounts/:accountId/members

List all members of an account. Any member can view the roster, not just owner/admin.

Authentication: JWT Bearer token

Request

ParameterTypeRequiredDescription
limitnumberNoPage size, up to 500
offsetnumberNoNumber of results to skip

Response

Response Example
json
{
  "data": [
    {
      "accountId": "acc-1234-5678",
      "chainerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "role": "owner",
      "status": "active",
      "email": "alice@example.com",
      "fullName": "Alice Johnson",
      "username": "alice_j",
      "avatarUrl": null,
      "invitedBy": null,
      "joinedAt": "2026-01-15T08:00:00.000Z"
    },
    {
      "accountId": "acc-1234-5678",
      "chainerId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "role": "member",
      "status": "active",
      "email": "bob@example.com",
      "fullName": "Bob Smith",
      "username": "bob_dev",
      "avatarUrl": null,
      "invitedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "joinedAt": "2026-02-20T10:00:00.000Z"
    }
  ],
  "meta": {
    "total": 2
  },
  "error": null
}
Response Fields
FieldTypeDescription
accountIdstringAccount ID
chainerIdstringMember's user ID
rolestringMember's role (owner, admin, analyst, billing, viewer, member)
statusstringinvited or active
emailstring | nullMember's sign-in email address
fullNamestring | nullMember's full name
usernamestring | nullMember's username
avatarUrlstring | nullMember's avatar URL
invitedBystring | nullUser ID of whoever added this member, null for the account owner
joinedAtstring | nullISO 8601 timestamp of when the member joined

Code Examples

bash
curl "https://api.chainabit.com/api/v1/accounts/$ACCOUNT_ID/members" \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(`${BASE_URL}/accounts/${ACCOUNT_ID}/members`, {
  headers: {
    Authorization: `Bearer ${TOKEN}`,
  },
});
const data = await response.json();
python
import requests

response = requests.get(
    f"{BASE_URL}/accounts/{ACCOUNT_ID}/members",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()

POST /accounts/:accountId/members

Attach an existing chainer to an account by their chainerId. Requires owner or admin role. This is not an email invite -- the caller must already know the target's chainerId. To bring in someone by email address, use POST /accounts/:accountId/invitations instead.

Authentication: JWT Bearer token + Owner/Admin role required

Request

FieldTypeRequiredConstraintsDescription
chainerIdstringYesUUIDUser ID of the chainer to add
rolestringNoOne of the assignable roles above, defaults to memberRole to assign

Response

Response Example
json
{
  "data": {
    "accountId": "acc-1234-5678",
    "chainerId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
    "role": "member",
    "status": "active",
    "email": "carol@example.com",
    "fullName": "Carol Nguyen",
    "username": "carol_n",
    "avatarUrl": null,
    "invitedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "joinedAt": "2026-03-17T11:00:00.000Z"
  },
  "meta": null,
  "error": null
}
Response Fields

Same shape as the member object above.

Code Examples

bash
curl -X POST "https://api.chainabit.com/api/v1/accounts/$ACCOUNT_ID/members" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "chainerId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
    "role": "member"
  }'
javascript
const response = await fetch(`${BASE_URL}/accounts/${ACCOUNT_ID}/members`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    chainerId: "c3d4e5f6-a7b8-9012-cdef-123456789012",
    role: "member",
  }),
});
const data = await response.json();
python
import requests

response = requests.post(
    f"{BASE_URL}/accounts/{ACCOUNT_ID}/members",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"chainerId": "c3d4e5f6-a7b8-9012-cdef-123456789012", "role": "member"},
)
data = response.json()

PATCH /accounts/:accountId/members/:chainerId

Update a member's role in an account. Requires owner or admin role.

Authentication: JWT Bearer token + Owner/Admin role required

Request

chainerId is the member's user ID, returned as chainerId in the GET /accounts/:accountId/members response -- not the literal value shown below.

FieldTypeRequiredConstraintsDescription
rolestringYesOne of the assignable roles aboveNew role for the member

Response

Response Example
json
{
  "data": {
    "accountId": "acc-1234-5678",
    "chainerId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "role": "admin",
    "status": "active",
    "email": "bob@example.com",
    "fullName": "Bob Smith",
    "username": "bob_dev",
    "avatarUrl": null,
    "invitedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "joinedAt": "2026-02-20T10:00:00.000Z"
  },
  "meta": null,
  "error": null
}
Response Fields

Same shape as the member object above.

Code Examples

bash
curl -X PATCH "https://api.chainabit.com/api/v1/accounts/$ACCOUNT_ID/members/$CHAINER_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "role": "admin"
  }'
javascript
const response = await fetch(
  `${BASE_URL}/accounts/${ACCOUNT_ID}/members/${CHAINER_ID}`,
  {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ role: "admin" }),
  }
);
const data = await response.json();
python
import requests

response = requests.patch(
    f"{BASE_URL}/accounts/{ACCOUNT_ID}/members/{CHAINER_ID}",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"role": "admin"},
)
data = response.json()

DELETE /accounts/:accountId/members/:chainerId

Remove a member from an account. Requires owner or admin role. Also revokes any enterprise API keys the member owns, removes their workspace memberships, and revokes their active sessions.

Authentication: JWT Bearer token + Owner/Admin role required

Request

ParameterTypeRequiredDescription
releaseSeatbooleanNoRelease the vacated seat back to the subscription. Prorated by the payment provider, so it is never inferred -- omit it (or send anything other than the literal true) to leave billing untouched. Defaults to false.

Response

Response Example
json
{
  "data": {
    "accountId": "acc-1234-5678",
    "chainerId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "role": "member",
    "status": "active",
    "email": "bob@example.com",
    "fullName": "Bob Smith",
    "username": "bob_dev",
    "avatarUrl": null,
    "invitedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "joinedAt": "2026-02-20T10:00:00.000Z",
    "revokedApiKeys": [
      { "id": "key-01", "name": "CI deploy key", "keyPrefix": "cb_live_", "lastFour": "a1b2" }
    ],
    "removedWorkspaceMemberships": 3,
    "revokedSessions": 1,
    "seatRelease": { "status": "not_requested" }
  },
  "meta": null,
  "error": null
}
Response Fields

Member fields as in GET /accounts/:accountId/members, plus:

FieldTypeDescription
revokedApiKeysarrayEnterprise API keys owned by this member that were revoked as part of removal (id, name, keyPrefix, lastFour)
removedWorkspaceMembershipsnumberCount of workspace memberships removed along with this account membership
revokedSessionsnumberCount of active sessions revoked
seatRelease.statusstringnot_requested (no releaseSeat sent), requested, or a reason the release did not happen

Code Examples

bash
curl -X DELETE "https://api.chainabit.com/api/v1/accounts/$ACCOUNT_ID/members/$CHAINER_ID?releaseSeat=true" \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(
  `${BASE_URL}/accounts/${ACCOUNT_ID}/members/${CHAINER_ID}?releaseSeat=true`,
  {
    method: "DELETE",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
    },
  }
);
const data = await response.json();
python
import requests

response = requests.delete(
    f"{BASE_URL}/accounts/{ACCOUNT_ID}/members/{CHAINER_ID}",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={"releaseSeat": "true"},
)
data = response.json()

Account Audit Logs

GET /accounts/:id/logs

List audit events for an account. Requires owner or admin role.

Authentication: JWT Bearer token + Owner/Admin role required

Request

ParameterTypeRequiredDescription
limitnumberNoNumber of results to return (default: 20)
cursorstringNoPagination cursor from a previous response

Response

Response Example
json
{
  "data": [
    {
      "id": "log-0001",
      "action": "member.invited",
      "actorId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "actorUsername": "alice_j",
      "targetId": "carol@example.com",
      "metadata": {
        "role": "member"
      },
      "createdAt": "2026-03-17T11:00:00.000Z"
    },
    {
      "id": "log-0002",
      "action": "account.updated",
      "actorId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "actorUsername": "alice_j",
      "targetId": "acc-1234-5678",
      "metadata": {
        "field": "name",
        "oldValue": "Alice's Team",
        "newValue": "Alice and Team"
      },
      "createdAt": "2026-03-17T11:00:00.000Z"
    }
  ],
  "meta": {
    "total": 2,
    "hasMore": false,
    "cursor": null
  },
  "error": null
}
Response Fields
FieldTypeDescription
idstringAudit log entry ID
actionstringEvent type (e.g. "member.invited", "account.updated")
actorIdstringUser ID of the person who performed the action
actorUsernamestringUsername of the person who performed the action
targetIdstringID or identifier of the resource affected
metadataobjectAdditional context about the event; shape varies by action type
createdAtstringISO 8601 timestamp of when the event occurred

Code Examples

bash
curl "https://api.chainabit.com/api/v1/accounts/$ACCOUNT_ID/logs?limit=20" \
  -H "Authorization: Bearer $TOKEN"
javascript
const response = await fetch(
  `${BASE_URL}/accounts/${ACCOUNT_ID}/logs?limit=20`,
  {
    headers: {
      Authorization: `Bearer ${TOKEN}`,
    },
  }
);
const data = await response.json();
python
import requests

response = requests.get(
    f"{BASE_URL}/accounts/{ACCOUNT_ID}/logs",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={"limit": 20},
)
data = response.json()

Built with purpose.