> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Apps

> Hosted web applications: provisioning, deployments, secrets, custom domains, the app database, and the app's own MCP endpoint.

An **app** is a hosted web application the platform runs for your organization — a static or client-rendered frontend, optionally paired with a managed Postgres backend (`fullstack`). Apps are **org-level** resources, like [domains](/docs/api/domains): they do not belong to an agent, they outlive any session, and by default every agent in the organization can operate every app (narrow that per-agent with the `apps` tool config's `allowed_apps`).

Everything asynchronous — the fullstack backend coming up, a deployment building — advances on read: poll `GET /v1/apps/{id}` or the deployments list until the status is terminal.

## The app object

<ResponseField name="id" type="string">Unique id (e.g. `app_01H...`).</ResponseField>
<ResponseField name="object" type="string">Always `app`.</ResponseField>
<ResponseField name="name" type="string">A lowercase DNS label, unique in the organization.</ResponseField>
<ResponseField name="description" type="string | null">Free text.</ResponseField>
<ResponseField name="type" type="string">`frontend_only` or `fullstack`.</ResponseField>
<ResponseField name="status" type="string">`provisioning`, `active`, or `error`. A `frontend_only` app is `active` at birth; a `fullstack` app is `provisioning` until its database is ready.</ResponseField>
<ResponseField name="url" type="string | null">The app's live URL.</ResponseField>
<ResponseField name="mcp" type="string | null">The path of the MCP endpoint the app itself serves (e.g. `/mcp`), or `null` when it serves none. `fullstack` only. See [App MCP tools](#app-mcp-tools).</ResponseField>
<ResponseField name="error" type="string | null">Set when `status` is `error`.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

## Create an app

`POST /v1/apps` — scope `agents:write`

Idempotent on `name`: creating an app that already exists returns the existing record.

<ParamField body="name" type="string" required>A lowercase DNS label, 63 characters or fewer.</ParamField>
<ParamField body="description" type="string">Optional, up to 500 characters.</ParamField>
<ParamField body="type" type="string">`frontend_only` (default) or `fullstack`. A `fullstack` app gets a managed database; its connection string is pushed into the app's environment automatically when the database is ready.</ParamField>
<ParamField body="mcp" type="string">Optional. The path the app serves an MCP endpoint on — absolute, beginning with `/`, up to 128 characters. `fullstack` only: on a `frontend_only` app it is refused with `400 validation_failed` (param `mcp`), because there is no server to answer on it. See [App MCP tools](#app-mcp-tools).</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/apps \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -d '{ "name": "storefront", "type": "fullstack", "mcp": "/mcp" }'
  ```

  ```typescript TypeScript theme={"system"}
  const app = await vetta.apps.create({ name: "storefront", type: "fullstack", mcp: "/mcp" });
  console.log(app.mcp); // "/mcp"
  ```
</CodeGroup>

## List, read, update, delete

* `GET /v1/apps` — scope `agents:read`. Paginated with `limit` and `after`.
* `GET /v1/apps/{id}` — scope `agents:read`.
* `PATCH /v1/apps/{id}` — scope `agents:write`. Mutable fields: `description` and `mcp` (see [App MCP tools](#app-mcp-tools)); the name and type are fixed at creation.
* `DELETE /v1/apps/{id}` — scope `agents:write`. Tears down the hosting project and, for `fullstack`, the app database with it. Irreversible.

## Deployments

A deployment ships the whole site in one request: a map of file paths to base64-encoded contents. The build runs asynchronously; the deployment advances `queued → building → ready` (or `error`) on read.

* `POST /v1/apps/{id}/deployments` — scope `agents:write`
* `GET /v1/apps/{id}/deployments` — scope `agents:read`

<ParamField body="files" type="object" required>Path → base64 contents, e.g. `{ "index.html": "PGgxPuKAplPC9oMT4=" }`. At least one file.</ParamField>
<ParamField body="content_hash" type="string">An opaque digest of `files` you compute (up to 128 characters). Stored as sent and echoed on every read, so a client can tell an unchanged site from a changed one by comparing against the newest deployment before uploading again. Never computed or checked by the platform.</ParamField>

<ResponseField name="id" type="string">Deployment id (e.g. `apd_01H...`).</ResponseField>
<ResponseField name="object" type="string">Always `app_deployment`.</ResponseField>
<ResponseField name="status" type="string">`queued`, `building`, `ready`, or `error`.</ResponseField>
<ResponseField name="url" type="string | null">The deployment's URL once ready.</ResponseField>
<ResponseField name="error" type="string | null">Set when the build failed.</ResponseField>
<ResponseField name="content_hash" type="string | null">The `content_hash` the deploy was created with; `null` when none was sent.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

### Deployment files

The deployment is the app's codebase: the platform keeps the `files` a deploy posted and gives them back as posted, so the newest deployment can be pulled, edited and shipped again without a repository in between.

* `GET /v1/apps/{id}/deployments/{apd}/files` — scope `agents:read`

<ResponseField name="object" type="string">Always `app_deployment_files`.</ResponseField>
<ResponseField name="deployment_id" type="string">The deployment the files belong to.</ResponseField>
<ResponseField name="files" type="object">Path → base64 contents, exactly as they were deployed.</ResponseField>

Answers `not_found` for a deployment made before the platform kept deployment files, and for any id that is not one of this app's deployments.

## Secrets

App secrets are **write-only** environment variables: the value goes to the app's runtime and never comes back on any read — the list returns names and timestamps only. Setting an existing name overwrites it.

* `POST /v1/apps/{id}/secrets` — scope `agents:write`. Body: `name` (an environment variable name: `A-Z`, `0-9`, `_`) and `value`.
* `GET /v1/apps/{id}/secrets` — scope `agents:read`. Names and `created_at` only, never values.
* `DELETE /v1/apps/{id}/secrets/{name}` — scope `agents:write`.

## Opening an app's own dashboard

An app that gates itself with an operator bearer named `DASHBOARD_TOKEN` can be opened without anyone handling that token. The platform holds its own copy — it is what generated it — and mints a short-lived proof of it on request.

`POST /v1/apps/{id}/entry` — scope `agents:write`

```json theme={"system"}
{ "object": "app_entry", "url": "https://your-app.example/api/enter", "ticket": "1757160000000.f3Xq…" }
```

`ticket` is an HMAC keyed by the app's token over its own expiry, good for two minutes. It is **not** the token, and the token is still returned by no route.

**Post the ticket; never link it.** Submit it as a form field to `url` from the operator's browser — the app trades it for its own session cookie and redirects to its home page. Putting it in a query string would leave it in the app's server logs, the browser's history and every `Referer` the page sends afterwards.

Refused with `feature_not_configured` on an app holding no `DASHBOARD_TOKEN`, and with `job_not_ready` on one whose first build has not produced an address yet.

## Dashboard access

A hosted app that declares a generated secret named `DASHBOARD_PASSWORD` is password-protected by default: the platform generates a password-shaped value (`kq7m-x2rt-8bvn-pz4h`) once, alongside `DASHBOARD_TOKEN`, and keeps its own copy so it can be handed to a person.

`GET /v1/apps/{id}/access` — scope `agents:write`

```json theme={"system"}
{ "object": "app_access", "url": "https://your-app.example", "password": "kq7m-x2rt-8bvn-pz4h" }
```

`password` is `null` until one has been provisioned. This is the one route that returns a credential, deliberately and audited (`app.access_read`): the password exists to be typed into the dashboard's own sign-in form. Show it masked, copy it, never put it in a URL or a log line.

`POST /v1/apps/{id}/access` — scope `agents:write`, answers `201` with the same object after regenerating both `DASHBOARD_PASSWORD` and `DASHBOARD_TOKEN` through the app's secret path (host environment, secret record, republish). The dashboard's session cookie is the token, so every browser signed in to the dashboard is signed out; the Studio signs its own frames back in. Audited as `app.access_rotated`.

Both are refused with `feature_not_configured` on an app that has no hosting project.

## App MCP tools

A `fullstack` app can serve its own MCP endpoint and hand its tools to your agents with no per-agent wiring. Declare the path with `mcp` on create, or set it later:

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL -X PATCH https://api.vetta.sh/v1/apps/app_01H... \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -d '{ "mcp": "/mcp" }'
  ```

  ```typescript TypeScript theme={"system"}
  await vetta.apps.update("app_01H...", { mcp: "/mcp" });
  ```
</CodeGroup>

Setting `mcp` to a path — the first time, or to a different path — **mints a fresh opaque bearer token** and upserts it as the app secret `VETTA_MCP_TOKEN`. Your app reads it from its environment and requires `Authorization: Bearer <token>` on its MCP endpoint. The secret is listed by `GET /v1/apps/{id}/secrets` like any other and, like any other, its value is never returned by any route; the platform's copy is never on the wire either.

* Re-sending the current path is a no-op — the token is not rotated. To rotate, set `mcp` to `null` and then back to the path.
* Setting `mcp` to `null` deletes the `VETTA_MCP_TOKEN` secret and forgets the token.
* `mcp` on a `frontend_only` app is refused with `400 validation_failed` (param `mcp`), on create and on patch alike.

Once the app is `active`, every turn of every agent that can access it (the `apps` tool's `allowed_apps` — absent means every app in the organization) is offered the endpoint's tools as `<app-name>.<tool>`, e.g. `storefront.list_orders`, under the agent's normal `allow`/`ask`/`deny` policy. The platform calls `url + mcp` with the bearer injected server-side; the token never reaches the sandbox. An unreachable endpoint contributes no tools and never fails a turn. See [Tools from your apps](/docs/capabilities/tools#tools-from-your-apps).

## Custom domains

Connecting a domain points an existing org [domain](/docs/api/domains) at an app — it never registers one. The domain must already exist at `/v1/domains`; its `app_connect_status` track advances `pending → connected` as the hosting verifies it.

* `POST /v1/apps/{id}/domains` — scope `agents:write`. Body: `domain_id`, the id of an existing org domain.
* `GET /v1/apps/{id}/domains` — scope `agents:read`.
* `DELETE /v1/apps/{id}/domains/{domain}` — scope `agents:write`. By domain name.

## The app database

A `fullstack` app's managed database is its own primitive, documented on the [Database](/docs/api/database) page: `POST /v1/apps/{id}/db/query` runs SQL, `GET …/db/tables` lists the tables, `…/db/rest/{table}` is a REST layer over every table, and `…/db/migrations` is a migration ledger. `{id}` may be the word `default` to mean the organization's only `fullstack` app.

## App storage

A `fullstack` app also has an object store — buckets and objects, uploaded inline or through signed URLs, under `/v1/apps/{id}/storage/…`. See [Storage](/docs/api/storage).

## Installs

An **install** is one applied declaration — the whole project config, applied once, recorded once per `(organization, project)`. It exists because two writers can now apply the same declaration to one organization and neither can see the other: applying from a checkout and applying from the studio are the same act against the same rows, and without a shared record the second one silently reverts the first.

The install always **records** an apply. Whether it also **performs** one depends on where the bytes came from, and the request shape already says which: an apply that carries a `declaration` was reconciled by whoever sent it, and an apply that names a published `blueprint` and `template` without one is reconciled by the server, [below](#apply). Either way there is no second provisioning path — every resource is created and updated by its own idempotent route, keyed by its own name, and the hosted apply is a client of those same routes with your credential's scopes and your organization's limits.

<ResponseField name="id" type="string">Unique id (e.g. `bpi_01H...`).</ResponseField>
<ResponseField name="object" type="string">Always `blueprint_install`.</ResponseField>
<ResponseField name="project" type="string">Your own project name — the same word an app's `project` stamps. Unique in the organization.</ResponseField>
<ResponseField name="blueprint" type="string | null">The blueprint the declaration resolved to, or `null` when it names none.</ResponseField>
<ResponseField name="template" type="string | null">Which of that blueprint's templates provides the crew.</ResponseField>
<ResponseField name="revision" type="integer">Counts **applies**, not versions of anything: `1` on the first, `+1` on each one after, whichever writer made it. It never rolls back.</ResponseField>
<ResponseField name="artifact_version" type="string | null">The released version the declaration came from, or `null` when it was applied from a working tree. On an apply that names a published pair this is the version the server read — the newest publish of that pair — whatever the request asked for.</ResponseField>
<ResponseField name="answers" type="object">The setup answers this project was configured with, so a re-apply does not re-ask. Configuration only — a secret is a vault credential or an app secret, and neither is ever an answer. Editable after the apply with [`PATCH`](#edit-the-answers).</ResponseField>
<ResponseField name="selection" type="object | null">Which of the published crew this apply took: `{ agents: string[], apps: string[] }` of declared names, or `null` when the apply took everything. `null` on every row applied before selection existed.</ResponseField>
<ResponseField name="source" type="string">`studio` or `cli` — which writer applied this revision.</ResponseField>
<ResponseField name="report" type="object | null">What the apply did, or `null` when it only recorded — and `null` while it is still `pending`. One line per resource under `skills`, `identities`, `vaults`, `apps`, `agents`, `schedules` and `intake` — `{name, action, id?, url?, reason?}`, where `action` is `created`, `updated`, `unchanged`, `deleted` or `refused` — plus `skipped`, naming any live row the reconciler could not read and stepped over, and `deselected`, naming the declared agents and apps the `selection` left out. The two are distinct: `skipped` is something the reconciler could not do, `deselected` something it was told not to. A refusal is one line and never an escape: an app whose environment could not be resolved is `refused` beside a crew that was created.</ResponseField>
<ResponseField name="status" type="string">Whether **this revision's** reconcile has finished: `applied` (it has, or the apply only recorded), `pending` (it is running behind the response), or `failed` (it threw). `report` alone cannot say this — a `null` report is a record-only apply, a run that has not happened yet, and a run that failed, all three.</ResponseField>
<ResponseField name="failure" type="string | null">Why a `failed` reconcile threw, in one sentence. `null` on every other row, and never a stack trace or a credential.</ResponseField>
<ResponseField name="applied_at" type="string">When this revision was applied.</ResponseField>
<ResponseField name="context_updated_at" type="string | null">When the answers were last edited after the apply, or `null` when they never were.</ResponseField>
<ResponseField name="created_at" type="string">When the project was first installed.</ResponseField>

### Apply

`POST /v1/blueprints/installs` — scope `agents:write`

Returns `201` on the project's first apply and `200` on every one after — or `202`, when the apply is queued rather than run (below).

**Whether the apply provisions anything depends on where its bytes come from, and there is no flag for it.** Send a `declaration` and you have already reconciled it yourself — that is what `naive up` does — so this records the apply and `report` is `null`. Name a published `blueprint` and `template` and send none, and the server reads that declaration from the catalog and reconciles it for you, into your organization, as you: the resources it creates are exactly the ones you could have created by hand, with the same scopes, the same plan gate and the same limits. What it did comes back in `report`, and stays on the row.

**An apply made from the dashboard runs after its response.** Provisioning a real crew was measured at 47–49 seconds, and nobody should be held on a request for it, so an apply made by a **signed-in person** against a published pair answers **`202`** immediately with `status: "pending"` and `report: null`, and reconciles behind that response. Poll `GET /v1/blueprints/installs?project=<project>`: the same row moves to `applied` with the `report` on it, or to `failed` with one sentence in `failure`. Nothing about *who* the apply runs as changes — the run is filed under the same person, with the role and scopes they hold **when it runs**, so a membership revoked in between refuses it.

**An apply made with an API key never does this.** A key carries no session claims to re-derive a credential from, so it reconciles inside its request and answers `201`/`200` with the finished `report`, exactly as it always has. So does a record-only apply, and so does every apply on a deploy that has no queue. If you are branching, branch on `status` rather than on the code.

A published crew of more than 24 agents is refused `400 validation_failed` (param `template`) before anything is written, including the install row: a crew is a handful of people, and a release declaring otherwise is one to look at rather than to run.

<ParamField body="project" type="string" required>Your own project name, up to 64 characters.</ParamField>

<ParamField body="declaration" type="object">
  The project config being applied, verbatim. Omit it only when `blueprint` and `template` name a [published artifact](#the-catalog) — the catalog holds that declaration and the apply reads it there. A working tree always has one.

  The applied declaration is stored but never returned: it is what lets a `revision_conflict` name the paths two writers disagree at, and an apply that named a published pair was never given those bytes in the first place.
</ParamField>

<ParamField body="blueprint" type="string">The blueprint this resolved to, or `null`.</ParamField>
<ParamField body="template" type="string">The template within it, or `null`.</ParamField>
<ParamField body="artifact_version" type="string">The released version it came from. Omit when applying from a working tree — there is no version to name.</ParamField>
<ParamField body="answers" type="object">The setup answers, keyed by each question's `key`. Defaults to `{}`. Every key must be a question the published pair asks — an answer to a question nobody asked would be stored and never used, and is refused `400 validation_failed` (param `answers`) naming it. An answer to a declared question is always accepted: the crew reads it through its [project context](#project-context), whether or not an app's environment names it. Every question the pair asks must be answered, except one it marked `optional: true` — leave that key out to say nothing, and it is absent from the context rather than empty in it.</ParamField>
<ParamField body="selection" type="object">`{ agents: string[], apps: string[] }` — which of the published crew to provision, by declared name. **Omit it to take everything.** What it leaves out is not created, its timers are not armed and its first briefing is not sent, and the names come back under `report.deselected`. A name the pair does not declare, or a selection that leaves out an agent or app the template marks `required`, is refused `400 validation_failed` (param `selection`) naming every one of them, before anything is written. A resource an earlier apply created and a later apply deselects is **left running** — deselecting is not deleting.</ParamField>
<ParamField body="source" type="string">`cli` (default) or `studio`.</ParamField>
<ParamField body="expected_revision" type="integer">The revision you believe you are moving from. **Omitting it is a deliberate unconditional apply**, exactly as omitting `expected_version` is on `PATCH /v1/agents/{id}`: declarative CI should not have to read before every write.</ParamField>

A mismatched `expected_revision` is refused `409 revision_conflict` (param `expected_revision`) and **nothing is written**. The message names the paths at which the stored declaration and yours disagree — `agents.2.system`, `apps.0.mcp` — not just the two revision numbers, so you can see whether the other writer touched anything you own. At most 20 paths are named, then a count of the rest.

`revision_conflict` is not `version_conflict`: the latter means one versioned object is stale and the fix is to re-read it, while this one means a whole shared declaration moved underneath you and the fix is a merge.

**Two applies of one project never RECONCILE at once**, whichever way they were made. Whatever performs the reconcile — this request, or the queued run behind a `202` — holds a lease on the project for as long as it takes, renewing it while it works, so the lease is released when the apply finishes rather than after a fixed time. That is what keeps reconciliation by name idempotent: without it two applies arriving together both create the same resources. Applies of different projects never wait for each other.

Where the wait lands differs by which apply it is:

* **An apply that reconciles inside its request** (an API key's, or any on a deploy without the queue) waits up to **60 seconds** for the one ahead of it and then applies normally, recording the next revision. One still running after that wait is refused with the retryable `409 job_not_ready` (param `project`) and nothing is recorded for it — retry it, and it will apply once the first has finished.
* **A queued apply is not held at all.** It records its revision and answers `202` at once; the *run* waits its turn behind the one ahead of it, and a run that never gets its turn ends as `failed` on the row with that reason in `failure` rather than as an error you receive. A second apply lodged while the first is still running supersedes it: the row is at the newer revision, only the newer run stamps a report, and the older one finds the row moved on and does nothing.

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/blueprints/installs \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -d '{ "project": "acme-agency", "blueprint": "agency", "template": "seo-geo",
          "expected_revision": 7, "answers": { "mailbox": "hello@acme.com" },
          "declaration": { "name": "acme-agency", "agents": [] } }'
  ```

  ```typescript TypeScript theme={"system"}
  const install = await vetta.apps.installs.apply({
    project: "acme-agency",
    blueprint: "agency",
    template: "seo-geo",
    expected_revision: 7,
    answers: { mailbox: "hello@acme.com" },
    declaration,
  });
  console.log(install.revision); // 8
  ```
</CodeGroup>

### List

`GET /v1/blueprints/installs` — scope `agents:read`. Every install in the organization, most recently applied first. Paginated with `limit` and `after`, and narrowed to one project with `project=<project>`. This is the read that watches a queued apply finish.

A row cannot sit `pending` forever waiting for a run that is never coming: past fifteen minutes — longer than any apply takes — this read stamps it `failed` with `the apply never finished and is no longer running; run it again`, so a client polling it is told to stop rather than told to keep waiting. Fifteen minutes is a plausible age, not a proof that nothing is running — a run mints its credential when it starts, so one delivered late still finishes normally — and a run that does finish always wins: the stamp is refused on any row that has already left `pending`, so a completed apply keeps its `applied` status and its report.

### Edit the answers

`PATCH /v1/blueprints/installs/{id}` — scope `agents:write`. Returns the install.

The business changes its mind after the crew is provisioned — a new offer, a new audience — and re-running the apply to say so would re-provision a crew that is already standing. This rewrites the answers alone.

<ParamField body="answers" type="object" required>The **whole** answer set, keyed by question `key`. It replaces what was stored; it does not merge, so a key you leave out is gone. Every key must be a question the install's published pair asks, refused `400 validation_failed` (param `answers`) otherwise, exactly as on the apply — and, exactly as on the apply, every question must be answered unless it is `optional: true`, which is left out rather than blanked.</ParamField>

The row's `revision` does not move — nothing was applied — and nothing is reconciled: the crew, its apps and its timers stand exactly as the last apply left them. `context_updated_at` is set, and the [project context](#project-context) reads the new answers at once.

An install applied from a working tree has no published questions to validate against and is refused `400 validation_failed` (param `id`): apply the tree again with the new answers.

```bash theme={"system"}
curl -fsSL -X PATCH https://api.vetta.sh/v1/blueprints/installs/bpi_01H... \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "answers": { "offer": "Short explainers, for students", "tone": "Plain" } }'
```

### Project context

`GET /v1/blueprints/installs/{id}/context` — scope `agents:read`.

What a template's crew knows about the business, as one object. It is what the crew's own read-only `project_context` tool returns to each agent, and the same read a dashboard makes — so what a person sees and what the agents are told never differ.

It is **derived**, not stored: from the install's answers, the published pair's questions, and the report of the latest apply. Only answer edits are ever written. It exists only for an install whose latest apply is `applied`; a `pending` or `failed` row, or one applied from a working tree, answers `404 not_found`.

<ResponseField name="object" type="string">Always `project_context`.</ResponseField>
<ResponseField name="project" type="string">The project name.</ResponseField>
<ResponseField name="blueprint" type="string | null">The blueprint, and</ResponseField>
<ResponseField name="template" type="string | null">the template that provided the crew.</ResponseField>
<ResponseField name="artifact_version" type="string | null">The released version it was applied from.</ResponseField>
<ResponseField name="answers" type="array">`{ key, label, value }` — every answered question in the order the pair asks them, with the question's own wording as `label`. `value` is a string, or an array of strings for a multiple-choice question. A question left unanswered is not listed.</ResponseField>
<ResponseField name="apps" type="array">`{ name, url }` — the apps standing after the latest apply, with the address each was deployed at, or `null` when the apply did not record one.</ResponseField>
<ResponseField name="agents" type="array">`{ name, role }` — the crew standing after the latest apply, with the role the template gives each, or `null` when it names none.</ResponseField>
<ResponseField name="updated_at" type="string">When the answers were last edited, or when the latest apply ran if they never were.</ResponseField>

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/blueprints/installs/bpi_01H.../context \
  -H "authorization: Bearer sk_live_..."
```

## The catalog

A **blueprint artifact** is one published build of one blueprint × template × released version. It
is the same row for every organization — a hosted run does not fork a blueprint into a per-customer
copy, because there is nothing per-customer in the tree — so it carries no organization and nothing
you write ever reaches it.

Read it before you install: it is where the setup questions come from, and it is what lets a screen
show a person the crew, the apps and the daily cost of a company **before** anything is provisioned.

<ResponseField name="id" type="string">`bpa_…`</ResponseField>
<ResponseField name="blueprint" type="string">The machine — its repository, screens and `/api/*`.</ResponseField>
<ResponseField name="template" type="string">Which of that blueprint's templates provides the crew.</ResponseField>
<ResponseField name="version" type="string">The blueprint repository's own released version.</ResponseField>

<ResponseField name="trees" type="array">
  One built tree **per deployable app**, ordered as `apps` is, and never empty. A blueprint ships as many apps as it declares — a marketing site and a private dashboard are two — so the digest is per app, not per release.

  <Expandable title="trees[]">
    <ResponseField name="app" type="string">The declared app name this tree builds, matching an entry in `apps`.</ResponseField>
    <ResponseField name="content_hash" type="string">Lowercase sha-256 of that app's built tree — the same digest a [deployment's `content_hash`](#deployments) carries, which is what lets an unchanged app skip its upload. Compare per app: a single digest over the whole release would move whenever any one app changed and re-upload the others for nothing.</ResponseField>
  </Expandable>

  Where the bytes live is not on the wire. A hosted apply fetches them itself; a client compares digests and uploads its own.
</ResponseField>

<ResponseField name="questions" type="array">The setup questions — at most four — each a `{ key, label, type, help?, optional?, … }` field, the same shape a running agent asks with. Ask them before you apply, and send the answers on the apply. A field with `optional: true` may be left blank: omit its key from `answers`.</ResponseField>
<ResponseField name="schedules" type="array">What the crew does on a timer: `{ agent, cron, timezone, input, budget_micro_usd }`. `agent` is the declared **name**; no agent exists yet to have an id.</ResponseField>
<ResponseField name="agents" type="array">Who the crew is: `{ name, description, model, briefed, intake, intake_micro_usd, role, skills, tools, required }`. `briefed` is whether this agent is sent a first message the moment it is created; `intake` is that message, or `null`; `intake_micro_usd` is the integer micro-USD cap that first session spends under, present when `briefed` — sum it over the agents you select for the one-time cost of the apply. `role` is the job title the template gives the agent; `skills` the skill references it loads; `tools` the sorted names of the tools its toolset enables — built-in ones and `<app>.<tool>` ones alike; `required` whether an apply's `selection` may leave it out. `intake`, `role`, `skills`, `tools` and `required` are **absent** on a row published before they existed — the catalog is not rewritten by a release, so read them as optional.</ResponseField>
<ResponseField name="apps" type="array">What it deploys: `{ name, type, description, mcp, tools, required }`. `mcp` is the path the app serves MCP on, or `null`; `tools` is the tool names the crew's toolsets configure under that app; `required` whether a `selection` may leave it out. All three are **absent** on a row published before they existed, for the same reason `intake` is.</ResponseField>
<ResponseField name="crew_per_client" type="array">The agents the template creates **per client the operator signs**, in the same shape as `agents` — printed so a person can see the whole company before anything is provisioned, never created by an apply. Absent when the template declares none.</ResponseField>
<ResponseField name="identities" type="array">The personas the crew acts as: `{ name, description, connections }`, where `connections.social` — present only when the template declares it — names the `choice` question (`from`) whose picked options map (`map`) to the publishing networks to connect to that persona before the crew is provisioned. Absent on a row published before it existed.</ResponseField>
<ResponseField name="published_at" type="string">When this version was published.</ResponseField>

### List

`GET /v1/blueprints/artifacts` — any key or session, including one not yet scoped to an
organization. Newest release first. Narrow it with
`blueprint` and `template`; paginate with `limit` and `after`.

```bash theme={"system"}
curl -fsSL "https://api.vetta.sh/v1/blueprints/artifacts?blueprint=media&template=faceless" \
  -H "authorization: Bearer sk_live_..."
```

Applying one of these takes no `declaration`: name its `blueprint` and `template` on the apply and
the newest published declaration for that pair is what gets recorded, along with that publish's
`version`. The declaration itself never comes back on the wire.
