# Run a blueprint over the REST API

The five blueprint routes, who may call each one, the request and response shapes, and a curl example from plan to apply.

An API (application programming interface) is a way for one program to ask another to do something. The blueprint API is a set of REST routes: web addresses your program sends a request to, and gets JSON (JavaScript Object Notation) back from. The console and the [command line](/blueprints/cli) use these same routes, so they give the same plan for the same file.

```bash
curl -X POST https://api.console.buildbase.app/api/blueprint/plan \
  -H "Authorization: Bearer $BUILDBASE_API_TOKEN" \
  -H "Content-Type: application/json" \
  --data @request.json
```

> **Note:**
  An admin key: an API key with the owner or admin role, issued as an [admin
  key](/reference/admin-api#key-types). [Before you
  start](/blueprints/overview#before-you-start) shows how to make one. Send it
  in the `Authorization` header, as for the [Admin
  API](/reference/admin-api#authenticating-a-request).


The server is [https://api.console.buildbase.app](https://api.console.buildbase.app) on BuildBase's cloud, or your own server when self-hosted.

## Routes

| Method | Path                      | Who may call it                              | What it does                                              |
| ------ | ------------------------- | -------------------------------------------- | --------------------------------------------------------- |
| `GET`  | `/api/blueprint/schema`   | A role that may read organization settings   | Returns the JSON Schema: every field a blueprint can hold |
| `POST` | `/api/blueprint/validate` | A role that may create organization settings | Checks the file on its own. Changes nothing               |
| `POST` | `/api/blueprint/plan`     | The owner or an admin                        | Compares the file with the organization. Changes nothing  |
| `POST` | `/api/blueprint/apply`    | The owner or an admin                        | Makes the changes, then plans again to check              |
| `GET`  | `/api/blueprint/export`   | The owner or an admin                        | Returns the organization's current setup as a blueprint   |

Plan, apply and export need the owner or admin role, because a blueprint covers the whole console. A custom role is not enough, even one that can see every screen. The owner and admins can call every route.

A request with a missing or wrong key, or a key without the role a route needs, gets `401`. An app-server key gets `403 ADMIN_SCOPE_REQUIRED` on every route here, whatever its role, and so does an app user's credential ([Error codes](/reference/error-codes#admin-routes)). A read-only support session (impersonation) can validate and plan, but apply answers `403`.

## The request body

`validate`, `plan` and `apply` take the same JSON body. Send `source` or `blueprint`, not both.

| Field       | Type    | What it is                                                                                                                                    |
| ----------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `source`    | string  | The file as text. Prefer it: from text, BuildBase can report a key written twice, which a parsed object has already lost. Up to 4 MB          |
| `blueprint` | object  | The same document, already parsed                                                                                                             |
| `env`       | object  | A value for each `${env:NAME}` the file uses, such as `{ "APP_URL": "https://app.acme.example" }`. Up to 200 names. Never stored or sent back |
| `prune`     | boolean | `plan` and `apply`: also delete what the file no longer lists, for the kinds in `policy.prune`. Off when left out                             |
| `planHash`  | string  | `apply` only, and required: the `hash` of the plan you reviewed. A request without it, or with an empty one, is refused with `400`            |

Any other field is refused with `400`.

### Why apply takes the plan hash

The `hash` is a short code that stands for the plan: which resources would change and how. Apply needs the `planHash` of the plan you reviewed. BuildBase makes the plan again and compares the two. If they differ, because someone changed the organization or you changed the file, apply refuses and nothing changes. So the changes you reviewed are the only changes that run. A request without a `planHash` never reaches the plan: it is refused with `400`.

## The responses

Every route answers `200` with the result, even when the file has problems. Read `ok` to know whether it worked.

### validate

{/* json: not a blueprint */}

```json
{
  "ok": false,
  "diagnostics": [
    {
      "level": "error",
      "path": "/auth/clients/web/redirectUrls/0",
      "message": "APP_URL has no value. Set the environment variable APP_URL (in the console, fill in its field), then try again."
    }
  ],
  "env": [{ "name": "APP_URL", "secret": false, "provided": false }]
}
```

- `diagnostics` lists each problem. `level` is `error` or `warning`. `path` points at the place in the file: `/auth/clients/web/redirectUrls/0` is the first entry of `redirectUrls` in the auth client `web`.
- `env` lists each `${env:NAME}` the file uses, whether it is in a secret field, and whether you sent a value.

### plan

{/* json: not a blueprint */}

```json
{
  "ok": true,
  "diagnostics": [],
  "actions": [
    {
      "kind": "app",
      "key": "",
      "address": "app",
      "type": "update",
      "title": "App settings",
      "id": "665f1a2b3c4d5e6f7a8b9c0d",
      "changes": [{ "path": "name", "from": "My app", "to": "Acme" }]
    },
    {
      "kind": "webhooks",
      "key": "crm",
      "address": "webhooks.crm",
      "type": "create",
      "title": "Webhook \"CRM sync\""
    }
  ],
  "summary": {
    "create": 1,
    "update": 1,
    "version": 0,
    "noop": 0,
    "manual": 0,
    "blocked": 0,
    "conflict": 0,
    "unknown": 0,
    "delete": 0,
    "unmanaged": 0
  },
  "hash": "3f2a9c1d0b7e4a65",
  "changes": true,
  "text": "  ~ update    App settings  [app]\n      name: \"My app\" -> \"Acme\"\n  + create    Webhook \"CRM sync\"  [webhooks.crm]\n\nPlan: 1 to create, 1 to update, 0 new versions, 0 to delete, 0 unchanged."
}
```

- `actions` has one entry for each resource the file names, and for each resource of those kinds that the file leaves out. `type` is one of the [plan actions](/blueprints/file-format#reading-a-plan).
- `changes` on an action lists each field that differs, old value first. A secret shows `"secret": true` and never its value. A deploy hook URL or a workflow request's URL or header value shows `"(secret)"` in its place, and a change only there reads as a secret one.
- `notes` on an action, when present, says what to do about it.
- `moves` on an action, when present, is a publish with `versionMoves` on: `holders` is how many a move would reach (`null` when they could not be counted), with `mode`, `noticeDays` and, when known, the console `url` where the move waits for confirmation. It is not part of the `hash`: holders come and go, and apply never moves anyone itself. See [version moves](/blueprints/file-format#version-moves).
- `changes` at the top level is `true` when apply would change something.
- `text` is the plan as the command line prints it.

When `ok` is `false`, the file has errors, `diagnostics` lists them, and nothing was compared.

### apply

{/* json: not a blueprint */}

```json
{
  "ok": true,
  "plan": { "ok": true, "hash": "3f2a9c1d0b7e4a65", "changes": true },
  "steps": [
    {
      "address": "app",
      "type": "update",
      "title": "App settings",
      "status": "done",
      "id": "665f1a2b3c4d5e6f7a8b9c0d"
    },
    {
      "address": "webhooks.crm",
      "type": "create",
      "title": "Webhook \"CRM sync\"",
      "status": "done",
      "id": "6660aa1b2c3d4e5f6a7b8c9d",
      "outputs": { "signingSecret": "the value, shown only here" }
    }
  ],
  "recheck": { "ok": true, "changes": false },
  "text": "..."
}
```

This example shortens `plan` and `recheck`, which are whole plans.

- `steps` has one entry for each change, in the order it ran. `status` is `done`, `failed` or `skipped`. A failed step carries `error`, and `httpStatus` when a request was refused.
- Apply stops at the first failed step. The steps after it are `skipped`, and the changes already made stay.
- `outputs` holds the values shown only once, such as a webhook signing secret. This response is the only place they appear. Store them before you do anything else.
- `move` on a step says what a publish did about its move: `outcome` is `awaiting_confirmation` (it waits for an admin in the console, at `url`, for `holders` holders) or `none` (no move was made). A blueprint never starts, confirms or cancels a move.
- `recheck` is the plan made again after apply. `"changes": false` means the organization now matches the file.

When apply refuses before any step runs, `ok` is `false`, `steps` is empty, there is no `recheck`, and `plan.diagnostics` says why. That happens when the file has errors, or when `planHash` does not match.

Only one apply runs for each organization at a time. A second one answers `409` with a `message` while the first runs. Each apply that changes something, or fails, writes a **Blueprint applied** row to the audit log, with the plan hash, how many steps ran and which failed.

### export

{/* json: not a blueprint */}

```json
{
  "blueprint": {
    "$schema": "https://docs.buildbase.app/schemas/blueprint.v1.json",
    "version": 1,
    "billing": {
      "stripe": {
        "publishableKey": "pk_test_51Hxxxxxxxxxxxxxxxx",
        "secretKey": "${env:BILLING_STRIPE_SECRETKEY}"
      }
    }
  },
  "env": [{ "name": "BILLING_STRIPE_SECRETKEY", "secret": true }],
  "skipped": []
}
```

Secrets are never exported. Each one is a `${env:NAME}` named after where it sits, and `env` lists the names to set before you plan the file. That includes values the console shows but that work as credentials: a deploy hook URL, and the URL and header values of a workflow's HTTP request. Set those to their current values to plan the file unchanged. A part of the console the key cannot read is left out.

Coupons are read from your Stripe account. When Stripe refuses the saved secret key, or does not answer, the export leaves coupons out and names them in `skipped`, each with its `path` (`billing.coupons`) and the server's `reason`; the rest of the organization still exports. A section listed there is not empty: fix the key in Billing > Credentials, then export again.

### schema

The JSON Schema, the same document served at [https://docs.buildbase.app](https://docs.buildbase.app)/schemas/blueprint.v1.json.

## From plan to apply with curl

`curl` sends web requests from a terminal, and `jq` builds and reads JSON. Both come with most Mac and Linux systems, or install [jq](https://jqlang.github.io/jq/) first.

1. Set your key without showing it, as on the [command line page](/blueprints/cli#set-the-two-variables-safely):

   ```bash
   read -rs BUILDBASE_API_TOKEN && export BUILDBASE_API_TOKEN
   ```

2. Build the request from your file and the values it needs:

   ```bash
   jq -n --rawfile source buildbase.blueprint.json \
     --arg key "$STRIPE_SECRET_KEY" \
     '{source: $source, env: {STRIPE_SECRET_KEY: $key}}' > request.json
   ```

3. Plan, print the plan, and keep its hash:

   ```bash
   curl -sS -X POST https://api.console.buildbase.app/api/blueprint/plan \
     -H "Authorization: Bearer $BUILDBASE_API_TOKEN" \
     -H "Content-Type: application/json" \
     --data @request.json > plan.json
   jq -r .text plan.json
   ```

4. Read the plan. If you agree, apply that plan:

   ```bash
   jq --arg hash "$(jq -r .hash plan.json)" '. + {planHash: $hash}' request.json > apply.json
   curl -sS -X POST https://api.console.buildbase.app/api/blueprint/apply \
     -H "Authorization: Bearer $BUILDBASE_API_TOKEN" \
     -H "Content-Type: application/json" \
     --data @apply.json > result.json
   jq -r .text result.json
   ```

5. Move the shown-once values from `result.json` to your secret store, then delete `request.json`, `apply.json` and `result.json`: they hold secrets.

## Next steps

- [File format](/blueprints/file-format) - what each section holds, and every plan action
- [Command line](/blueprints/cli) - the same routes, with less to write
- [Troubleshooting](/blueprints/troubleshooting) - what each message means and what to do
