Build73 Project API
Project-scoped automation API for Cursor and other agents. Same capabilities are exposed as:
- REST —
https://api.build73.com/api/v1/projects/{projectId}/… - Hosted MCP —
https://api.build73.com/api/mcp/projects/{projectId}
Both call the same ProjectAutomationGateway. Credentials never allow deleting the project, managing members/share links, or billing/subscription.
| Artifact | Location |
|---|---|
| OpenAPI (static) | `docs/project-api-openapi.json` |
| OpenAPI (live) | GET https://api.build73.com/api/v1/openapi.json |
| Agent bundle | `docs/project-api-agent-bundle/` |
| Tool/REST parity | apps/api/src/modules/project-api/project-api.manifest.ts |
Feature flag: project_external_api. Kill switch: PROJECT_EXTERNAL_API_ENABLED=false.
Authentication
All Project API requests require:
Authorization: Bearer <token>Recommended: Project API key (Cursor + REST)
Format: b73_proj_<keyId>_<secret>
1. Open your project in the Build73 editor → Share → API & agents. 2. Create API key — the full key is shown once. 3. Copy the ready MCP config (includes project URL + key) into Cursor Settings → MCP or .cursor/mcp.json. 4. In Cursor chat: *"Use Build73 MCP for this project. Start with project_status."*
The key is bound to one projectId. Default expiry 30 days, max 90 days. Caps at EDITOR role; scopes narrow access further.
MCP config (what the editor generates after key creation):
{
"mcpServers": {
"build73-xxxxxxxx": {
"url": "https://api.build73.com/api/mcp/projects/YOUR_PROJECT_ID",
"headers": {
"Authorization": "Bearer b73_proj_…"
}
}
}
}REST check:
curl -H "Authorization: Bearer b73_proj_…" \
"https://api.build73.com/api/v1/projects/YOUR_PROJECT_ID/status"Create a key programmatically (owner JWT session, not a project key):
curl -X POST "https://api.build73.com/api/projects/{projectId}/api-access/keys" \
-H "Authorization: Bearer <EDITOR_JWT>" \
-H "Content-Type: application/json" \
-d '{"name":"ci-agent","scopes":["project:read","files:write","assets:write","versions:write"],"expiresInDays":30}'Response includes fullKey once. Never commit it.
Alternative: OAuth 2.1 + PKCE (Cursor, no API key)
If you prefer browser login instead of storing a key, use OAuth MCP config (no Authorization header):
{
"mcpServers": {
"build73": {
"url": "https://api.build73.com/api/mcp/projects/YOUR_PROJECT_ID"
}
}
}Cursor discovers OAuth and opens consent on first use.
| Endpoint | URL |
|---|---|
| MCP resource | https://api.build73.com/api/mcp/projects/{projectId} |
| Protected resource metadata | https://api.build73.com/api/.well-known/oauth-protected-resource/mcp/projects/{projectId} |
| Authorization server metadata | https://api.build73.com/api/project-api/oauth/.well-known/oauth-authorization-server |
| Authorize | https://api.build73.com/api/project-api/oauth/authorize |
| Token | https://api.build73.com/api/project-api/oauth/token |
| Revoke | https://api.build73.com/api/project-api/oauth/revoke |
- Public client id for Cursor:
cursor-mcp - Grant: authorization code + PKCE S256 only
- Access token prefix:
b73_pat_…(short-lived, ~1h) - Refresh token prefix:
b73_prt_…(rotating) - Tokens are project-bound;
resource/project_idselects the project at consent time
Scopes
Scopes narrow the underlying project ACL; they never widen it. project:read is always implied. Effective permission = min(project role, credential scopes).
| Scope | Allows |
|---|---|
project:read | Status, files, assets, versions, jobs (read) |
files:write | Create / update / delete code files |
assets:write | Replace asset metadata (not pipeline-owned keys) |
ai:generate | AI generation (charges credits) |
versions:write | Create / restore versions |
build:run | Export / build jobs and cancel (charges credits) |
Default scopes on new keys (if omitted): project:read, files:write, assets:write, versions:write.
VIEWER project members cannot hold write scopes even if requested.
Never granted via Project API: delete project, manage members/links, billing, subscription, ownership transfer.
REST endpoints
Base: https://api.build73.com/api/v1/projects/{projectId}
| Method | Path | Scope | Notes | |||||
|---|---|---|---|---|---|---|---|---|
GET | /status | project:read | Plan, credits, capabilities, prices — call first | |||||
GET | /files | project:read | List; ?path=&cursor=&limit= | |||||
GET | /files?q= | project:read | Content search (q ≥ 2 chars) | |||||
GET | /files/{fileId} | project:read | Content + contentRevision | |||||
POST | /files | files:write | Body { path, content } | |||||
PUT | /files/{fileId} | files:write | Body { content, comment?, expectedRevision? } | |||||
DELETE | /files/{fileId} | files:write | Prefer checkpoint first | |||||
GET | /assets | project:read | `?type=image\ | audio\ | video\ | model3d\ | font\ | other` |
PUT | /assets/{assetId}/meta | assets:write | { name?, metadata?, expectedRevision? } | |||||
GET | /versions | project:read | ?cursor=&limit=&search= | |||||
POST | /versions | versions:write | Checkpoint { name, description? } | |||||
POST | /versions/{versionId}/restore | versions:write | { backupName? } | |||||
POST | /export | build:run | 202 + job; { format?, note? } | |||||
GET | /jobs/{jobId} | project:read | Poll job + credits | |||||
POST | /jobs/{jobId}/cancel | build:run | Cancel non-terminal job |
MCP tool names (parity): project_status, files_list, files_get, files_search, files_write, files_create, files_delete, assets_list, assets_update_meta, versions_list, versions_create, versions_restore, export_start, jobs_get, jobs_cancel.
MCP resources: build73://project/status, build73://project/capabilities, build73://project/billing.
Idempotency
Every mutating REST call requires header:
Idempotency-Key: <client-unique-string>Rules:
- Max length 255
- Scoped per credential (
api_key:…/oauth:…) - Same key + same body → replay stored success response
- Same key + different body →
409 conflict - In-flight duplicate →
409 conflict - Failed attempts do not consume the key (safe to retry)
- TTL ≈ 24 hours
MCP mutations use the same gateway semantics. The tool layer derives a stable key from the JSON-RPC request id. Paid ai_generate_image and ai_generate_audio calls additionally require an idempotencyKey argument: generate one key per intended asset and reuse it when retrying after a timeout.
Optimistic concurrency (expectedRevision)
Project content carries a monotonic contentRevision.
GET /files/{fileId}returnscontentRevisionPUTfile / asset meta may includeexpectedRevision- If the project changed since you read it → `409 conflict`
Recommended flow:
1. GET /status (and file reads) → note contentRevision 2. POST /versions checkpoint before large edits 3. Mutate with expectedRevision 4. On 409, re-read and reconcile
Jobs
Heavy work (export) returns HTTP 202 with a job object:
{
"id": "…",
"projectId": "…",
"type": "export",
"status": "queued",
"progress": 0,
"result": null,
"error": null,
"credits": { "held": 0, "charged": 0, "refunded": 0 },
"createdAt": "…",
"updatedAt": "…",
"finishedAt": null
}Statuses: queued → running → completed | failed | cancelled.
Poll GET /jobs/{jobId}. Cancel with POST /jobs/{jobId}/cancel (requires Idempotency-Key).
Credits & status
GET /status (MCP: project_status) returns everything an agent needs to budget:
project— id, name,contentRevision, countscredential— type, role, scopesplan— tier, name, status (no payment methods / customer ids)credits— total / free / purchased / bonus / subscriptioncapabilities— booleans for read/write/AI/build after scopes + feature flagsactionPrices— credit cost hints for version create, export, AI actions, etc.rateLimits— e.g. 5000 req / 60s (exports also plan-limited)
AI / export operations charge the key/grant owner’s credits. Check credits and actionPrices before expensive calls. Job responses include credits.held|charged|refunded.
Errors
Stable envelope on all /api/v1 routes:
{
"error": {
"code": "conflict",
"message": "…",
"status": 409,
"requestId": "optional-from-X-Request-Id",
"details": {}
}
}| HTTP | code |
|---|---|
| 400 | invalid_request |
| 401 | unauthorized |
| 403 | forbidden |
| 404 | not_found |
| 409 | conflict |
| 413 | payload_too_large |
| 422 | unprocessable_entity |
| 429 | rate_limited |
| 500 | internal_error |
| 503 | service_unavailable |
Pass X-Request-Id to correlate with audit logs. Unauthorized MCP/REST challenges include WWW-Authenticate with protected-resource metadata for OAuth discovery.
Examples
curl — status
export BUILD73_PROJECT_TOKEN='b73_proj_…'
export PROJECT_ID='your-project-uuid'
export API='https://api.build73.com'
curl -sS -H "Authorization: Bearer $BUILD73_PROJECT_TOKEN" \
"$API/api/v1/projects/$PROJECT_ID/status" | jq .curl — checkpoint + write file
IDEM=$(uuidgen)
curl -sS -X POST -H "Authorization: Bearer $BUILD73_PROJECT_TOKEN" \
-H "Idempotency-Key: $IDEM-checkpoint" \
-H "Content-Type: application/json" \
-d '{"name":"agent-checkpoint","description":"before edit"}' \
"$API/api/v1/projects/$PROJECT_ID/versions"
REV=$(curl -sS -H "Authorization: Bearer $BUILD73_PROJECT_TOKEN" \
"$API/api/v1/projects/$PROJECT_ID/files/$FILE_ID" | jq -r .contentRevision)
curl -sS -X PUT -H "Authorization: Bearer $BUILD73_PROJECT_TOKEN" \
-H "Idempotency-Key: $IDEM-write" \
-H "Content-Type: application/json" \
-d "{\"content\":\"console.log('hi')\\n\",\"expectedRevision\":$REV}" \
"$API/api/v1/projects/$PROJECT_ID/files/$FILE_ID"TypeScript — thin client
const API = 'https://api.build73.com';
const token = process.env.BUILD73_PROJECT_TOKEN!;
const projectId = process.env.BUILD73_PROJECT_ID!;
async function projectApi<T>(
method: string,
path: string,
body?: unknown,
idempotencyKey?: string,
): Promise<T> {
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
Accept: 'application/json',
};
if (body !== undefined) headers['Content-Type'] = 'application/json';
if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
const res = await fetch(`${API}/api/v1/projects/${projectId}${path}`, {
method,
headers,
body: body === undefined ? undefined : JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) {
throw new Error(`${res.status} ${data?.error?.code}: ${data?.error?.message}`);
}
return data as T;
}
const status = await projectApi<any>('GET', '/status');
console.log(status.credits, status.capabilities);
await projectApi('POST', '/versions', { name: 'agent-checkpoint' }, crypto.randomUUID());
const file = await projectApi<any>('GET', `/files/${fileId}`);
await projectApi(
'PUT',
`/files/${fileId}`,
{ content: file.content.replace('foo', 'bar'), expectedRevision: file.contentRevision },
crypto.randomUUID(),
);Owner management API
JWT (editor session) + OWNER only. Not reachable with a project API key.
| Method | Path |
|---|---|
GET | /api/projects/{projectId}/api-access/keys |
POST | /api/projects/{projectId}/api-access/keys |
DELETE | /api/projects/{projectId}/api-access/keys/{id} |
GET | /api/projects/{projectId}/api-access/oauth-grants |
DELETE | /api/projects/{projectId}/api-access/oauth-grants/{id} |
GET | /api/projects/{projectId}/api-access/audit |
GET | /api/projects/{projectId}/api-access/connection-info |
connection-info returns MCP URL, REST base, OpenAPI URL, OAuth discovery, and sample mcp.json without secrets.
Security notes
- Store keys/tokens in env or a secret manager; never in git, screenshots, or chat logs.
- Prefer least-privilege scopes; omit
ai:generate/build:rununless needed. - Rotate keys before expiry; revoke immediately if leaked (
DELETE …/keys/{id}). - Token is project-bound — do not reuse across projects.
- Do not send secrets in query strings; Bearer header only.
- Agents must not attempt forbidden actions (delete project, billing, member admin).
- Audit trail records method, path, action, status, latency, credential id — not file contents or prompts.
- Rate limit: treat
429as retryable with backoff. - Production OpenAPI for agents is the slim
/api/v1/openapi.jsondocument — not the internal Nest Swagger UI.
Agent workflow (required)
1. `project_status` / `GET /status` — capabilities, credits, revision 2. Read / search only what you need 3. `versions_create` checkpoint before large or destructive edits 4. Mutate with idempotency + `expectedRevision` where supported 5. Verify (re-read files / status); run export only when asked and credits allow 6. Never delete the project or touch billing
Copy-paste Cursor rules and MCP template: `docs/project-api-agent-bundle/`.