REST API Reference
All MCP tools map 1:1 to REST endpoints. Use the REST API directly for CI/CD pipelines, non-MCP integrations, webhook handlers, server-side automation, or any HTTP client.
Base URL: https://api.briefgate.dev
All requests and responses use JSON (Content-Type: application/json). All timestamps are ISO 8601 in UTC.
Authentication
Include your API key in every request as a Bearer token:
Authorization: Bearer bg_live_xxxxxKey prefixes
| Prefix | Environment | Behavior |
|---|---|---|
bg_live_ |
Production | Sends real emails and SMS to clients. |
bg_test_ |
Sandbox | Routes email to a test inbox in the dashboard; SMS is dropped silently. Webhooks fire normally. |
Manage API keys
POST /v1/keys Create a new key
GET /v1/keys List all keys
DELETE /v1/keys/:id Revoke a keyCreate a key:
curl -X POST https://api.briefgate.dev/v1/keys \
-H "Authorization: Bearer bg_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "CI/CD pipeline",
"mode": "live",
"scopes": ["intakes:read", "intakes:write"]
}'Available scopes: admin, intakes:read, intakes:write, secrets:read. The admin scope includes all others. Keys created via the API inherit a maximum of the creating key's scopes.
Error format
All errors return a JSON body:
{
"error": "not_found",
"message": "Intake in_8f3k not found or does not belong to this account.",
"request_id": "req_abc123"
}Include request_id when contacting support.
Error codes
| HTTP status | error value |
Meaning |
|---|---|---|
| 400 | invalid_request |
Malformed JSON or missing required field. Check message for details. |
| 401 | unauthorized |
Missing or invalid API key. |
| 403 | forbidden |
Key does not have the required scope for this operation. |
| 404 | not_found |
Resource does not exist or belongs to a different account. |
| 409 | conflict |
A resource with the same identifier already exists (e.g. duplicate idempotency_key within the expiry window). Returns the existing resource. |
| 410 | gone |
Resource existed but has been permanently deleted or consumed (e.g. a secret token after first reveal). |
| 413 | payload_too_large |
File upload exceeds the declared max_bytes constraint or the account's hard limit. |
| 422 | unprocessable |
Request is structurally valid but semantically invalid (e.g. min_count greater than max_count). |
| 429 | rate_limited |
Too many requests. Honour the Retry-After header (seconds) before retrying. |
| 402 | quota_exceeded |
Monthly intake or file-storage quota reached. |
| 402 | plan_required |
Feature is not available on the current plan. |
| 500 | internal_error |
Unexpected server error. Safe to retry with exponential backoff. |
Intakes
POST /v1/intakes
Create a new intake and send the client their portal link.
Request headers
| Header | Description |
|---|---|
Idempotency-Key |
Optional. A unique string (UUID recommended). If an intake was already created with this key, the original is returned (HTTP 200) instead of creating a duplicate. |
Key request fields
| Field | Type | Required | Description |
|---|---|---|---|
project_name |
string | Yes | Human-readable project title. |
client.email |
string | Yes | Client email address. |
client.name |
string | No | Client display name for greetings. |
client.language |
string | No | BCP 47 language code (default: en). |
items |
array | Yes | Item definitions. See item-types.md. |
chase_schedule |
string | No | default, gentle, or aggressive. |
due_date |
string | No | ISO 8601 date shown to the client. |
send |
boolean | No | Set to false to create a draft without sending. Call POST /v1/intakes/:id/send to send later. Default: true. |
retention |
object | No | How long to keep data after completion. { mode: "days"|"on_delivery", days?: number, anonymize?: boolean }. Default: purge 90 days after intake.completed. Use mode: "on_delivery" for intakes with credentials — contents are removed ~24 h after you collect results. |
Chase schedules
| Schedule | Reminders |
|---|---|
default |
T+2 days, T+5 days, T+9 days, then weekly |
gentle |
T+3 days, T+8 days, then bi-weekly |
aggressive |
T+1 day, T+3 days, T+5 days, then every other day |
Response (HTTP 201)
{
"intake_id": "in_8f3kQmR2",
"portal_url": "https://app.briefgate.dev/portal/in_8f3kQmR2",
"status": "sent",
"items": [
{ "key": "logo", "status": "pending" },
{ "key": "hero_copy", "status": "pending" }
]
}Complete curl example
curl -X POST https://api.briefgate.dev/v1/intakes \
-H "Authorization: Bearer bg_live_xxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bella-napoli-website-2024" \
-d '{
"project_name": "Bella Napoli — Website",
"client": {
"email": "[email protected]",
"name": "Marco Esposito",
"language": "en"
},
"items": [
{
"key": "logo",
"label": "Restaurant logo",
"help": "SVG or PNG, transparent background, minimum 512px.",
"type": "image",
"required": true,
"constraints": {
"formats": ["svg", "png"],
"min_width": 512,
"transparent_background": true
}
},
{
"key": "hero_copy",
"label": "Hero section tagline",
"type": "longtext",
"required": true,
"constraints": { "max_chars": 400 }
},
{
"key": "opening_hours",
"label": "Opening hours",
"type": "structured",
"required": true,
"schema": {
"type": "object",
"required": ["mon_fri", "sat", "sun"],
"properties": {
"mon_fri": { "type": "string" },
"sat": { "type": "string" },
"sun": { "type": "string" }
}
}
},
{
"key": "photos",
"label": "Food and interior photos",
"type": "file_list",
"required": true,
"constraints": {
"formats": ["jpg", "png", "heic"],
"min_count": 5,
"max_count": 15
}
}
],
"chase_schedule": "default",
"due_date": "2024-04-01"
}'GET /v1/intakes
List intakes with optional filters.
Query parameters
| Parameter | Description |
|---|---|
status |
Filter by status: draft, sent, in_progress, completed, archived. |
client_email |
Filter to a specific client. |
limit |
Results per page (default 20, max 100). |
offset |
Pagination offset (default 0). |
Response
{
"total": 47,
"intakes": [
{
"id": "in_8f3kQmR2",
"projectName": "Bella Napoli — Website",
"clientEmail": "[email protected]",
"status": "in_progress",
"createdAt": "2024-03-15T10:22:00Z"
}
]
}Note: The list response returns intake fields in camelCase (raw DB representation). Use
GET /v1/intakes/:id/statusfor a fully formatted status object.
GET /v1/intakes/:id
Retrieve the full intake definition including all item definitions and metadata.
GET /v1/intakes/:id/status
Lightweight status endpoint. Returns item statuses, chase history, and progress without transferring file content or signed URLs.
Response
{
"status": "in_progress",
"due_date": "2024-04-01",
"client_last_seen": "2024-03-16T14:05:33Z",
"progress": {
"submitted": 2,
"total": 4,
"outstanding": 2,
"percent": 50
},
"items": [
{ "key": "logo", "status": "approved", "submitted_at": "2024-03-16T10:00:00Z", "label": "Restaurant logo" },
{ "key": "hero_copy", "status": "needs_revision", "submitted_at": "2024-03-16T11:00:00Z", "label": "Hero section tagline" },
{ "key": "opening_hours", "status": "pending", "submitted_at": null, "label": "Opening hours" },
{ "key": "photos", "status": "submitted", "submitted_at": "2024-03-16T12:00:00Z", "label": "Food and interior photos" }
],
"chases": [
{ "channel": "email", "sent_at": "2024-03-17T09:00:00Z", "status": "sent", "attempt_no": 1 },
{ "channel": "email", "sent_at": null, "status": "scheduled", "attempt_no": 2 }
]
}GET /v1/intakes/:id/results
Returns typed results for approved items. File and image items include signed URLs (24-hour validity).
Query parameters
| Parameter | Type | Description |
|---|---|---|
only_new |
boolean | Only return items approved since the last call to this endpoint. |
include_pending |
boolean | Also include items in submitted status (not yet approved). |
POST /v1/intakes/:id/items
Add items to an existing intake. The client is notified by email.
Request body
{
"items": [
{
"key": "favicon",
"label": "Favicon",
"type": "image",
"required": true,
"constraints": { "formats": ["png","svg"], "min_width": 32 }
}
]
}Response
{
"intake_id": "in_8f3kQmR2",
"items_added": [
{ "key": "favicon", "status": "pending" }
]
}POST /v1/intakes/:id/revision
Request a revision on a specific item.
Request body
{
"item_key": "logo",
"note": "The logo appears blurry at 320px. Please re-export at minimum 512px, ideally as SVG."
}Response
{
"intake_id": "in_8f3kQmR2",
"item_key": "logo",
"status": "needs_revision",
"client_notified": true
}POST /v1/intakes/:id/chase
Manually trigger a chase message outside the automatic schedule.
Request body
{
"channel": "sms"
}channel is optional. Defaults to email. Use sms to escalate after emails have bounced or to reach the client through a different channel.
POST /v1/intakes/:id/send
Send a draft intake to the client. Only valid when status is draft.
DELETE /v1/intakes/:id
Permanently delete an intake and all associated uploaded files. This action is irreversible. Returns HTTP 204 on success.
Templates
Save an intake definition as a reusable template to avoid re-specifying items for recurring project types.
GET /v1/templates List all templates
POST /v1/templates Create a template from an item definition arrayCreate a template
curl -X POST https://api.briefgate.dev/v1/templates \
-H "Authorization: Bearer bg_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Restaurant Website",
"items": [...]
}'Use the template ID in define_intake (or POST /v1/intakes) with "template_id": "tmpl_xyz" instead of specifying items manually.
Webhooks
BriefGate fires webhooks on intake state changes. All webhook payloads include intake_id, event, and occurred_at.
GET /v1/webhooks List configured endpoints
POST /v1/webhooks Register a new endpoint
DELETE /v1/webhooks/:id Remove an endpoint
POST /v1/webhooks/:id/test Send a test event to the endpointRegister a webhook
curl -X POST https://api.briefgate.dev/v1/webhooks \
-H "Authorization: Bearer bg_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/briefgate",
"events": ["intake.complete", "item.submitted", "item.approved"],
"secret": "whsec_your_signing_secret"
}'BriefGate signs each webhook request with an HMAC-SHA256 signature in the BriefGate-Signature header. Verify it using your secret before processing the payload.
Events
| Event | Fired when |
|---|---|
intake.sent |
Intake email delivered to client. |
intake.viewed |
Client opened the portal for the first time. |
intake.complete |
All required items are approved. |
item.submitted |
Client uploaded or filled in an item. |
item.approved |
Item approved (explicitly or via 72h auto-approval). |
item.revision_requested |
Agent called request_revision. |
chase.sent |
A chase email or SMS was dispatched. |
Account and billing
GET /v1/account Account info and current tier
PATCH /v1/account Update account settings (name, default language, etc.)
GET /v1/usage Current period usage against plan limits
GET /v1/audit Paginated audit log of API and user actions
POST /v1/billing/checkout Create a Stripe Checkout session to upgrade
POST /v1/billing/portal Create a Stripe Customer Portal session to manage subscriptionUsage response
{
"period": "2024-03",
"intakes": { "used": 12, "limit": 50 },
"storage_bytes": { "used": 524288000, "limit": 5368709120 },
"sms_messages": { "used": 3, "limit": 100 }
}Public endpoints
These endpoints require no authentication and are designed for programmatic consumption.
| Endpoint | Description |
|---|---|
GET /pricing.json |
Machine-readable pricing tiers, limits, and feature flags. Intended for agents to decide which plan to recommend or to check quota before creating intakes. |
GET /v1/openapi.json |
OpenAPI 3.1 specification for the entire API. Import into Postman, generate an SDK, or pass to an agent for full API introspection. |
GET /healthz |
Liveness probe. Returns HTTP 200 with {"status":"ok"} when the API process is running. |
GET /readyz |
Readiness probe. Returns HTTP 200 only when the API process and all downstream dependencies (database, object storage, email provider) are healthy. Returns HTTP 503 during deployments or outages. |