# API reference

> The Alaskan for Business API (v1). Signing in with email, linking a browser or an iPhone, workspaces and dogs, Decide and undo, the Ledger and its CSV export, templates and workflows.

Canonical: https://alaskanlabs.com/docs/api

The web app and the iPhone app use one API. Every call is a `POST` with a JSON body unless it says otherwise. Errors are `{error}` with a 4xx status.

## Who's asking

Every `/v1/workspace*` and `/v1/decisions*` call (and `/v1/link/list`, `/revoke`, `/join`, `/v1/auth/me` and `/signout`) is made as a person:

- the **iPhone app** sends `X-Alaskan-User`,
- a **browser** sends `Authorization: Bearer <web token>`, from an email sign-in or from a phone that linked it.

Neither, or a revoked, signed-out or unknown token, is a `401`. A person without a workspace gets `404` from everything but `/v1/workspace` and `/v1/workspace/create`. Browsers may call these paths from the web app's own origin (CORS); no cookies are involved.

## Signing in on the web

- `POST /v1/auth/email/start {email}` (no auth) → `{ok: true, email, expiresAt}`. Emails a sign-in link and a 6-digit code, valid for 15 minutes, once. At most 5 an hour to one address and 20 an hour from one network (`429`); `400` for something that isn't an email.
- `POST /v1/auth/email/verify {token}` or `{email, code}` → `{token, email, isNew}`: a web token. The first sign-in for an address makes its account (`isNew: true`); later ones are the same person, on any browser. `410` for a used or expired link or code, `401` for a wrong code; five wrong codes end that sign-in (`429`).
- `POST /v1/auth/me` → `{kind: "email", email}` for an account made with an email, `{kind: "phone"}` for a browser signed in as a phone.
- `POST /v1/auth/signout` → `{ok: true}`; that token is a `401` from then on.

Links, codes and web tokens are stored only as hashes.

## Linking a browser to a phone

- `POST /v1/link/start {label?}` (no auth) → `{code, display, expiresAt, pollToken}`. `code` is 8 characters with no 0, O, 1 or I; `display` is the same as `ABCD-2345`. Valid for 10 minutes, once. `label` names the browser in the phone's list (by default from its User-Agent, "Chrome on Mac").
- `POST /v1/link/confirm {code, name?}` (the phone only) → `{ok: true, label}`. Any case, with or without the dash. `404` for a wrong code, `409` used, `410` expired, `403` from a browser.
- `POST /v1/link/poll {pollToken}` → `{status: "waiting" | "linked" | "expired", token?, name?}`. Poll every couple of seconds. The token comes once; a poll after that says `expired`.
- `POST /v1/link/list` → `{browsers: [{id, label, createdAt, lastSeenAt}]}`.
- `POST /v1/link/revoke {id}` → `{ok: true}`; that browser's token is a `401` from then on.

Poll tokens and web tokens are stored only as SHA-256 hashes.

## Bringing an iPhone into a workspace

- `POST /v1/link/join` (someone in the workspace, usually from the web) → `{code, display, expiresAt, pollToken}`: the same kind of code, valid 10 minutes, once. The web shows it with a QR code.
- The phone types it where it types any browser code and sends the usual `POST /v1/link/confirm {code, name?}`. For a join code, the phone's person joins the workspace as a `member` → `{ok: true, label, joined: {workspace}}`. `409` if the phone already belongs to another workspace (one per person in v1).
- `POST /v1/link/join/poll {pollToken}` → `{status: "waiting" | "joined" | "expired", member?: {id, name, role}}`.
- On the phone, the join shows in `/v1/link/list` as "<workspace> on the web"; revoking it takes the phone out of the workspace (an owner is never removed).

## Workspace

- `POST /v1/workspace` → `{workspace: Workspace | null}`.
- `POST /v1/workspace/create {name, template?, ownerName?, timeZone?}` → `{workspace}`, from a team template (default `starter`) with its dogs, its workflows installed and sample work. `timeZone` is IANA (default UTC). `409` if the person already has a workspace; one per person in v1.
- `POST /v1/workspace/dog {id, rules?, name?, routines?: [{id, enabled}]}` → `{dog}`. `rules` replaces all three lists.
- `POST /v1/workspace/routine {dogId, id, enabled}` → `{dog}`.
- `POST /v1/workspace/hire {template, dogId}` → `{dog}`: adds that team template's dog with its job, rules and routines. Up to 12 dogs; `409` when a dog of that name is on the team.
- `POST /v1/workspace/turn {dogId, text}` → `{messages: [Message]}`: what was typed and the dog's reply (with `decisionId` when it proposed something).
- `POST /v1/workspace/messages {dogId}` → `{messages: [Message]}`, oldest first, the last 100.
- `POST /v1/workspace/sessions {dogId?}` → `{sessions: [Session]}`, newest first.
- `POST /v1/workspace/computer` → `{computer: Computer}`; `/computer/start`, `/computer/stop`.

