Auth & middlewares

There are two ways to shape a request before it goes out (and inspect what comes back):

  • Declarative auth: pick bearer or API key, point it at a variable. No code.
  • Scripts: TypeScript functions API Craft runs in a sandbox: custom auth (compute a token dynamically) and middlewares (before/after hooks that mutate the request or the response).

Scripts share one small typed SDK and run inside the app: there is nothing to install to run them. See The script SDK below for editor autocomplete.

Declarative auth

API Craft supports two auth types out of the box: bearer tokens and API keys. Auth can be set as an API-wide default (merged into new requests) or overridden on an individual instance. Use {{variables}} so the actual secret comes from an environment, never the committed file.

API defaults

Set a default once and it's applied when creating new requests:

# Bearer token
craftr api set auth bearer "{{apiKey}}" -a petstore-api

# API key (header or query). Petstore reads the `api_key` header
craftr api set auth api-key --in header --name api_key --value "{{apiKey}}" -a petstore-api

# Clear it
craftr api unset auth -a petstore-api

You can do the same in the app's API settings.

Per-request auth

An instance's auth field overrides the default. In the request editor's Auth tab, pick the type and reference a variable for the secret.

The request editor Auth tab with bearer and API-key options

On disk:

{ "type": "bearer", "token": "{{apiKey}}" }
{ "type": "apiKey", "in": "header", "name": "api_key", "value": "{{apiKey}}" }

The secret variable (apiKey) is declared in the environment schema as secret: true, so its value lives in the gitignored *.secrets.env file.

Custom auth

When a static or {{variable}} token isn't enough (you need to call a login endpoint, sign a request, or refresh a credential), set the auth type to custom and point it at a script.

The script is an exported function in the API's middlewares/ folder. It receives the request before it's sent and mutates it in place:

// apis/petstore-api/middlewares/petstore-auth.ts
import type { BeforeRequestContext } from '@apicrafthq/script-sdk';

export async function fetchToken(ctx: BeforeRequestContext) {
  let token = await ctx.cache.get<string>('token');
  if (!token) {
    const res = await ctx.http.post('https://petstore.example/auth', {
      body: { key: ctx.var('apiKey') },
    });
    token = (res.body as { token: string }).token;
    await ctx.cache.set('token', token);
  }
  ctx.request.headers['Authorization'] = `Bearer ${token}`;
}

On disk the request references it by name and export:

{ "type": "custom", "ref": { "name": "petstore-auth", "exportName": "fetchToken" } }

name is the file (without .ts), exportName the exported function inside it; they are independent, so one file can hold several auth or middleware functions.

The context gives you ctx.var() to read the environment, ctx.http for auxiliary calls, ctx.cache for a persistent key/value store shared across the project's scripts, and ctx.logger for output. Full types are in The script SDK.

Middlewares

A middleware is a hook in the request pipeline:

  • before runs before the request is sent. Mutate ctx.request (method, url, headers, query, body) in place.
  • after runs once the response is back. ctx.request is read-only; mutate ctx.response (status, statusText, headers, body).

Like custom auth, middlewares are exported functions living in the API's middlewares/ folder; one file can export several:

// apis/petstore-api/middlewares/tracing.ts
import type { BeforeRequestContext, AfterResponseContext } from '@apicrafthq/script-sdk';

export async function addRequestId(ctx: BeforeRequestContext) {
  ctx.request.headers['X-Request-Id'] = crypto.randomUUID();
}

export async function warnOnServerError(ctx: AfterResponseContext) {
  if (ctx.response.status >= 500) {
    ctx.logger.warn(`Petstore returned ${ctx.response.status} for ${ctx.request.url}`);
  }
}

Attaching middlewares

A request carries an ordered list per phase. On disk:

{
  "middlewares": {
    "before": [{ "name": "tracing", "exportName": "addRequestId" }],
    "after": [{ "name": "tracing", "exportName": "warnOnServerError" }]
  }
}

In the app, use the middleware chain editors. You can attach a chain at four scopes:

  • API / resource / operation defaults: seeded into new request instances (same model as auth defaults). Editing a default doesn't retro-fit existing instances.
  • Request: the specific instance's own chain.

Within a phase, middlewares run top to bottom in the listed order; reorder them in the editor.

Execution rules

  • Each middleware has a 5-second timeout. Exceeding it fails the request.
  • A middleware that throws aborts the whole request: a before hook stops it from being sent; an after hook fails the run after the response arrived.
  • Scripts only run in a trusted project. The first time you run a request that uses a script, API Craft asks you to approve the project.

Middlewares and custom auth run real code from the project. Only approve projects you trust; approval lets every script in the project execute.

The script SDK

Custom auth and middlewares import their types from @apicrafthq/script-sdk. This package is types only: the runtime is built into API Craft, so you don't need to install anything to run scripts. Install it only for editor autocomplete and type-checking while you write them.

The context passed to every script:

MemberAvailable inPurpose
ctx.var(name)allRead a variable from the active environment. Typed from the env schema.
ctx.httpallHTTP client (get/post/…) for auxiliary calls: login, signing, polling.
ctx.cacheallPersistent key/value store, shared across the project's scripts.
ctx.loggeralldebug/info/warn/error. Use this; console is not allowed.
ctx.requestbefore / authThe outgoing request. Mutable in before hooks.
ctx.responseafterThe response. Mutable in after hooks.

console is rejected at load time: a script that calls console.log won't run. Use ctx.logger instead.

Installing the types

When you create a script, API Craft generates a tsconfig.json and a .api-craft/env.d.ts (which types ctx.var() from your environment schema) inside the API folder. Both are gitignored; they are local editor tooling, not part of the project. Add the SDK the same way: as a dev-only dependency, wherever your editor will find it.

TypeScript projects: add it as a dev dependency at the repo root:

npm install -D @apicrafthq/script-sdk

Your existing node_modules is above the API folder, so the editor resolves it with no extra setup.

Everything else (a non-JS backend, or a standalone API Craft project): install it in the apis/ directory so a stray package.json doesn't land at your repo root:

cd apis
npm install -D @apicrafthq/script-sdk

One install under apis/ covers every API beneath it. Gitignore the tooling so it stays local:

# apis/.gitignore
node_modules/
package.json
package-lock.json

This is purely a developer-experience step. Middlewares and custom auth run fine with nothing installed; the SDK is bundled into API Craft. Skip it and you only lose autocomplete.

Next

Chain requests together and assert on responses in writing workflows.