# Brila API

Brila turns a business listing (Google Maps or Yelp) into a published landing page.
Generation is **asynchronous**: you start a generation, then poll it until the site is ready.

## Authentication

Every request must carry your API key in the `Api-Key` header:

```
Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Your key is shown in your Brila account once you have an **active subscription** — the public API is a
subscriber feature. Keep the key secret; it is tied to your account.

- Missing / invalid / revoked key → `401 INVALID_API_KEY`.
- Valid key but no active subscription → `403 SUBSCRIPTION_REQUIRED`.

## Limits & fair use

Two kinds of limits apply:

- **Creation limit** — your plan allows a fixed number of site generations per billing period.
- **Request rate** — don't flood the API. Too many requests in a short window (for example,
  polling too aggressively) return `429 TOO_MANY_REQUESTS`.

**Best practices:** poll no faster than the `Retry-After` header suggests, back off when you
get a `429`, and avoid tight retry loops.

**OpenAPI spec (JSON):** https://api.brila.ai/api/public/v1/swagger_doc

## Generations

Generating a site is **asynchronous**: start a generation, then poll it by id until it finishes.

Create a generation, then poll it until `status` is `ready` or `failed`:

- `queue` — accepted, waiting in the queue;
- `processing` — generation is running;
- `ready` — done; fetch the site via `GET /v1/sites/{id}` (same id);
- `failed` — generation failed.

Respect the `Retry-After` header (seconds) between polls.

### Endpoints

- `POST /api/public/v1/generations` — Start generating a new site. Returns immediately; poll GET /v1/generations/{id} until status is ready or failed.
- `GET /api/public/v1/generations/{id}` — Poll a generation by id. Once status is ready, fetch the site via GET /v1/sites/{id} (it also appears in GET /v1/sites).

## Sites

Read and export the sites you've generated. A site shows up here once its generation reaches `ready`.

You can list your sites, fetch one by id, inspect which content sections it has, and export its
content as **HTML**, **JSON**, or **Markdown** — the whole site or only selected sections.

### Endpoints

- `GET /api/public/v1/sites` — List your sites
- `GET /api/public/v1/sites/{id}` — Fetch a single site by id
- `DELETE /api/public/v1/sites/{id}` — Delete a site and take it offline. Note: a site deleted within the current billing period still counts toward your creation limit until the period resets.
- `GET /api/public/v1/sites/{id}/export` — Export a site's content. `format=html` returns the raw published HTML, `format=json` the structured section data (`{ id, data }`), `format=md` human-readable Markdown. Use `sections` (json/md only, comma-separated) to export only chosen sections — list available section names via GET /v1/sites/{id}/sections.
- `GET /api/public/v1/sites/{id}/sections` — List the site section names (use them in the export `sections` param)
- `GET /api/public/v1/sites/{id}/sections/{name}` — Read a section, including its read-only JSON `$schema` (what is editable).
- `PUT /api/public/v1/sites/{id}/sections/{name}` — Edit a section. Send the editable fields under `data`. **Image fields must be URLs you host on Brila** — upload your image via `POST /v1/uploads` and use the returned `asset.url`; external image URLs are rejected with `SITE_SECTION_INVALID_URL`. The server validates against the stored schema (any `$schema`/`_meta` you send is ignored) and re-renders the site asynchronously.

## Domains

Attach, remove, or re-verify a **custom domain** on a site - a subscriber feature, one domain
per site. Point the domain CNAME at your site in your DNS provider (no DNS records are returned
by the API); the current domain and its `status` are shown on the site object. Attach and remove
share a rate limit of **5 requests per hour per IP** - exceeding it returns `429`.

After pointing the CNAME, check the status yourself with `POST /v1/sites/{id}/domain/verify`
(it returns the current `pending`/`active`). Verification also runs **automatically in the
background**, so `status` on `GET /v1/sites/{id}` moves from `pending` to `active` on its own
once the CNAME resolves - no need to poll aggressively.

### Endpoints

- `POST /api/public/v1/sites/{id}/domain` — Attach a custom domain to a site. Subscriber feature; one domain per site. Point the domain CNAME at your site out of band - no DNS records are returned. The current domain is also shown on the site object (`GET /v1/sites/{id}`). Rate-limited to 5 requests per hour per IP (shared with domain removal). After pointing the CNAME, check the status yourself with POST /v1/sites/{id}/domain/verify (returns the current `pending`/`active`). Verification also runs automatically in the background, so the status on GET /v1/sites/{id} moves to `active` on its own once the CNAME resolves.
- `DELETE /api/public/v1/sites/{id}/domain` — Remove the site's custom domain. Rate-limited to 5 requests per hour per IP (shared with domain creation).
- `POST /api/public/v1/sites/{id}/domain/verify` — Re-check the custom domain now and return its current status. Verification also runs automatically in the background; use this to trigger an immediate check after pointing the CNAME. Returns the domain with status `pending` or `active`.

## Uploads

Upload images to the CDN. Use uploaded image URLs when editing site sections.

### Endpoints

- `GET /api/public/v1/uploads` — List your uploads
- `POST /api/public/v1/uploads` — Upload an image to the CDN
- `DELETE /api/public/v1/uploads/{id}` — Delete an upload

## User

Your account and the plan limits that apply to API usage (creation budget).

### Endpoints

- `GET /api/public/v1/user/info` — Your account and the plan limits that apply to API usage. Works without an active subscription (you get the `free` plan), so you can check status before subscribing.

## 🤖 Agent Skills

[![Discord](https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white)](https://discord.gg/uQ97scyNTX)

**Repo:** [github.com/brila-ai/agent-skills](https://github.com/brila-ai/agent-skills)

Prefer to let an AI assistant do the work? The Brila plugin ships **skills + slash commands**
(the [**🔌 MCP**](#tag/mcp) server included, registered automatically on install) that drive
Brila for you end-to-end:

- `/brila:generate-site <google-maps-or-yelp-url>` — generate a site from a Google Maps or
  Yelp business listing, poll until `ready`, and return the **live URL** plus the site
  content as **Markdown**.
- `/brila:widget <site>` — build an embeddable **reviews widget** from a site's content,
  styled to match the store it will live on.

### Install (Claude Code)

```
/plugin marketplace add brila-ai/agent-skills
/plugin install brila@brila
```

Also works in **OpenAI Codex** (`SKILL.md` is supported natively), or install into any agent via:

```
npx skills add brila-ai/agent-skills --skill brila-generate-site
# add --skill brila-widget for the reviews-widget skill
```

### What it does

- **Generate** — `POST /v1/generations`, polls `GET /v1/generations/{id}` until `ready`/`failed`,
  then exports `GET /v1/sites/{id}/export?format=md`.
- **Edit sections** — `GET`/`PUT /v1/sites/{id}/sections/{name}`, reading each section's schema
  so it only changes what's allowed.
- **Upload images** — `POST /v1/uploads`, then uses the returned CDN URL in a section's image field.
- **Custom domain** — `POST`/`DELETE /v1/sites/{id}/domain` + `POST /v1/sites/{id}/domain/verify`:
  attach a domain, point the CNAME, and it verifies to `active` in the background.
- **Manage sites** — `GET /v1/sites` to find a site by name, `DELETE /v1/sites/{id}` to take one offline.
- **Reviews widget** *(the `brila-widget` skill)* — reads the reviews already on your site,
  **matches the destination store's visual style**, and generates a **self-contained** snippet
  (inline HTML/CSS/JS, no API key, no calls) to paste into Shopify (Customize → Custom Liquid),
  WordPress, Webflow, or any HTML block. It's a **static snapshot** — rebuild to refresh.

### Using it

- **Authenticate** one of two ways — connect the Brila **MCP server** and sign in with OAuth
  (no key to copy), or set your **API key** (`BRILA_API_KEY`, or simply **paste it into the
  chat**). An **active subscription** is required either way.
- Then run `/brila:generate-site <google-maps-or-yelp-url>` (or just ask) — it returns the live
  URL plus the site content as Markdown.

*Under the hood the skill runs over the **MCP server** when you're connected, otherwise via a
bundled **Python script** that calls the API with your key — same result either way. Want to
wire up your own MCP client directly? See [**🔌 MCP**](#tag/mcp).*

## 🔌 MCP

Let an AI agent drive Brila through a hosted **remote MCP server** — connect any MCP client and
Brila's tools show up in your agent. Nothing to install; sign in with OAuth or bring your API key.

**Endpoint:** `https://mcp.brila.ai/mcp` (Streamable HTTP).