## Decisions and the Ledger

- `POST /v1/decisions {status?: "pending" | "all", dogId?}` → `{decisions: [Decision], counts: {pending, today}}`. `pending` (the default) is the queue, oldest first; `all` is newest first.
- `POST /v1/decisions/decide {id, option?, typed?}` → `{decision}`. `option` is an option's `key` (1–4). The decision is `declined` when that option's `effect` is `decline`, otherwise `approved`. A typed answer is kept exactly as typed and counts as `approved`. `undoUntil` is 10 seconds on. `409` if it's already decided or expired.
- `POST /v1/decisions/undo {id}` → `{decision}`, only before `undoUntil`. A person's answer goes back to `pending`; a rule's becomes `undone`.
- `POST /v1/decisions/outcome {id, status: "good" | "bad" | "unknown", note?}` → `{decision}`, on a decided one.
- `POST /v1/decisions/events {id}` → `{events: [{id, at, actor: {kind: "person" | "rule" | "dog" | "system", name}, action, detail}]}`: the decision's audit trail, oldest first. Append-only.
- `GET /v1/decisions/export.csv` → the Ledger as CSV. See [The Ledger](/docs/ledger#export).

## Templates and workflows

- `GET /v1/templates` (no auth) → `{teams: [...], workflows: [...]}`: every team template with its dogs, and every workflow template with its trigger, node count and apps.
- `GET /v1/templates/team/<slug>`, `GET /v1/templates/workflow/<slug>` → the template itself.
- `POST /v1/workspace/workflows` → `{workflows: [{id, slug, name, summary, for, dog, dogId, trigger, enabled, installedAt, nodes, apps, lastRun}]}`.
- `POST /v1/workspace/workflows/install {slug}` → `{workflow}`. Installing twice keeps the first.
- `POST /v1/workspace/workflows/run {id}` → `{session}`, as it stands when the run finishes or waits at a Decide.

## Alaskan bots

Bots built by others: the public marketplace (`GET /v1/marketplace/bots`), adding, running and removing a bot in a workspace (`/v1/workspace/bots/*`), and the creator calls (`/v1/creators/*`). They're all in the [Alaskan bot reference](/docs/bot-reference#the-api).

## Shapes

```
Workspace { id, name, plan: "trial" | "business", sample: boolean, template, timeZone, createdAt,
            you: Member, members: [Member], dogs: [Dog], computer: Computer,
            dashboard: {views: [View, with each metrics item's value]}, counts: {pending, today} }
Member    { id, name, role: "owner" | "member", app: "web" | "iphone" }
Dog       { id, key, name, job, breed: "malamute" | "husky" | "kleekai",
            coat: "aurora" | "ember" | "glacier" | "twilight", lead: boolean, tagline, persona,
            status: {working, needsYou, queued}, preview, lastActivityAt, screen,
            rules: {justDo: [string], ask: [string], never: [string]},
            routines: [{id, title, when, nextAt, enabled}], connectors: [string],
            hoursThisWeek }
Session   { id, dogId, title, status: "running" | "needs_you" | "queued" | "done", screen,
            costCents, startedAt, endedAt?, steps: [{at, kind: "look" | "think" | "act" | "result",
            text}], sample, workflowId?, runId? }
Decision  { id, dogId, kind: "send" | "update" | "book" | "spend" | "publish" | "other",
            title, summary, source, items: [{label, field, from?, to, flagged?}],
            options: [{key: 1–4, label, detail, effect: "approve" | "decline", value?}], reasoning,
            evidence: [{label, url?}], thread: [{from, at, text}], status: "pending" | "approved" |
            "declined" | "auto" | "undone" | "expired", chosen?, typed?, decidedBy?: {kind:
            "person" | "rule", name}, decidedAt?, undoUntil?, expiresAt?, outcome?: {status:
            "pending" | "good" | "bad" | "unknown", note, checkAt?, checkedAt?}, createdAt, sample,
            workflowId?, runId? }
Computer  { provider, state: "demo" | "off" | "starting" | "on", screens: [{n, dogId, title,
            activity}], costPerHourCents, hoursThisWeek, note?, error? }
Message   { id, role: "user" | "dog", dogId, text, at, decisionId?, sample? }
```

`value` on an option is a workflow template's own key for it, when that isn't its number. A member's `app` is where they came in: an email account on the web, or the iPhone app. `decidedBy.name` is a person's name, or the rule with its kind (`Just do: Refunds up to $50`). `screens[].activity` is the dog's running session's latest step.

## Limits

A workspace's people may send its dogs 400 messages a UTC day (fair use); past that, the dogs say so until midnight UTC.
