Skip to content

API keys API ​

Keys for this API. Part of the API reference: setup, shared types and errors are there.

List API keys ​

GET /keys · scope keys:read

Never their secrets. With the scopes and presets there are.

ParameterInRequiredDescription
sitequerynoThe site (a key always uses its own).

Request

bash
curl "$HELPPUFF_URL/api/v1/keys" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY"
ts
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/keys`, {
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
  },
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ListAPIKeysResponse;

Response 200

json
{
  "keys": [
    {
      "id": "k7m3p9q2r4s8",
      "name": "Website backend",
      "prefix": "hp_live_k7m3p9q2r4s8",
      "site": "acme",
      "scopes": [
        "chat",
        "leads:read"
      ],
      "allowIps": [],
      "ratePerMinute": 120,
      "createdBy": "owner@acme.example",
      "createdAt": 1760000000000,
      "expiresAt": null,
      "lastUsedAt": null,
      "revokedAt": null
    }
  ],
  "scopes": [
    {
      "scope": "chat",
      "description": "Talk to the assistant as a visitor…"
    }
  ],
  "presets": [
    {
      "id": "chat",
      "label": "Chat only",
      "scopes": [
        "chat"
      ]
    }
  ]
}
ts
type ListAPIKeysResponse = {
  keys: Array<{
    id: string;
    name: string;
    prefix: string;
    site: string;
    scopes: string[];
    allowIps: string[];
    ratePerMinute: number;
    createdBy: string;
    createdAt: number;
    expiresAt: number | null;
    lastUsedAt: number | null;
    revokedAt: number | null;
  }>;
  scopes: Array<{
    scope: string;
    description: string;
  }>;
  presets: Array<{
    id: string;
    label: string;
    scopes: string[];
  }>;
};

Create an API key ​

POST /keys · scope keys:write

The full key is in this answer only: store it in your secrets manager. A key cannot create a key with scopes it does not have.

Body fieldRequiredDescription
nameyesWhat the key is for.
presetnochat, crm, read or full. Or, instead:
scopesnoA list of scopes (see the API page).
expiresInDaysno1–3650; absent or null: never.
allowIpsnoIP addresses or CIDR ranges it may be used from.
ratePerMinutenoRequests a minute (default 120).

Request

bash
curl -X POST "$HELPPUFF_URL/api/v1/keys" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Website backend",
  "preset": "chat",
  "expiresInDays": 365
}'
ts
type CreateAPIKeyRequest = {
  /** What the key is for. */
  name: string;
  /** `chat`, `crm`, `read` or `full`. Or, instead: */
  preset?: string;
  /** A list of scopes (see the API page). */
  scopes?: string;
  /** 1–3650; absent or null: never. */
  expiresInDays?: number;
  /** IP addresses or CIDR ranges it may be used from. */
  allowIps?: string;
  /** Requests a minute (default 120). */
  ratePerMinute?: string;
};

const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/keys`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Website backend',
    preset: 'chat',
    expiresInDays: 365
  } satisfies CreateAPIKeyRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as CreateAPIKeyResponse;

Response 201

json
{
  "key": "hp_live_k7m3p9q2r4s8_Zx1…",
  "id": "k7m3p9q2r4s8",
  "name": "Website backend",
  "prefix": "hp_live_k7m3p9q2r4s8",
  "site": "acme",
  "scopes": [
    "chat"
  ],
  "allowIps": [],
  "ratePerMinute": 120,
  "createdBy": "owner@acme.example",
  "createdAt": 1760000000000,
  "expiresAt": 1791536000000,
  "lastUsedAt": null,
  "revokedAt": null
}
ts
type CreateAPIKeyResponse = {
  key: string;
  id: string;
  name: string;
  prefix: string;
  site: string;
  scopes: string[];
  allowIps: string[];
  ratePerMinute: number;
  createdBy: string;
  createdAt: number;
  expiresAt: number;
  lastUsedAt: number | null;
  revokedAt: number | null;
};

Errors

StatusCodeWhen
403forbiddenThe calling key would hand out scopes it does not have.

Revoke an API key ​

DELETE /keys/:id · scope keys:write

Refused everywhere within 30 seconds. :id is the key's id or prefix.

ParameterInRequiredDescription
idpathyesThe record's id.
sitequerynoThe site (a key always uses its own).

Request

bash
curl -X DELETE "$HELPPUFF_URL/api/v1/keys/ID" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY"
ts
const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/keys/${id}`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
  },
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as RevokeAPIKeyResponse;

Response 200

json
{
  "id": "k7m3p9q2r4s8",
  "name": "Website backend",
  "prefix": "hp_live_k7m3p9q2r4s8",
  "site": "acme",
  "scopes": [
    "chat",
    "leads:read"
  ],
  "allowIps": [],
  "ratePerMinute": 120,
  "createdBy": "owner@acme.example",
  "createdAt": 1760000000000,
  "expiresAt": null,
  "lastUsedAt": null,
  "revokedAt": 1760000060000,
  "revoked": true
}
ts
type RevokeAPIKeyResponse = {
  id: string;
  name: string;
  prefix: string;
  site: string;
  scopes: string[];
  allowIps: string[];
  ratePerMinute: number;
  createdBy: string;
  createdAt: number;
  expiresAt: number | null;
  lastUsedAt: number | null;
  revokedAt: number;
  revoked: boolean;
};

MIT licensed. Runs in your own Cloudflare account.