Kizaki
ReferenceInspire

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
    },
  }
}
ScopeTypical use
readReporting, sync, external dashboards
importerA single integration workflow
adminRare; 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
  • rotate can 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-keys are app-level credentials for your declared HTTP routes
  • keys are platform or CI credentials for working with Kizaki itself

If the credential calls your app's routes, it belongs in apiKeys.

  • 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:

On this page