# API tokens

Tokens that let automation administer one club, or manage clubs on the platform.

Source: https://docs.champagne.dev-ukab.fr/en/graphql/api-tokens

An API token lets a tool such as the [Terraform provider](/en/terraform) call this API without a
person signing in. A token belongs to one club and reaches only the areas of that club's
configuration it was granted. Send it like a session token:

```http
Authorization: Bearer chp_3f9c…
```

Secrets start with `chp_` followed by 48 hexadecimal characters. Only a hash is stored: the
secret is returned once, by `createClubApiToken`, and cannot be read again.

## What a token can do [#what-a-token-can-do]

A token is for configuration only. It reaches the operations below, which are the ones the
[Terraform provider](/en/terraform) calls, and is refused everything else with
`API tokens cannot use this operation.`: people, memberships, applications, reservations,
billing, payment accounts, chats, the team, the platform, API tokens themselves, and public
operations such as `plans` or `signOut`. An operation added to the API stays closed to tokens
until it is added to this list.

Each operation belongs to one area. A token is granted `read` or `write` on each area it
needs; an area left out is closed to it, and the operation answers
`This API token lacks the permission for this operation.` with `UNAUTHENTICATED`.

| Area (`ClubApiTokenArea`) | Queries (`read` or `write`)                                                                | Mutations (`write`)                                                                                                                                                                                                                                                 |
| ------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `settings`                | `clubConfiguration`                                                                        | `updateClubConfiguration`                                                                                                                                                                                                                                           |
| `plans`                   | `plan`                                                                                     | `createPlan`, `updatePlan`, `splitPlan`, `deletePlan`                                                                                                                                                                                                               |
| `reservation_catalog`     | `reservationCategory`, `reservationResource`, `clubStaff`, `reservationSessionDefinitions` | `createReservationCategory`, `updateReservationCategory`, `deleteReservationCategory`, `setReservationCategoryLinks`, `createReservationResource`, `updateReservationResource`, `deleteReservationResource`, `createReservationSession`, `deleteReservationSession` |
| `floor_plans`             | `reservationFloorPlan`                                                                     | `saveReservationFloorPlan`                                                                                                                                                                                                                                          |
| `housekeeping`            | `housekeeping`                                                                             | `setResourceChecklist`                                                                                                                                                                                                                                              |
| `person_fields`           | `personAttributes`, `personTags`                                                           | `createPersonAttribute`, `updatePersonAttribute`, `deletePersonAttribute`, `createPersonTag`, `updatePersonTag`, `deletePersonTag`                                                                                                                                  |
| `webhooks`                | `clubWebhookEndpoints`                                                                     | `createClubWebhookEndpoint`, `updateClubWebhookEndpoint`, `deleteClubWebhookEndpoint`, `revealClubWebhookSecret`                                                                                                                                                    |
| `newsletter`              | `newsletterSettings`                                                                       | `configureNewsletterSender`                                                                                                                                                                                                                                         |

`clubApiTokenIdentity` and `platformApiTokenIdentity` need no permission.

On the operations of its areas a token passes the checks a club administrator passes for its
club, including modules the platform turned off for the club (refused to administrators too):

| Rule                       | Behavior                                                               |
| -------------------------- | ---------------------------------------------------------------------- |
| `read` on an area          | The queries of the area. Its mutations are refused.                    |
| `write` on an area         | The queries and mutations of the area.                                 |
| Area not granted           | Every operation of the area is refused.                                |
| Other clubs                | Refused, like for an administrator of another club.                    |
| Blocked token              | Resolves to no identity until it is unblocked: requests are anonymous. |
| Expired or deleted token   | Resolves to no identity, for good.                                     |
| Suspended or archived club | Same as a blocked token until the club is active again.                |

