Skip to content
Takeida
Browse the reference

Account key · /v1/api

People, roles and what each may reach

Two things decide what a person can do. Their role in the organization says how much, and a grant on each Concept says where. Being a member reaches no Concept by itself. Only the owner reaches everything, because they own it.

A key may change both, and may invite. It may not remove anybody. Ending a membership cannot be undone, and a person can do it themselves in a moment.

Members

Find a member's userId in the member list. Every change here is written to the organization's log, with the key that made it.

Change a member's role

PUT/v1/api/o/members/{userId}

Sets what a member may do across the organization. It takes effect on their next request.

Body

roleEDITOR · WRITER · READER · required
A reader reads what is approved. A writer drafts and proposes. An editor also reviews and publishes.
Ownership is not a role that can be given. An organization has one owner, fixed when it was made. The owner's role cannot be changed, and nobody can be made one.

Refusals

  • 404not_foundNobody with that id is a member.
  • 403insufficient_roleThe member is the owner, or the role asked for is owner.
Request
curl -X PUT \
  https://your-server.example/v1/api/o/members/8b7a6f5e-4d3c-4b2a-9180-7f6e5d4c3b2a \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "role": "EDITOR" }'
Response200
{
  "ok": true,
  "role": "EDITOR"
}

Read a member's Concept access

GET/v1/api/o/members/{userId}/concepts

The Concepts this member has been granted, by name, with the role held in each. Names only. Nothing of what a Concept holds.

What they can actually do in one is the lesser of this role and their role in the organization.

Request
curl https://your-server.example/v1/api/o/members/8b7a6f5e-…/concepts \
  -H "Authorization: Bearer arb_live_…"
Response200
[
  {
    "conceptId": "3c2b1a09-8f7e-4d6c-b5a4-9e8d7c6b5a41",
    "slug": "coastal-atlas",
    "name": "Coastal atlas",
    "role": "WRITER",
    "grantedAt": "2026-08-19T14:02:10.000Z"
  }
]

Set a member's Concept access

PUT/v1/api/o/members/{userId}/concepts

Granting and withdrawing are one call. The body is the complete set: a Concept left out of it loses access, and an empty list withdraws everything.

That makes it safe to send twice, and means a client states what the access should be and never has to work out a difference.

Body

conceptsarray · required
Every Concept the member should reach. May be empty.
concepts[].conceptIduuid · required
The Concept's id, which is not its address. Both are in the list of reachable Concepts.
concepts[].roleOWNER · EDITOR · WRITER · READER · required
The most they may do in that Concept.
Read the current access first if you mean to add one Concept. Sending only the new one withdraws the rest.

Refusals

  • 404not_foundNo such member, or one of the Concepts is not in this organization, is archived, or is deleted.
  • 403insufficient_roleThe member is the owner. Their reach is not held in grants, so there is nothing to edit.
Request
curl -X PUT \
  https://your-server.example/v1/api/o/members/8b7a6f5e-…/concepts \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "concepts": [
      { "conceptId": "3c2b1a09-8f7e-4d6c-b5a4-9e8d7c6b5a41", "role": "WRITER" },
      { "conceptId": "7d6c5b4a-3f2e-4d1c-8b0a-9f8e7d6c5b4a", "role": "READER" }
    ]
  }'
Response200
[
  {
    "conceptId": "3c2b1a09-8f7e-4d6c-b5a4-9e8d7c6b5a41",
    "slug": "coastal-atlas",
    "name": "Coastal atlas",
    "role": "WRITER",
    "grantedAt": "2026-10-09T11:20:00.000Z"
  },
  {
    "conceptId": "7d6c5b4a-3f2e-4d1c-8b0a-9f8e7d6c5b4a",
    "slug": "tide-tables",
    "name": "Tide tables",
    "role": "READER",
    "grantedAt": "2026-10-09T11:20:00.000Z"
  }
]

The access as it now stands.

Allow working through agents

PUT/v1/api/o/members/{userId}/agent-use

Decides whether a member may do their work here through agents of their own. It says how somebody may act and never what they may do. A writer with it is still a writer, and so is their agent.

Body

canUseAgentsboolean · required
On, and the member's agents may borrow the member's standing. Off, and an agent working for them is refused with agent_use_not_enabled.
Request
curl -X PUT \
  https://your-server.example/v1/api/o/members/8b7a6f5e-…/agent-use \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "canUseAgents": true }'
Response200
{
  "ok": true,
  "canUseAgents": true
}

Invitations

Somebody joins by being invited and accepting. An invitation names a person who already has an account, the role offered, and the Concepts that come with it. Nobody is in the organization until they accept, which is why a key may send one.

Accepting and declining are the invited person's to do, signed in. They are not on this surface.

sent

PENDING

