Skip to content

API keys ​

API keys are bearer tokens for non-interactive access - scripts, CI pipelines, cron jobs, and the automation / MCP endpoint. Unlike a login JWT, a key does not expire, and you can scope it down to exactly what it needs.

All endpoints need a bearer token (JWT, or an API key that itself holds apikeys permissions). Listing needs apikeys:read, creating apikeys:create, revoking apikeys:delete.

Token format ​

A key is the literal prefix mck_ followed by 64 hex characters. Send it exactly like any other bearer token:

http
Authorization: Bearer mck_1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809

The API decides whether you handed it a JWT or a key by looking for that mck_ prefix - same header either way.

List keys ​

http
GET /api/api-keys/

Returns your account's keys as a plain array. The secret is never included - only metadata:

json
[
  {
    "id": "6a4d5b83b7006ccae6391810",
    "accountId": "6a4d5b83b7006ccae6391800",
    "userId": "6a4d5b83b7006ccae6391801",
    "name": "ci-deploy",
    "prefix": "mck_1a2b3c4d",
    "allowedIps": ["203.0.113.4", "198.51.100.0/24"],
    "permissions": ["pods:read", "pods:update"],
    "lastUsedAt": "2026-07-16T14:02:11Z",
    "lastUsedIp": "203.0.113.4",
    "createdAt": "2026-07-01T09:12:00Z",
    "updatedAt": "2026-07-16T14:02:11Z"
  }
]

prefix is the first twelve characters of the key, which is enough to tell two keys apart and no use to anyone else. lastUsedAt and lastUsedIp are refreshed at most once a minute, so they will lag a busy key by up to sixty seconds. A key that has never been used omits both.

Create a key ​

http
POST /api/api-keys/
json
{
  "name": "ci-deploy",
  "allowedIps": ["203.0.113.4", "198.51.100.0/24"],
  "permissions": ["pods:read", "pods:update"]
}
FieldRequiredNotes
nameyesA label so you remember what it is for.
allowedIpsnoList of IPs or CIDR ranges. If set, the key only works from those addresses.
permissionsnoArray of resource:action (or resource:*) tokens. Leave it empty for full access, capped to your own grants.

Returns 201 with the plaintext token. This is the only time you will ever see it:

json
{
  "id": "6a4d5b83b7006ccae6391810",
  "accountId": "6a4d5b83b7006ccae6391800",
  "userId": "6a4d5b83b7006ccae6391801",
  "name": "ci-deploy",
  "prefix": "mck_1a2b3c4d",
  "allowedIps": ["203.0.113.4", "198.51.100.0/24"],
  "permissions": ["pods:read", "pods:update"],
  "createdAt": "2026-07-01T09:12:00Z",
  "updatedAt": null,
  "token": "mck_1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809"
}

Copy it now

We store only a hash of the token, so we can't show it to you again. Copy it into your secret manager the moment you create it. Lost it? Delete the key and make a new one.

Revoke a key ​

http
DELETE /api/api-keys/:keyId
json
{ "message": "API key deleted" }

Deleting a key revokes it - the next request using it fails with 401. There is no un-delete and no expiry to wait for, so this is how you retire a key.

What a key can and cannot do ​

  • Keys never expire. There is no expiry field to set and none to read. They work until you revoke them.
  • A key can never exceed your own permissions. Its effective access is the overlap of what you asked for and what you are actually allowed to do, recomputed on every request. If your own grants shrink later, the key shrinks with them, within a minute.
  • A key is always a member, never the Owner. Even a key created by the Owner is evaluated against a permission list rather than getting the Owner's blanket pass.
  • Some things are off-limits to keys entirely. A key can never hold team, roles, or account permissions - inviting teammates, editing roles, and changing account settings are interactive, owner-level jobs, not automation. Ask for them anyway and they are quietly dropped from the key's scope. See Team & roles for the full permission catalogue.
  • IP binding is optional but worth it for a key that lives in one place, like a CI runner or a fixed server. A request from an address outside allowedIps returns 403 with "This API key is not allowed from your IP address".
  • A key dies with its creator's access. Remove the person who made it from the team and their keys are revoked in the same operation. Disable them and their keys return 403 instead.

Same key, MCP too ​

The same key authenticates the automation / MCP endpoint, so an MCP client (Claude and others) can drive your account under the exact permissions you granted. Scope the key down, IP-bind it, and you have a safe automation credential.

Validation errors ​

ProblemStatusMessage
Missing name400Key name is required
An allowedIps entry is not a valid IP400Invalid IP address: 203.0.113.999
An allowedIps entry is not a valid CIDR400Invalid CIDR: 198.51.100.0/64
permissions contains an unknown token400Unknown or disallowed permission: pods:deploy

Example ​

Create a key scoped to reading and restarting pods, locked to one CI runner:

bash
curl -X POST https://cloud-api.microapps.io/api/api-keys/ \
  -H "Authorization: Bearer $MICROAPPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ci-deploy",
    "allowedIps": ["203.0.113.4"],
    "permissions": ["pods:read", "pods:update"]
  }'

Then use the returned token:

bash
curl https://cloud-api.microapps.io/api/pods/ \
  -H "Authorization: Bearer mck_1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809"

Built for the long tail.