Skip to main content
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: 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

string
Unique id (e.g. app_01H...).
string
Always app.
string
A lowercase DNS label, unique in the organization.
string | null
Free text.
string
frontend_only or fullstack.
string
provisioning, active, or error. A frontend_only app is active at birth; a fullstack app is provisioning until its database is ready.
string | null
The app’s live URL.
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.
string | null
Set when status is error.
string
Creation timestamp.

Create an app

POST /v1/apps — scope agents:write Idempotent on name: creating an app that already exists returns the existing record.
string
required
A lowercase DNS label, 63 characters or fewer.
string
Optional, up to 500 characters.
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.
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.

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); 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
object
required
Path → base64 contents, e.g. { "index.html": "PGgxPuKAplPC9oMT4=" }. At least one file.
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.
string
Deployment id (e.g. apd_01H...).
string
Always app_deployment.
string
queued, building, ready, or error.
string | null
The deployment’s URL once ready.
string | null
Set when the build failed.
string | null
The content_hash the deploy was created with; null when none was sent.
string
Creation timestamp.

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
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.

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:
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.

Custom domains

Connecting a domain points an existing org domain 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

POST /v1/apps/{id}/db/query — scope agents:write Runs SQL against the app’s own managed database. fullstack apps only, and only once the app is active: a frontend_only app answers 501 feature_not_configured, and a still-provisioning backend answers a retryable job_not_ready.
string
required
The SQL to run.

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. 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.
string
Unique id (e.g. bpi_01H...).
string
Always blueprint_install.
string
Your own project name — the same word an app’s project stamps. Unique in the organization.
string | null
The blueprint the declaration resolved to, or null when it names none.
string | null
Which of that blueprint’s templates provides the crew.
integer
Counts applies, not versions of anything: 1 on the first, +1 on each one after, whichever writer made it. It never rolls back.
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.
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.
string
studio or cli — which writer applied this revision.
object | null
What the apply did, or null when it only recorded. 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. 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.
string
When this revision was applied.
string
When the project was first installed.

Apply

POST /v1/blueprints/installs — scope agents:write Returns 201 on the project’s first apply and 200 on every one after. 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. 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.
string
required
Your own project name, up to 64 characters.
object
The project config being applied, verbatim. Omit it only when blueprint and template name a published artifact — 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.
string
The blueprint this resolved to, or null.
string
The template within it, or null.
string
The released version it came from. Omit when applying from a working tree — there is no version to name.
object
The setup answers, keyed by each question’s key. Defaults to {}.
string
cli (default) or studio.
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.
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 run at once. An apply holds a lease on its project for as long as it takes — it renews that lease while it works, so the lease is released when the apply finishes rather than after a fixed time. A second apply of the same project waits up to 60 seconds for the first and then applies normally, recording the next revision; that is what keeps reconciliation by name idempotent, because without it two applies that arrive together both create the same resources. An apply 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. Applies of different projects never wait for each other.

List

GET /v1/blueprints/installs — scope agents:read. Every install in the organization, most recently applied first. Paginated with limit and after.

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.
string
bpa_…
string
The machine — its repository, screens and /api/*.
string
Which of that blueprint’s templates provides the crew.
string
The blueprint repository’s own released version.
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.Where the bytes live is not on the wire. A hosted apply fetches them itself; a client compares digests and uploads its own.
array
The setup questions, each a { key, label, type, … } field — the same shape a running agent asks with. Ask them before you apply, and send the answers on the apply.
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.
array
Who the crew is: { name, description, model, briefed }. briefed is whether this agent is sent a first message the moment it is created.
array
What it deploys: { name, type, description }.
string
When this version was published.

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.
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.