# REST API

Every /api/v1 endpoint for tours and beacon tooltips, with request and response shapes.

Base URL: `https://guidemark.co/api/v1`. Authenticate every request with an [API key](/docs/agents/api-keys):

```bash
-H "Authorization: Bearer gm_…"
```

A key belongs to one project, so endpoints never take a project ID. Request and response bodies are JSON. Errors are `{ "error": "…" }`; see [Errors and limits](/docs/reference/errors-and-limits). Permissions follow [Drafts and publishing](/docs/agents/drafts-and-publishing).

The same API is described in OpenAPI 3.1 at [`/api/v1/openapi.json`](https://guidemark.co/api/v1/openapi.json): every endpoint, its scope, request body, response shape and error codes. Point a coding agent or an OpenAPI client generator at it. It needs no key.

## Project

### `GET /project`

```json
{
  "id": "f40f040d-…",
  "name": "Ledgerloop",
  "domain": "https://ledgerloop-demo.pages.dev",
  "agentPublishPolicy": "drafts_only",
  "key": { "scopes": ["read", "write_drafts"], "canPublish": false }
}
```

`canPublish` is true only when the key has `publish` **and** the policy is `publish`.

## Tours

A tour in responses:

```json
{
  "id": "d127bb48-…",
  "shortId": "mYYDr",
  "title": "Pricing: pick your plan",
  "pageUrl": "/pricing",
  "status": "draft",
  "playBehavior": "first_time",
  "showPlaybackButton": false,
  "createdAt": "2026-10-28T15:50:17Z",
  "updatedAt": "2026-10-28T15:50:17Z",
  "steps": [
    { "target": "#plan-studio", "content": "Most teams start here.", "action": "click", "actionValue": null, "name": null }
  ]
}
```

`status` is `published` (live) or `draft`. `steps` appear only when fetching one tour or after a write.

| Method and path                  | Scope             | Does                                                                                                |
| -------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------- |
| `GET /tours`                     | read              | All tours, drafts included, newest change first, without steps                                      |
| `POST /tours`                    | write_drafts      | Create a tour with all its steps; a draft unless `"publish": true` (needs publishing rights). `201` |
| `GET /tours/:id`                 | read              | One tour with its steps                                                                             |
| `PUT /tours/:id`                 | write_drafts      | Replace title, `pageUrl` and **all** steps. Live tours need publishing rights                       |
| `DELETE /tours/:id`              | write_drafts      | Delete. Live tours need publishing rights                                                           |
| `POST /tours/:id/publish`        | publishing rights | Put live; switches off the page's other live tour                                                   |
| `POST /tours/:id/unpublish`      | publishing rights | Back to draft                                                                                       |
| `POST /tours/:id/request-review` | write_drafts      | Email the owner to review this draft ([details](/docs/agents/review-requests))                      |

### Tour body (`POST`, `PUT`)

| Field                 | Type                         | Rules                                                                                      |
| --------------------- | ---------------------------- | ------------------------------------------------------------------------------------------ |
| `title`               | string                       | 3–200 characters                                                                           |
| `pageUrl`             | string                       | A path on the project's domain: starts with `/`, no spaces, no `..`, up to 2048 characters |
| `steps`               | array                        | Up to 200 steps, in order                                                                  |
| `steps[].target`      | string                       | CSS selector, required, up to 4000 characters                                              |
| `steps[].content`     | string                       | Step text, plain text (HTML tags show literally), up to 20000 characters                   |
| `steps[].action`      | `click` \| `hover` \| `type` | Default `click`                                                                            |
| `steps[].actionValue` | string \| null               | For `type`: the text to type                                                               |
| `steps[].name`        | string \| null               | Short label, up to 200 characters                                                          |
| `publish`             | boolean                      | `POST` only. Default `false`                                                               |

```bash
curl -X POST https://guidemark.co/api/v1/tours \
  -H "Authorization: Bearer gm_…" -H "Content-Type: application/json" \
  -d '{"title":"Pick your plan","pageUrl":"/pricing","steps":[{"target":"#plan-studio","content":"Most teams start here."}]}'
```

## Beacon tooltips

A tooltip in responses:

```json
{
  "id": "…", "title": "Export", "content": "Downloads every invoice as CSV.",
  "targetSelector": "[data-testid=export-csv]", "pageUrl": "/invoices",
  "trigger": "beacon", "delivery": "always", "status": "draft",
  "createdAt": "…", "updatedAt": "…"
}
```

| Method and path                            | Scope             | Does                                                                      |
| ------------------------------------------ | ----------------- | ------------------------------------------------------------------------- |
| `GET /beacons`                             | read              | All tooltips, drafts included                                             |
| `POST /beacons`                            | write_drafts      | Create; a draft unless `"publish": true` (needs publishing rights). `201` |
| `PATCH /beacons/:id`                       | write_drafts      | Change any fields. Live tooltips need publishing rights                   |
| `DELETE /beacons/:id`                      | write_drafts      | Delete. Live tooltips need publishing rights                              |
| `POST /beacons/:id/publish` · `/unpublish` | publishing rights | Put live or back to draft                                                 |
| `POST /beacons/:id/request-review`         | write_drafts      | Email the owner to review this draft                                      |

### Tooltip body

| Field            | Type                                  | Rules                                          |
| ---------------- | ------------------------------------- | ---------------------------------------------- |
| `title`          | string                                | Required on create, up to 200 characters       |
| `content`        | string                                | Required on create, up to 5000 characters      |
| `targetSelector` | string                                | Required on create, up to 4000 characters      |
| `pageUrl`        | string \| null                        | A path; `null` or empty shows it on every page |
| `trigger`        | `beacon` \| `hover` \| `click`        | Default `beacon`                               |
| `delivery`       | `always` \| `first_time` \| `session` | Default `always`                               |
| `publish`        | boolean                               | `POST` only. Default `false`                   |

Publishing is never a field on update: use the publish and unpublish endpoints.
