Documentation
Set up Kaja
Install Kaja, connect an API, and run your first call.
- Desktop
- Install Kaja and add your APIs from the sidebar.
- Docker
- Put your APIs, variables and scripts in kaja.json, and run the container.
Installation
Install Kaja from the Mac App Store.
Apps
An app is one API Kaja can call. Its services and methods appear in the sidebar under the app's name.
Add an app with + beside Apps in the sidebar. Pick the type, then give Kaja the address and the authentication it needs.
- gRPC
- Connect to a server using reflection or .proto files.
- OpenAPI
- Add a spec from a URL, a file, or pasted text.
- Twirp
- Connect to a server using its address and .proto files.
Kaja tests the connection before saving. Headers entered on the form are sent with every call.
If the app won't connect
“The server answered, but doesn't serve the reflection API.”
The gRPC server is reachable, but it does not register the reflection service. Click Use proto files in the banner and point Kaja at a folder of .proto files instead.
“Couldn't parse the document”, or “That URL returned a web page, not an OpenAPI document”
The URL is serving HTML, such as a documentation site or a sign-in redirect, or the file is not valid JSON or YAML. Open the URL in a browser and check that it returns the spec itself. If it needs a credential, switch the source to File or Pasteand give Kaja the document directly.
Run your first call
Once an app is connected, four steps get you a response.
1. Open an app in the sidebar and click one of its methods.
2. Kaja writes a typed call into a new draft and opens it in the editor. Every field of the request is written out, so you can see what the method takes before you change anything.
3. Press ⌘⏎, or click Run at the top right.
4. The run opens below the editor on Calls. Click the call to read the request Kaja sent and the response it got back.
Writing scripts
Scripts let you call APIs with TypeScript. Click a method to create a typed call, or start with an empty script. Press ⌘⏎ to run it. Save the script when you want to use it again.
import { kaja } from "kaja";
import { Theatre } from "theatre";
import { Seating } from "seating";
const { shows } = await Theatre.ListShows({ city: "Chicago" });
kaja.table(["show", "starts"], shows.map((show) => [show.id, show.startsAt]));
await kaja.approve(
Seating.BookSeats({ showId: shows[0].id, seatIds: ["F7", "F8"] }),
);A script is TypeScript with top-level await and one import for each app. Results appear below the editor:
- Calls
- Every request and response.
- Canvas
- Tables, text, questions and anything else the script drew.
- Stats
- Latency and timing for the run.
Scripts also get fetch and console, both bound to the current run. A fetch request appears in Calls like any other call.
Every generated method takes headers as its second argument. Call .withHeaders() when you also need the headers the API answered with.
⌘P finds a method, file or draft by name.
Kaja helpers
A script can import kaja to draw on the run's canvas, ask you a question, or hold a call for approval. The table lists every helper. The entries under it have the detail.
import { kaja } from "kaja";
import { Theatre } from "theatre";
const catalog = kaja.table(["id", "title"], async function* () {
for (let cursor = ""; ; ) {
const page = await Theatre.ListMovies({ cursor });
catalog.total(page.total);
yield* page.movies.map((movie) => [movie.id, movie.title]);
if (!(cursor = page.nextCursor)) return;
}
});import { kaja } from "kaja";
import { Theatre } from "theatre";
const { theaters } = await Theatre.ListTheaters({ city: "" });
const cities = [...new Set(theaters.map((theater) => theater.city))];
const city = await kaja.askSelect(
"Where are you tonight?",
cities.sort().map((name) => ({ label: name, value: name })),
);
const mood = await kaja.askStr("And what do you feel like?");
const party = await kaja.askInt("How many of you?");import { kaja } from "kaja";
import { Theatre } from "theatre";
import { Seating } from "seating";
const { shows } = await Theatre.ListShows({ limit: 1 });
kaja.rateLimit(Seating);
const reads = kaja.table(["read", "remaining"]);
for (let read = 1; read <= 150; read++) {
const { headers } = await Seating.GetSeatMap({
showId: shows[0].id,
}).withHeaders();
reads.row(read, headers["ratelimit-remaining"] ?? "—");
}import { kaja } from "kaja";
import { Theatre } from "theatre";
const report = await kaja.perfTest(
() => Theatre.ListMovies({ limit: 25 }),
{ duration: "12s", concurrency: 6, warmup: "2s", rampUp: "4s" },
);
kaja.text(`p99 ${Math.round(report.latency.p99 ?? 0)} ms`);The editor uses these declarations for autocomplete and inline documentation, and an agent reads the same ones when it writes a script. The whole of it is the kaja module declaration.
Files and drafts
An unsaved script appears under Drafts in the sidebar. Choose Save as file, or press ⌘S, to write it to disk under Files.
Reveal in Finder opens the folder the file is in. Edit a file in another editor and Kaja picks up the change the next time you run it.
Variables
Variables are named values shared by scripts and app configuration. Read one in a script with kaja.variables.NAME. Use ${NAME} in app configuration, either by itself or inside a longer value.
Add variables in the Variables section, { } at the top of the sidebar. Each row has a source:
- Value
- Stored directly in the workspace.
- Keychain
- Stored in the macOS Keychain. Use this for tokens and other secrets.
- Environment
- Read from an environment variable.
Deeplinks
Saved scripts have deeplinks. Right-click the file, or open the menu beside Run, and choose Copy deeplink… (⌘⇧C).
kaja://run/whats-on?city=ChicagoOpen it with open, a Raycast quicklink or a Shortcut.
The deeplink opens the script and fills in its input values. Press ⏎ to run it.
Read the query parameters through kaja.input. Every value arrives as a string. A script run from the editor with nothing to carry gets an empty input:
const city = kaja.input.city ?? (await kaja.askStr("Which city?"));
const { shows } = await Theatre.ListShows({ city });Run with parameters… (⇧⌘⏎) asks for the keys the script reads, then runs it.
Agents
Kaja provides an MCP server for your connected APIs. An agent can inspect the available methods, write TypeScript scripts, and run them through Kaja.
Open the plug menu at the top of the sidebar and turn on the MCP server. Copy the command or configuration shown for your agent.
The server listens on 127.0.0.1:41521.
- list_services
- One TypeScript signature per method, marked read or write.
- describe_method
- The types a method uses, and an example call.
- run_script
- Runs a script. Every request and response comes back in the result.
Scripts an agent creates appear as drafts under the agent's name. Its requests appear in Calls. A call wrapped in kaja.approve waits until you approve it.