A token created with `hidesPersonalData: true` reads personal data pseudonymized, exactly like
a staff account with the [`hidesPersonalData` restriction](/en/graphql/auth#account-restrictions):
guest names in `housekeeping` and `reservationFloorPlan` read `Member` followed by a code, or
`Guest`. Staff names are not member data and stay readable.

Permissions, `hidesPersonalData`, the name and the expiration date are set when the token is
created and cannot be changed afterwards: create another token, then delete the first.

Activity recorded by a token names it `API token <name>`.

## Identify the token [#identify-the-token]

```graphql
query TokenIdentity {
  clubApiTokenIdentity {
    id
    name
    permissions {
      area
      access
    }
    hidesPersonalData
    clubId
  }
}
```

Returns the token authenticating the request, or `null` for any other caller. Automation uses
`clubId` as the `clubId` argument of every other operation.

## Permissions [#permissions]

`clubApiTokens`, `createClubApiToken`, `setClubApiTokenBlocked` and `deleteClubApiToken`
require a signed-in administrator of the club. Staff accounts are refused whatever their
permissions, and so are API tokens.

## List tokens [#list-tokens]

```graphql
query ClubApiTokens($clubId: ID! = "51000000-0000-4000-8000-000000000051") {
  clubApiTokens(clubId: $clubId) {
    id
    name
    permissions {
      area
      access
    }
    hidesPersonalData
    hint
    createdBy
    createdAt
    lastUsedAt
    expiresAt
    blockedAt
    status
  }
}
```

Tokens are returned newest first, blocked and expired ones included. `hint` holds the last
four characters of the secret, `createdBy` the name of the administrator who created the token
(null once that account is deleted). `lastUsedAt` is refreshed at most once a minute.
`status` is `blocked` while `blockedAt` is set, otherwise `expired` once `expiresAt` is
reached, otherwise `active`.

## Create a token [#create-a-token]

```graphql
mutation CreateClubApiToken($input: CreateClubApiTokenInput!) {
  createClubApiToken(input: $input) {
    secret
    token {
      id
      name
      status
    }
  }
}
```

```json
{
  "input": {
    "clubId": "51000000-0000-4000-8000-000000000051",
    "name": "Terraform, staging",
    "permissions": [
      { "area": "plans", "access": "write" },
      { "area": "settings", "access": "read" }
    ],
    "hidesPersonalData": true,
    "expiresAt": "2027-06-30T23:59:59+02:00"
  }
}
```

| Argument            | Rule                                                                                                                                                                                  |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`              | Trimmed, 1–80 characters on a single line.                                                                                                                                            |
| `permissions`       | At least one, each area listed once: otherwise `Give the API token at least one permission.` or `List each API token area only once.`                                                 |
| `hidesPersonalData` | Optional, `false` by default. `true` pseudonymizes the personal data the token reads.                                                                                                 |
| `expiresAt`         | Optional. An ISO 8601 date and time with an offset (`Use a valid expiration date.`), in the future (`The expiration date must be in the future.`). Left out, the token never expires. |

A club holds at most 20 tokens, blocked and expired ones included; beyond that the mutation
answers `Delete an API token before creating another one.`

## Block or unblock a token [#block-or-unblock-a-token]

```graphql
mutation SetClubApiTokenBlocked($clubId: ID!, $id: ID!, $blocked: Boolean!) {
  setClubApiTokenBlocked(clubId: $clubId, id: $id, blocked: $blocked) {
    id
    status
    blockedAt
  }
}
```

Blocking is immediate and reversible. Blocking an already blocked token keeps its first
`blockedAt`. Unblocking never revives an expired token. The mutation returns `null` when the
club has no token with this id.

## Delete a token [#delete-a-token]

```graphql
mutation DeleteClubApiToken($clubId: ID!, $id: ID!) {
  deleteClubApiToken(clubId: $clubId, id: $id)
}
```

Deletion is immediate and permanent: the token no longer authenticates and leaves the list.
Returns `false` when the club has no token with this id.

## Platform tokens [#platform-tokens]

A platform token lets automation manage the clubs themselves, as the
[platform administration](/en/graphql/platform) does. It belongs to no club and reads no club
data. Its secret starts with `chpp_` followed by 48 hexadecimal characters and is sent the
same way.

| Rule                              | Behavior                                                                                                                     |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Allowed operations                | `platformSpaces`, `createPlatformClub`, `updatePlatformClub`, `setClubStatus`, `setClubFeatures`, and both identity queries. |
| Everything else                   | Refused with `API tokens cannot use this operation.`: admins, owners, spaces, history, tokens and all club data.             |
| `read` access                     | `platformSpaces` only.                                                                                                       |
| Blocked, expired or deleted token | Resolves to no identity.                                                                                                     |

`platformApiTokenIdentity { id name access }` returns the platform token authenticating the
request, or `null`; `clubApiTokenIdentity` is `null` for a platform token and conversely.

Platform admins manage these tokens; club admins, space owners and API tokens are refused.

```graphql
query PlatformApiTokens {
  platformApiTokens {
    id
    name
    access
    hint
    createdBy
    createdAt
    lastUsedAt
    expiresAt
    blockedAt
    status
  }
}
mutation CreatePlatformApiToken($name: String!, $access: ClubApiTokenAccess!, $expiresAt: String) {
  createPlatformApiToken(name: $name, access: $access, expiresAt: $expiresAt) {
    secret
    token {
      id
      name
    }
  }
}
mutation SetPlatformApiTokenBlocked($id: ID!, $blocked: Boolean!) {
  setPlatformApiTokenBlocked(id: $id, blocked: $blocked) {
    id
    status
    blockedAt
  }
}
mutation DeletePlatformApiToken($id: ID!) {
  deletePlatformApiToken(id: $id)
}
```

Names, expiration dates, blocking and deletion follow the rules of club tokens; a platform
token keeps a single `read` or `write` access instead of per-area permissions. The platform
holds at most 20 tokens, and creations, blockings, unblockings and deletions are recorded in the [platform history](/en/graphql/platform#platform-history).