**Auth — OAuth or API key.** By default your MCP client opens a browser to sign in to Brila and
authorize access — nothing to copy. Already have an **API key**? Skip the browser and pass it as
an `Api-Key` header instead. Either way the server only ever touches your own account. Most tools
require an **active subscription**; `account_info` and `create_subscription_checkout` work without
one, so a new user can check status and subscribe.

**Connect — Claude Code** (most common):

```
claude mcp add --transport http brila https://mcp.brila.ai/mcp
```

**Any other MCP client** — add it to your `mcp.json`:

```json
{
  "mcpServers": {
    "brila": { "type": "http", "url": "https://mcp.brila.ai/mcp" }
  }
}
```

To use an API key instead of the browser sign-in, add a header to that entry:
`"headers": { "Api-Key": "sk_xxxxxxxx" }`.

### Product workflows

Goal-oriented tools that chain several API calls and return rich, product-shaped results:

- `generate_site` — start a site from a Google Maps or Yelp business URL; returns a
  `generation_id` right away (build runs server-side), then poll `get_generation` until
  `ready` and fetch it with `get_site` / `export_site`; auto-resumes an in-progress generation.
- `connect_domain` — attach a custom domain and poll verification until it's live (or hand back
  the DNS to set and re-verify).
- `set_section_image` — upload an image to the CDN and set it on a section in one step.
- `analyze_reviews` — summarize a business's reviews: schema.org aggregate rating plus the
  review-derived highlights on the site (advantages, popular items, special touches, tips).

### Atomic tools

Thin, direct operations over the public API:

- `get_generation` — check the status of a site generation by id.
- `list_sites` — list your published Brila sites.
- `get_site` — get a site overview (live URL, business, publish and domain status).
- `delete_site` — take a site down (delete it).
- `export_site` — get a site's content as Markdown, HTML, or JSON.
- `list_sections` — list the parts of a site you can edit.
- `get_section` — read one editable part of a site.
- `update_section` — edit the text/content of a site section.
- `add_domain` — attach a custom domain to a site.
- `remove_domain` — remove the custom domain from a site.
- `verify_domain` — check whether a site's custom domain is connected yet.
- `list_uploads` — list your uploaded images.
- `upload_image` — upload an image for use in a site.
- `delete_upload` — delete an uploaded image by id.
- `account_info` — your Brila plan and remaining site-creation budget; on the free plan it also
  lists the purchasable plans and points to `create_subscription_checkout`.
- `create_subscription_checkout` — start a subscription purchase; returns a payment link to open
  in a browser (works without an active subscription).

Prefer ready-made slash-commands instead? See [**🤖 Agent Skills**](#tag/agent-skills).