> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cubic.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Members API

> List organization members and manage their seats and roles from your own tools.

The Members API lets your scripts and tools manage cubic members. You can list your organizations and their members, read one member, turn a seat on or off, and change a role.

## Get an API key

The Members API accepts two kinds of key.

| Key | Starts with | Who creates it | Acts as |
| - | - | - | - |
| Personal key | `cbk_` | Any cubic user | You, with your own cubic access. |
| Organization key | `cok_` | An organization admin | One organization, with read or admin access to its members. |

Use an organization key for automation that should not depend on one person, such as a sync from
your identity provider. It keeps working when the admin who created it leaves.

### Personal key

<Steps>
  <Step title="Open the cubic API page">
    Go to [Settings > API, CLI & MCP > cubic API](https://www.cubic.dev/settings?tab=api-cli\&integration=api).
  </Step>

  <Step title="Generate a personal key">
    In the **Members API** card, click **Generate API key**. Keys start with `cbk_`, and cubic only shows the full value once.
  </Step>

  <Step title="Store it securely">
    Save the key in your secret manager or local environment.
  </Step>
</Steps>

<Note>
  This is the same personal key that the [cubic MCP server](/ide/mcp-server), the [cubic
  CLI](/ide/cli-review), and the [Analytics API](/analytics/api) use, and regenerating it replaces
  the key for all of them. The key acts with your own cubic access. You only see organizations you
  can already view in cubic, and changes need an organization admin. See
  [Roles and permissions](/account/roles-and-permissions).
</Note>

### Organization key

Only organization admins can create and revoke organization keys.

<Steps>
  <Step title="Open API, CLI & MCP">
    Go to [Settings > API, CLI & MCP](https://www.cubic.dev/settings?tab=api-cli) and pick the organization next to **Organization API keys**.
  </Step>

  <Step title="Create a key">
    Click **Create API key** and name the key after the script or integration that uses it. Choose **Read** to list organizations and members, or **Admin** to also turn seats on or off and change roles. Keys start with `cok_`, and cubic only shows the full value once.
  </Step>

  <Step title="Store it securely">
    Save the key in your secret manager.
  </Step>
</Steps>

<Note>
  An organization key belongs to the organization, not to the admin who created it. It reads
  members of that one organization. A key with **Admin** access also updates them with an admin's
  permissions, and a **Read** key gets `403 admin_required` on `PATCH`. No key can change billing.
  Any admin can revoke a key in **Settings > API, CLI & MCP**, and requests with a revoked key fail from
  then on. Uninstalling cubic from the organization deletes its keys.
</Note>

## Endpoints

| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/api/v1/organizations` | [List the organizations you can view](/api-reference/members/list-organizations). |
| `GET` | `/api/v1/organizations/{org}/members` | [List an organization's members](/api-reference/members/list-members). |
| `GET` | `/api/v1/organizations/{org}/members/{githubUserId}` | [Read one member](/api-reference/members/get-member). |
| `PATCH` | `/api/v1/organizations/{org}/members/{githubUserId}` | [Turn a seat on or off, or change a role](/api-reference/members/update-member). |

The base URL is `https://www.cubic.dev`. `{org}` is the GitHub organization login, such as `acme`. Each organization in the list includes `githubAccountId`, GitHub's numeric account ID, and flags such as `canManageSeats` that show what your access allows. An organization key lists only its own organization.

Each endpoint's reference page includes request fields, response schemas, and code examples. Enter
an API key in the playground to try a request with that key's access.

The [OpenAPI document](https://www.cubic.dev/api/v1/openapi.json) powers the API reference. You can
also generate a client from it.

### Authentication

Send a personal or organization key in the `Authorization` header as a bearer token.

```http theme={null}
Authorization: Bearer cbk_your_api_key
```

The same keys work with the [Analytics API](/analytics/api). Analytics API keys that start with
`cak_` do not work with the Members API.

### Example requests

<CodeGroup>
  ```bash cURL theme={null}
  # List members who have a seat
  curl --request GET \
    --url 'https://www.cubic.dev/api/v1/organizations/acme/members?seat=true&limit=100' \
    --header 'Authorization: Bearer cbk_your_api_key'

  # Turn off a member's seat
  curl --request PATCH \
    --url 'https://www.cubic.dev/api/v1/organizations/acme/members/583231' \
    --header 'Authorization: Bearer cbk_your_api_key' \
    --header 'Content-Type: application/json' \
    --data '{"seat": false, "expectedSeat": true}'
  ```

  ```javascript JavaScript theme={null}
  const memberUrl = 'https://www.cubic.dev/api/v1/organizations/acme/members/583231';
  const headers = {
    'Authorization': `Bearer ${process.env.CUBIC_API_KEY}`,
    'Content-Type': 'application/json',
  };

  const memberResponse = await fetch(memberUrl, { headers });
  const member = await memberResponse.json();

  if (!memberResponse.ok) {
    throw new Error(`${member.error.code}: ${member.error.message}`);
  }

  const response = await fetch(memberUrl, {
    method: 'PATCH',
    headers,
    body: JSON.stringify({ seat: false, expectedSeat: member.seat }),
  });
  const result = await response.json();

  if (!response.ok) {
    throw new Error(`${result.error.code}: ${result.error.message}`);
  }
  ```
</CodeGroup>

## Members

```json theme={null}
{
  "githubUserId": "583231",
  "githubLogin": "octocat",
  "workEmail": "octocat@acme.com",
  "role": "member",
  "seat": true,
  "isBot": false
}
```

| Field | Description |
| - | - |
| `githubUserId` | GitHub's numeric user ID, as a string. It never changes, so use it as the member's key. |
| `githubLogin` | GitHub login, or `null`. Logins can change. |
| `workEmail` | Work email, or `null`. An admin sets it in cubic, or cubic syncs it from GitHub SAML or SCIM. cubic never returns a personal email here. |
| `role` | `admin`, `member`, or `viewer`. |
| `seat` | Whether the member has a seat. |
| `isBot` | Whether the account is a bot. Bots do not consume paid seat capacity and cannot be admins. |

A seat is a paid license for cubic reviews. Turning a seat off does not remove the person's access to cubic. To remove someone, remove them from the GitHub organization. cubic removes their access when GitHub notifies it. See [Roles and permissions](/account/roles-and-permissions) for what each role can do.

The member list holds the people cubic tracks for the organization, including members with access to a repository cubic reviews and pull request authors. On paid plans, cubic refreshes it from GitHub every day.

### Filter the member list

| Parameter | Description |
| - | - |
| `search` | Match part of a GitHub login, ignoring case. |
| `role` | Only members with this role. |
| `seat` | `true` for members with a seat, `false` for members without one. |

The list also takes `cursor` and `limit`, described in [Pagination](#pagination). An unknown parameter returns `400 invalid_request`, so a mistyped filter never lists everyone.

## Update a member

Send the fields you want to change, each with the value from your latest read.

| Field | Description |
| - | - |
| `seat` | `true` turns the seat on. `false` turns it off. Send it with `expectedSeat`. |
| `expectedSeat` | The `seat` value from your latest read. |
| `role` | `admin`, `member`, or `viewer`. Send it with `expectedRole`. |
| `expectedRole` | The `role` value from your latest read. |

If the member changed since your read, the request fails with `409 member_state_changed` and changes nothing. Read the member again, then retry. A request that asks for the member's current state succeeds with `"changed": false`, so retrying a completed request is safe.

You can change a seat and role in the same request. Both changes are committed together, or the
request fails without changing either field. For example, to turn off a member's seat and change
their role to viewer, send this body with `PATCH`:

```json theme={null}
{
  "seat": false,
  "expectedSeat": true,
  "role": "viewer",
  "expectedRole": "member"
}
```

The response holds the member's `before` and `after` state, `changed`, and `availableSeats`, the number of paid seats still free after the change. `purchasedQuantityChanged` is always `false`: the API assigns existing capacity and never changes the number of seats you buy.

```json theme={null}
{
  "githubUserId": "583231",
  "before": { "seat": true, "role": "member" },
  "after": { "seat": false, "role": "viewer" },
  "changed": true,
  "availableSeats": 1,
  "purchasedQuantityChanged": false
}
```

<Note>
  Updates need an organization admin or an organization key with **Admin** access, and an active
  Team, Pro, or Max subscription billed through cubic. Manage seats for Vercel Marketplace
  subscriptions in cubic. The API never buys seats. When every paid seat is in use, turning on a
  human member's seat fails with `seat_capacity_exceeded`. Add seats in
  [Settings → Billing](https://www.cubic.dev/settings?tab=billing) first.
</Note>

## Pagination

List endpoints return one page and a `nextCursor`. To get the next page, send `nextCursor` back as `cursor`. `nextCursor` is `null` on the last page. Set the page size with `limit`, from 1 to 100. The default is 20.

Copy `nextCursor` unchanged. The members endpoint does not check whether a cursor came from an
earlier page, so an edited or unrecognized cursor can return an empty page instead of an error.
The organizations endpoint returns `400 invalid_cursor` for invalid cursor numbers.

To fetch every member, continue until `nextCursor` is `null`:

```javascript theme={null}
const members = [];
let cursor = null;

do {
  const url = new URL('https://www.cubic.dev/api/v1/organizations/acme/members');
  url.searchParams.set('limit', '100');
  if (cursor !== null) {
    url.searchParams.set('cursor', cursor);
  }

  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.CUBIC_API_KEY}` },
  });
  const page = await response.json();

  if (!response.ok) {
    throw new Error(`${page.error.code}: ${page.error.message}`);
  }

  members.push(...page.members);
  cursor = page.nextCursor;
} while (cursor !== null);
```

## Rate limits

The Members API allows 1,000 requests per 15 minutes for each person's personal key. An
organization's keys share a separate limit of 1,000 requests per 15 minutes. Regenerating a personal
key or creating more organization keys does not reset or raise a limit. Analytics API requests
with these keys count toward the same limits. Every response after authentication includes these
headers.

| Header | Description |
| - | - |
| `X-RateLimit-Limit` | Requests allowed per 15-minute window. |
| `X-RateLimit-Remaining` | Requests left in the current window. |
| `X-RateLimit-Reset` | When the window resets, in Unix epoch seconds. |

When you run out, cubic answers `429 rate_limited` with a `Retry-After` header in seconds. If cubic cannot check the limit, it answers `503 rate_limiter_unavailable` with `Retry-After: 30` and no `X-RateLimit-*` headers. In both cases, wait for `Retry-After` before you retry.

## Errors

Every error uses the same envelope.

```json theme={null}
{
  "error": {
    "code": "member_state_changed",
    "message": "The member changed since you read it. Fetch the member again and retry.",
    "retriable": false
  }
}
```

Branch on `code`, because messages can change. `retriable` says whether the same request can succeed if you send it again. Some errors include a `correlationId`. Quote it when you contact support. Validation errors include `details`, a list of `path` and `message` pairs for the fields that failed.

The reference pages describe response schemas. The codes below explain errors your scripts can handle.

| Status | Code | When it happens |
| - | - | - |
| `400` | `invalid_request` | A path, query, or body value is invalid, or a query parameter is unknown. |
| `400` | `invalid_arguments` | The body changes neither `seat` nor `role`, sends a value without its expected value, or sends an expected value without its value. |
| `400` | `invalid_cursor` | An organization-list cursor is not an integer from 0 to 2,147,483,647. Member lists do not return this error. |
| `401` | `unauthorized` | The bearer token is missing, or the account or organization cannot use cubic. |
| `401` | `invalid_api_key` | The key is invalid, expired, or revoked, or it is not a personal `cbk_` or organization `cok_` key. |
| `403` | `access_denied` | Your cubic access does not cover this organization. |
| `403` | `admin_required` | Updates need an organization admin, or an organization key with admin access. |
| `403` | `paid_plan_required` | The organization is not on a paid plan. |
| `403` | `subscription_not_active` | The subscription is not active, or its payment collection is paused. |
| `403` | `unsupported_subscription` | The subscription is not a standard Team, Pro, or Max seat plan. Manage it in cubic. |
| `403` | `billing_provider_not_supported` | The organization is billed through Vercel. Manage its seats in cubic. |
| `404` | `organization_not_found` | The organization does not exist in cubic, or you cannot access it. |
| `404` | `member_not_found` | No member of the organization has this GitHub user ID. |
| `409` | `member_state_changed` | The member changed since your read. |
| `409` | `seat_capacity_exceeded` | Every paid seat is in use. |
| `409` | `last_admin_required` | The change would remove the last active admin. |
| `409` | `billing_state_changed` | Billing changed while the request ran. Send the request again. |
| `422` | `self_demotion_not_allowed` | You cannot remove your own admin role. |
| `422` | `bot_admin_not_allowed` | Bots cannot be admins. |
| `429` | `rate_limited` | You used every request in this window. |
| `500` | `internal_error` | Something failed unexpectedly. Retry the request. |
| `503` | `unavailable` | cubic could not verify the key. Retry shortly. |
| `503` | `rate_limiter_unavailable` | cubic could not check the rate limit. Retry shortly. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.