May be changed or cancelled. One waiting for each person.

  • ACCEPTED

    by the person invited

  • DECLINED

    by the person invited

  • CANCELLED

    by the organization

  • EXPIRED

    by time

Send an invitation

POST/v1/api/invitations

Offers a person a place. They are told by email, and the invitation waits on their own page whether or not the email arrives. It is open for three hours, and expiresAt says until when.

Body

usernamestring · required
Who to invite, by username. They must already have an account.
roleEDITOR · WRITER · READER · required
Their role in the organization. Never more than the owner of the key holds.
conceptsarray · at least 1 · required
The Concepts that come with the invitation, each with a role. An invitation that reaches nothing is refused.

Refusals

  • 409already_a_memberThey are already in the organization, or the key's owner invited themselves.
  • 409conflictThey already have an invitation waiting. One at a time for each person.
  • 409no_active_conceptsThe organization has no Concept to grant yet. Create one first.
  • 409account_not_activeTheir account is pending, suspended or disabled.
  • 400validation_failedA Concept named is not in this organization or is archived, or the role is above the inviter's.
Request
curl -X POST https://your-server.example/v1/api/invitations \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "ana",
    "role": "WRITER",
    "concepts": [
      { "conceptId": "3c2b1a09-8f7e-4d6c-b5a4-9e8d7c6b5a41", "role": "WRITER" }
    ]
  }'
Response201
{
  "id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
  "organization": { "id": "…", "slug": "harbour-works", "name": "Harbour Works" },
  "invitedUser": { "id": "…", "username": "ana", "displayName": "Ana Reyes" },
  "invitedBy": { "id": "…", "username": "dmitri", "displayName": "Dmitri Okafor" },
  "role": "WRITER",
  "status": "PENDING",
  "concepts": [
    {
      "conceptId": "3c2b1a09-8f7e-4d6c-b5a4-9e8d7c6b5a41",
      "slug": "coastal-atlas",
      "name": "Coastal atlas",
      "role": "WRITER"
    }
  ],
  "expiresAt": "2026-10-09T14:25:00.000Z",
  "createdAt": "2026-10-09T11:25:00.000Z"
}

invitedBy is the key's owner. The person invited sees somebody they can ask.

List invitations

GET/v1/api/invitations

Every invitation the organization has sent, newest first.

Query

statusPENDING · ACCEPTED · DECLINED · EXPIRED · CANCELLED
Only those in this state. All of them when absent.
Request
curl "https://your-server.example/v1/api/invitations?status=PENDING" \
  -H "Authorization: Bearer arb_live_…"
Response200
[
  {
    "id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
    "invitedUser": { "id": "…", "username": "ana", "displayName": "Ana Reyes" },
    "role": "WRITER",
    "status": "PENDING",
    "concepts": [ { "slug": "coastal-atlas", "role": "WRITER", … } ],
    "expiresAt": "2026-10-09T14:25:00.000Z",
    …
  }
]

Read an invitation

GET/v1/api/invitations/{id}

One invitation, in whatever state it has reached. Poll it to learn when somebody has answered.

Refusals

  • 404not_foundNo such invitation in this organization.
Request
curl https://your-server.example/v1/api/invitations/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
  "invitedUser": { "id": "…", "username": "ana", "displayName": "Ana Reyes" },
  "role": "WRITER",
  "status": "ACCEPTED",
  …
}

Change an invitation

PATCH/v1/api/invitations/{id}

Corrects an offer that has gone out and not been answered. Only while it is waiting. Changing what an accepted invitation granted would rewrite somebody's access without their knowing.

Body

roleEDITOR · WRITER · READER
The role offered.
conceptsarray · at least 1
The Concepts it grants, replacing the ones it had. The body says what the invitation grants, never what to add to it.

Send one of the two, or both.

Refusals

  • 409invitation_invalidIt has been answered, cancelled, or has lapsed.
Request
curl -X PATCH \
  https://your-server.example/v1/api/invitations/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d \
  -H "Authorization: Bearer arb_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "role": "EDITOR" }'
Response200
{
  "id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
  "role": "EDITOR",
  "status": "PENDING",
  …
}

Cancel an invitation

DELETE/v1/api/invitations/{id}

Withdraws an offer. It is marked cancelled and kept, because the person may already have seen it, and withdrawn is a better answer than vanished. They can be invited again straight away.

An invitation that has lapsed can still be cancelled, and should be. Until it is, it holds that person's one waiting place and they cannot be invited again.

Refusals

  • 409invitation_invalidIt was already accepted, declined or cancelled. A settled invitation is history.
Request
curl -X DELETE \
  https://your-server.example/v1/api/invitations/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d \
  -H "Authorization: Bearer arb_live_…"
Response200
{
  "ok": true,
  "status": "CANCELLED"
}