API Keys
Define route-access scopes in Inspire and manage actual tokens through the CLI.
The apiKeys block defines which machine-access scopes your app supports. Routes reference scope names via auth: apiKey(scope). The platform handles token lifecycle.
Basic Shape
apiKeys {
scopes: {
read: @system("api_read") {
@grant read on Project
},
write: @system("api_write") {
@grant read, write on Project
},
}
}Each scope names a machine capability. Routes require that capability directly:
routes("/v1", auth: apiKey(read)) {
GET /projects -> listProjectsRoute
}
routes("/v1", auth: apiKey(write)) {
POST /projects -> createProjectRoute
}What The Block Does
The apiKeys block defines:
- Which scope names exist
- What each scope is allowed to do
- Which routes may use those scopes
Token creation, listing, rotation, and revocation happen through the CLI or server SDK.
Scope Design
Keep scopes narrow and task-shaped.
apiKeys {
scopes: {
read: @system("api_read") {
@grant read on Project
@grant read on Task
},
importer: @system("api_importer") {
@grant read on Project
@grant read, write on Task
},
admin: @system("api_admin") {
@grant read, write, delete on Project
@grant read, write, delete on Task
},
}
}| Scope | Typical use |
|---|---|
read | Reporting, sync, external dashboards |
importer | A single integration workflow |
admin | Rare; broad access |
Lifecycle: CLI
kizaki api-keys create --scope read --name "Mobile app"
kizaki api-keys create --scope importer --name "CRM sync" --expires 90d
kizaki api-keys list
kizaki api-keys list --scope read
kizaki api-keys revoke <id>
kizaki api-keys rotate <id> --grace-period 24h- The plaintext token is shown only on create and rotate
rotatecan keep the old token alive briefly so integrations have time to switch
Lifecycle: Server Code
Let customers create their own integration keys through exposed functions:
import { getContext } from "@kizaki/sdk";
/** @expose */
export async function createReportingKey(name: string) {
const { client } = getContext();
return client.apiKeys.create({
scope: "read",
name,
expiresIn: 60 * 60 * 24 * 90,
metadata: { createdFrom: "settings" },
});
}
/** @expose */
export async function rotateReportingKey(keyId: string) {
const { client } = getContext();
return client.apiKeys.rotate(keyId, {
gracePeriod: 60 * 60 * 24,
});
}
/** @expose */
export async function listIntegrationKeys() {
const { client } = getContext();
return client.apiKeys.list();
}What Callers Get Back
On create or rotate, the returned object includes the plaintext token once:
const key = await client.apiKeys.create({
scope: "read",
name: "Data warehouse sync",
});
key.id;
key.scope;
key.name;
key.token; // plaintext, shown once
key.createdAt;
key.expiresAt;On list, only metadata is returned. No token values.
const keys = await client.apiKeys.list({ scope: "read" });
// Each item includes metadata only.
// No token values are returned from list.API Keys Vs kizaki keys
These are different systems:
api-keysare app-level credentials for your declared HTTP routeskeysare platform or CI credentials for working with Kizaki itself
If the credential calls your app's routes, it belongs in apiKeys.
Recommended Pattern
- Keep scopes narrow and named after real capabilities
- Prefer several small scopes over one broad key
- Treat
admin-style keys as exceptional - Rotate keys with a grace window when integrations cannot switch instantly
- Manage customer-facing keys through exposed functions
Related docs: