# BOLD API > One front door for every BOLD Studios product, metered against one balance in cents. Base URL: https://boldstudios.io/api Docs: https://boldstudios.io/developers OpenAPI: https://boldstudios.io/api/v1/openapi.json Everything in one fetch: https://boldstudios.io/api/v1/llms-full.txt Drop-in rules for a coding agent: https://boldstudios.io/api/v1/AGENTS.md Every error and what to do about it: https://boldstudios.io/api/v1/docs/errors.md Receiving webhooks from BOLD: https://boldstudios.io/api/v1/docs/webhooks.md Postman collection: https://boldstudios.io/api/v1/collection.json MCP server: https://boldstudios.io/api/v1/mcp (config at https://boldstudios.io/api/v1/mcp.json) SDK: not published to npm yet. Source: https://github.com/BOLDStudios/bold-studios/tree/main/packages/api-sdk ## How it works - Auth: `Authorization: Bearer bold_live__`. Mint a key at https://boldstudios.io/developers. - Errors: `{ "statusCode": 402, "name": "insufficient_credits", "message": "..." }`. Switch on `name`, not on the message text. - Billing: one balance in cents. Charged after success only, never for a failed call. Every account starts with $2.00 free. - Idempotency: send `Idempotency-Key: ` on any paid call and a retry is not charged twice. The replay carries `X-BOLD-Idempotent-Replay: true`. - Every response carries `X-BOLD-Request-Id`, `X-BOLD-Operation`, `X-BOLD-Price-Cents` and `X-BOLD-Charged-Cents`. - Rate limits: per key, 60 requests a minute, 2000 an hour, 20000 a day, in fixed UTC windows. A 429 names the window and carries `Retry-After`. - Check the balance before a batch: `GET https://boldstudios.io/api/v1/balance`. A 402 means the balance is short, not that the key is wrong. - A 403 names the missing scope in `required_scope`. Mint a key with that scope rather than retrying. ## The things that catch people out - **Idempotency deduplicates the charge, not the work.** The operation runs on every request. Only the charge is deduplicated. Scoped to your account. Two accounts can send the same key on the same day without colliding, and only your own earlier call can make a later one a replay. Use a UUID anyway: within one account a reused string is a charge that silently does not happen, and a uuid is the only value you can be sure you have not used before. - **There is no sandbox.** THERE IS NO SANDBOX. A key labelled test is not a test key: it authenticates identically, calls the same providers, and spends the same real cents from the same wallet. A newly minted key carries a `daily_cents_cap` of 500 cents, so a runaway loop costs five dollars rather than a wallet. Lower it for anything you hand to an agent, and raise it deliberately when a real workload needs to. - **Pagination is `limit` and nothing else**, default 25, maximum 100. There is no cursor, no offset, no page number, no total and no has_more. A list is the newest N and nothing else, so an account with more than 100 of something cannot page past the first 100 today. - **Versioning.** `GET /v1` is the contract. Operations are added and retired there. BOLD does not send `Deprecation` or `Sunset` headers today, and there is no changelog feed. The mechanism is polling: fetch `GET /v1` or `/v1/openapi.json` on a schedule and diff it. That is the honest answer rather than a promise of headers that are not sent. - **Placeholders.** Placeholders in this documentation are written as ``, for example `bold_live__`. Ids in worked examples are well formed but fictional: they are the right shape to paste into a client and the wrong value to send to the API, which will answer 404 rather than touching anything real. The full treatment of all five, with the numbers: https://boldstudios.io/api/v1/llms-full.txt ## Privacy - Per call BOLD stores the operation, units, price, latency, a truncated IP (IPv4 /24, IPv6 /48) and the user agent. - The IP and user agent are erased after 30 days. The billing figures are kept. - Emails, phone numbers, IP addresses, user agents, account ids and tokens are stripped from every API response. - Nothing sent through this API is used to train anything. ## BOLD Publish Schedule and publish content to social networks and to your own platform. ### publish.list_posts `GET https://boldstudios.io/api/v1/publish/posts` - Scope: `publish:read` - Price: free - List scheduled and published posts Scheduled and published posts for the BOLD Publish brands connected to this account, newest first, each with its network, publish time, text, link and the impressions and interactions recorded so far. Filter with brand_id, network, since, until and limit, which defaults to 25 and caps at 100. Free. An account with no brands connected gets an empty list and a note rather than an error, so read the brands array in the response before concluding there is nothing scheduled. ```bash curl 'https://boldstudios.io/api/v1/publish/posts?limit=25&network=instagram' -H 'Authorization: Bearer bold_live_...' ``` ```json { "posts": [ { "network": "instagram", "published_at": "2026-08-30T14:02:00Z", "text": "New drop lands Friday. Link in bio.", "impressions": 4210, "interactions": 318 } ], "brands": [ "4c7e0f9a-6b21-4d83-9f5c-2a1e7d0b8c34" ] } ``` ### publish.list_destinations `GET https://boldstudios.io/api/v1/publish/destinations` - Scope: `publish:read` - Price: free - List your destinations Every platform of yours that receives scheduled posts, without the signing keys. ```bash curl 'https://boldstudios.io/api/v1/publish/destinations?brand_id=4c7e0f9a-6b21-4d83-9f5c-2a1e7d0b8c34' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "destinations": [ { "id": "5a91b73c-08de-4f27-b1a6-9c4d5e2f0731", "brand_id": "4c7e0f9a-6b21-4d83-9f5c-2a1e7d0b8c34", "name": "Acme ops webhook", "endpoint_url": "https://acme.example.com/hooks/bold", "is_active": true, "created_at": "2026-08-28T11:20:00Z", "last_delivery_at": "2026-09-01T16:45:00Z" } ] } ``` ### publish.analytics `GET https://boldstudios.io/api/v1/publish/analytics` - Scope: `publish:read` - Price: 1c per call - Engagement for a brand over a date range Daily engagement for your BOLD Publish brands over a date range, returned both as the raw daily rows and as totals rolled up by network and metric. The roll up honours each metric's own aggregation, so impressions are summed across the window while a follower count is taken as the peak rather than added up, which is why the totals are not simply the sum of the rows. Costs 1c per call, unlike the free publish.list_posts, and it covers days back from today (default 30, maximum 365) unless you pass explicit from and to dates. ```bash curl 'https://boldstudios.io/api/v1/publish/analytics?days=30' -H 'Authorization: Bearer bold_live_...' ``` ```json { "from": "2026-08-03", "to": "2026-09-02", "totals": [ { "network": "instagram", "slot": "impressions", "agg": "sum", "value": 128400, "days": 30 } ] } ``` ### publish.create_destination `POST https://boldstudios.io/api/v1/publish/destinations` - Scope: `publish:write` - Price: free - Register a destination Registers an https endpoint that will receive your scheduled posts. Returns the signing key once. ```bash curl -X POST 'https://boldstudios.io/api/v1/publish/destinations' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"brand_id":"4c7e0f9a-6b21-4d83-9f5c-2a1e7d0b8c34","name":"Acme ops webhook","endpoint_url":"https://acme.example.com/hooks/bold"}' ``` ```json { "ok": true, "id": "5a91b73c-08de-4f27-b1a6-9c4d5e2f0731", "brand_id": "4c7e0f9a-6b21-4d83-9f5c-2a1e7d0b8c34", "name": "Acme ops webhook", "endpoint_url": "https://acme.example.com/hooks/bold", "signing_key": "bdsk_3f9c1a7e52d80b46af13e6c94d27b085f0a31c6e8b25d4790fa6c3e18b5d02a7", "note": "The signing key is shown once. Store it now and verify it on every delivery." } ``` ### publish.rotate_destination `POST https://boldstudios.io/api/v1/publish/destinations/{id}/rotate` - Scope: `publish:write` - Price: free - Rotate a signing key Issues a new signing key and invalidates the old one immediately. ```bash curl 'https://boldstudios.io/api/v1/publish/destinations/{id}/rotate' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "id": "5a91b73c-08de-4f27-b1a6-9c4d5e2f0731", "signing_key": "bdsk_3f9c1a7e52d80b46af13e6c94d27b085f0a31c6e8b25d4790fa6c3e18b5d02a7", "rotated_at": "2026-09-02T10:05:00Z", "note": "The previous key stopped working the moment this one was issued." } ``` ### publish.delete_destination `DELETE https://boldstudios.io/api/v1/publish/destinations/{id}` - Scope: `publish:write` - Price: free - Remove a destination Deactivates the destination and cancels anything still queued for it. ```bash curl 'https://boldstudios.io/api/v1/publish/destinations/{id}' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "id": "5a91b73c-08de-4f27-b1a6-9c4d5e2f0731" } ``` ## BOLD QR Tracked short links and QR codes that keep resolving, including after a subscription lapses. ### qr.create `POST https://boldstudios.io/api/v1/qr/codes` - Scope: `qr:write` - Price: 2c per code - Create a tracked short link Returns a boldstudios.io/q/ short link and the id you use to read its scans. The link keeps resolving for as long as the code exists, including after a plan lapses, which is the one thing every competitor takes away. ```bash curl -X POST 'https://boldstudios.io/api/v1/qr/codes' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"title":"Spring flyer","target_url":"https://example.com/spring","ec_level":"M"}' ``` ```json { "id": "9b1c7f42-3ad8-4e05-9c61-0f2b8d6a4e37", "slug": "k3Rm9Ty", "short_url": "https://boldstudios.io/q/k3Rm9Ty", "title": "Spring flyer", "target_url": "https://example.com/spring", "ec_level": "M" } ``` ### qr.list `GET https://boldstudios.io/api/v1/qr/codes` - Scope: `qr:read` - Price: free - List your tracked links Your own codes, newest first, with lifetime scan counts. Free. ```bash curl 'https://boldstudios.io/api/v1/qr/codes?limit=25' -H 'Authorization: Bearer bold_live_...' ``` ```json { "codes": [ { "id": "9b1c7f42-3ad8-4e05-9c61-0f2b8d6a4e37", "slug": "k3Rm9Ty", "short_url": "https://boldstudios.io/q/k3Rm9Ty", "title": "Spring flyer", "total_scans": 412, "unique_scans": 380 } ] } ``` ### qr.stats `GET https://boldstudios.io/api/v1/qr/codes/{id}/scans` - Scope: `qr:read` - Price: 1c per call - Scans for one link Daily scan and unique counts plus a country breakdown. Aggregates only: no IP, device or browser data about the people who scanned leaves this API. ```bash curl 'https://boldstudios.io/api/v1/qr/codes/{id}/scans?days=30' -H 'Authorization: Bearer bold_live_...' ``` ```json { "id": "9b1c7f42-3ad8-4e05-9c61-0f2b8d6a4e37", "slug": "k3Rm9Ty", "total_scans": 412, "unique_scans": 380, "period_days": 30, "daily": [ { "date": "2026-09-01", "scans": 22, "unique": 20 } ], "by_country": [ { "country": "US", "scans": 340 } ] } ``` ## BOLD Embed Put a live card of your own data on somebody else's website, and turn the same data into a 1200x630 share image. Themed, auto-sizing, measured, and always current. ### embed.create `POST https://boldstudios.io/api/v1/embed/embeds` - Scope: `embed:write` - Price: 1c per embed - Create or update an embed Creates an embeddable card and returns its token, the widget URL, the share-image URL and a paste-ready snippet. Idempotent on (kind, ref): calling it again for the same subject updates that embed rather than minting a second token, which matters because the first token is already pasted into somebody's website. ```bash curl -X POST 'https://boldstudios.io/api/v1/embed/embeds' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"kind":"scorecard","ref":"jane-doe","mode":"fetch","source_path":"data/scorecard/jane-doe","width":480,"allowed_domains":["janedoe.example.com"]}' ``` ```json { "ok": true, "token": "e_0f3a9c7d21b4482e9a5c6d8e1f204b73", "kind": "scorecard", "ref": "jane-doe", "embed_url": "https://boldstudios.io/embed/v/e_0f3a9c7d21b4482e9a5c6d8e1f204b73", "image_url": "https://boldstudios.io/embed/i/e_0f3a9c7d21b4482e9a5c6d8e1f204b73.png", "snippet": "
" } ``` ### embed.list `GET https://boldstudios.io/api/v1/embed/embeds` - Scope: `embed:read` - Price: free - List your embeds Every embed on your property, newest first. Returns the token, kind, ref, mode, width, enabled state and domain allowlist. Does not return the blocks; read one with embed.create's idempotent behaviour or render it. ```bash curl 'https://boldstudios.io/api/v1/embed/embeds?kind=scorecard&limit=50' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "property": "acme", "embeds": [ { "token": "e_0f3a9c7d21b4482e9a5c6d8e1f204b73", "kind": "scorecard", "ref": "jane-doe", "title": "Jane Doe", "mode": "fetch", "width": 480, "enabled": true, "allowed_domains": [ "janedoe.example.com" ], "created_at": "2026-09-03T12:18:34Z", "updated_at": "2026-09-03T12:18:34Z" } ] } ``` ### embed.delete `DELETE https://boldstudios.io/api/v1/embed/embeds/{token}` - Scope: `embed:write` - Price: free - Delete an embed Removes the embed and its counters. Anything already pasted into a website stops rendering, so this is the call to make when a card should no longer exist rather than the one to make to hide it: set enabled false through embed.create for that. ```bash curl 'https://boldstudios.io/api/v1/embed/embeds/{token}' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "deleted": true, "token": "e_0f3a9c7d21b4482e9a5c6d8e1f204b73" } ``` ### embed.stats `GET https://boldstudios.io/api/v1/embed/stats` - Scope: `embed:read` - Price: free - Views, clicks and where from Views, clicks and share-image renders, by day and by the domain that showed the card. Pass a token for one embed or leave it off for the whole property. There is no visitor identity in here at all: the service stores a day, a domain and a count, and no IP, user agent or identifier of any kind. ```bash curl 'https://boldstudios.io/api/v1/embed/stats?days=30&token=e_0f3a9c7d21b4482e9a5c6d8e1f204b73' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "property": "acme", "stats": { "from": "2026-08-05", "to": "2026-09-03", "totals": { "views": 1840, "clicks": 96, "images": 212, "embeds": 34 }, "by_day": [ { "day": "2026-09-03", "views": 71, "clicks": 4 } ], "top_domains": [ { "domain": "janedoe.example.com", "views": 1290 } ] } } ``` ## BOLD Refer Referral links, first-touch attribution, commission with a hold period, and payouts. The parts that are not the link. ### refer.upsert_program `POST https://boldstudios.io/api/v1/refer/programs` - Scope: `refer:write` - Price: free - Create or update a programme A programme is the commercial shape: percent or flat, the rate, whether it pays on the first sale or every one, the attribution window, the hold period before commission is payable, and the payout threshold. All of it is data, so repricing is this call rather than a deploy. ```bash curl -X POST 'https://boldstudios.io/api/v1/refer/programs' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"program":"acme_partners","display_name":"Acme partner programme","commission_kind":"percent","commission_rate":20,"recurring":"first_only","cookie_days":90,"hold_days":30,"min_payout_cents":10000}' ``` ```json { "ok": true, "program": "acme_partners", "commission_rate": 20, "hold_days": 30 } ``` ### refer.upsert_partner `POST https://boldstudios.io/api/v1/refer/partners` - Scope: `refer:write` - Price: free - Create or update a partner and get their link Creates the partner and their shareable link in one call, because a partner without a code cannot refer anybody and that half-finished state is how referral systems end up with codes and no conversions. Give a contact address and only its SHA-256 is stored: enough to reconcile against, useless if the table ever leaks. ```bash curl -X POST 'https://boldstudios.io/api/v1/refer/partners' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"program":"acme_partners","partner_ref":"u_8814","display_name":"Jane Doe","payout_method":"bold_wallet"}' ``` ```json { "ok": true, "partner": { "partner_ref": "u_8814", "display_name": "Jane Doe", "status": "active", "payout_method": "bold_wallet" }, "codes": [ { "code": "K7M2QW", "destination_url": "https://acme.example.com" } ], "links": [ "https://boldstudios.io/refer/r/K7M2QW" ] } ``` ### refer.conversion `POST https://boldstudios.io/api/v1/refer/conversions` - Scope: `refer:write` - Price: 1c per call - Record a sale Records a sale and works out what it earns: whose it is, at the rate frozen when the referral was made, and whether the programme pays on this one at all. Idempotent on (program, external_ref, kind), because the caller is normally a payment webhook and repeat delivery is routine rather than a fault. A replay comes back flagged as a replay and charges nothing twice. A sale with nothing linking it to a partner comes back attributed false with a reason, not an error. ```bash curl -X POST 'https://boldstudios.io/api/v1/refer/conversions' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"program":"acme_partners","external_ref":"inv_10241","kind":"sale","subject_ref":"cus_5512","amount_cents":500000,"visit_token":"v_9c1f7b2e5d0a4318bc6e91f207d54a83"}' ``` ```json { "ok": true, "attributed": true, "conversion": { "external_ref": "inv_10241", "amount_cents": 500000, "commission_cents": 100000, "rate_applied": 20, "status": "pending", "hold_until": "2026-10-03T13:02:07Z" } } ``` ### refer.partner_summary `GET https://boldstudios.io/api/v1/refer/partners/{partner_ref}` - Scope: `refer:read` - Price: free - One partner's numbers Clicks, people referred, earnings split into pending, approved, paid and rejected, and their last 25 conversions. This is what you render on a partner's own dashboard. ```bash curl 'https://boldstudios.io/api/v1/refer/partners/{partner_ref}?program=acme_partners' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "partner_ref": "u_8814", "clicks": 412, "referred": 26, "earnings": { "pending_cents": 100000, "approved_cents": 0, "paid_cents": 240000, "rejected_cents": 0 } } ``` ### refer.payouts_due `GET https://boldstudios.io/api/v1/refer/payouts/due` - Scope: `refer:read` - Price: free - What is owed and to whom Every partner with approved commission that has not been paid, with the total and whether it has cleared the programme's minimum. Approved means the hold period has passed and nothing reversed it. ```bash curl 'https://boldstudios.io/api/v1/refer/payouts/due?program=acme_partners' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "due": [ { "program": "acme_partners", "partner_ref": "u_8814", "conversions": 1, "amount_cents": 100000, "min_payout_cents": 10000, "ready": true } ] } ``` ### refer.stats `GET https://boldstudios.io/api/v1/refer/stats` - Scope: `refer:read` - Price: free - Programme totals Clicks, people referred, conversions, revenue attributed, commission earned and commission still owed, across your whole property. ```bash curl 'https://boldstudios.io/api/v1/refer/stats?days=30' -H 'Authorization: Bearer bold_live_...' ``` ```json { "ok": true, "property": "acme", "stats": { "from": "2026-08-05", "to": "2026-09-03", "programs": 1, "partners": 12, "clicks": 3140, "referred": 208, "conversions": 14, "revenue_cents": 4100000, "commission_cents": 820000, "owed_cents": 300000 } } ``` ## BOLD Sign Send a document for signature and get back links you can deliver however you like. Dropbox Sign charges $75 a month for about fifty of these. ### sign.create_document `POST https://boldstudios.io/api/v1/sign/documents` - Scope: `sign:write` - Price: 10c per envelope - Send a document for signature Give it a title, a publicly reachable URL to the document, and the people who need to sign. You get back a signing URL for each of them. BOLD does not email your signers: you get the links and send them the way you already send things, which keeps your recipients on your sending reputation rather than ours. Charged once per envelope, however many signers are on it. ```bash curl -X POST 'https://boldstudios.io/api/v1/sign/documents' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"title":"Photography services agreement","source_url":"https://example.com/agreement.pdf","signers":[{"email":"dana@example.com","name":"Dana Whitfield"}]}' ``` ```json { "document_id": "0d2a5c81-7f34-4b96-a0e2-6d8c1b49f537", "title": "Photography services agreement", "status": "sent", "expires_at": "2026-10-02T18:00:00Z", "signers": [ { "signer_id": "77c1e408-9d52-4a37-b6f1-24e0a9c73d85", "display_name": "Dana Whitfield", "role": "signer", "order": 0, "status": "pending", "signing_url": "https://boldstudios.io/sign/9f3ca7d21e6b48059c3fa1287d6e40b5" } ] } ``` ### sign.list_documents `GET https://boldstudios.io/api/v1/sign/documents` - Scope: `sign:read` - Price: free - List your signature requests Your documents, newest first, with how many signers have signed. Free. ```bash curl 'https://boldstudios.io/api/v1/sign/documents?limit=25' -H 'Authorization: Bearer bold_live_...' ``` ```json { "documents": [ { "document_id": "0d2a5c81-7f34-4b96-a0e2-6d8c1b49f537", "title": "Photography services agreement", "status": "completed", "signers": 2, "signed": 2 } ] } ``` ### sign.get_document `GET https://boldstudios.io/api/v1/sign/documents/{id}` - Scope: `sign:read` - Price: free - Check a signature request Status of the document and of each signer. Returns display names and states, never the email addresses you supplied and never a signing token, so a stolen key cannot be turned into a list of the people involved. Free. ```bash curl 'https://boldstudios.io/api/v1/sign/documents/{id}' -H 'Authorization: Bearer bold_live_...' ``` ```json { "document_id": "0d2a5c81-7f34-4b96-a0e2-6d8c1b49f537", "title": "Photography services agreement", "status": "completed", "completed_at": "2026-09-03T09:14:00Z", "signers": [ { "signer_id": "77c1e408-9d52-4a37-b6f1-24e0a9c73d85", "display_name": "Dana Whitfield", "status": "signed", "signed_at": "2026-09-03T09:14:00Z", "signature_type": "typed" } ] } ``` ### sign.audit_trail `GET https://boldstudios.io/api/v1/sign/documents/{id}/audit` - Scope: `sign:read` - Price: free - The audit trail Every event on the document with its timestamp, the signer it belongs to, and the IP and user agent it came from. This is the record a dispute asks for, and it is the only read in the whole BOLD API that returns an address. It is reachable only by the account that sent the document. ```bash curl 'https://boldstudios.io/api/v1/sign/documents/{id}/audit' -H 'Authorization: Bearer bold_live_...' ``` ```json { "document_id": "0d2a5c81-7f34-4b96-a0e2-6d8c1b49f537", "status": "completed", "events": [ { "action": "document_created", "at": "2026-09-02T18:00:00Z", "actor": "sender" }, { "action": "signer_viewed", "at": "2026-09-03T09:11:00Z", "actor": "dana@example.com", "ip": "203.0.113.42", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" }, { "action": "consent_given", "at": "2026-09-03T09:14:00Z", "actor": "dana@example.com", "ip": "203.0.113.42" }, { "action": "signer_signed", "at": "2026-09-03T09:14:00Z", "actor": "dana@example.com", "ip": "203.0.113.42" }, { "action": "document_completed", "at": "2026-09-03T09:14:00Z", "actor": "sender" } ] } ``` ### sign.void_document `POST https://boldstudios.io/api/v1/sign/documents/{id}/void` - Scope: `sign:write` - Price: free - Void a signature request Stops a document that has not completed. Anyone opening a signing link afterwards is told it was withdrawn. Free. ```bash curl -X POST 'https://boldstudios.io/api/v1/sign/documents/{id}/void' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"reason":"Terms renegotiated"}' ``` ```json { "document_id": "0d2a5c81-7f34-4b96-a0e2-6d8c1b49f537", "status": "voided", "voided_at": "2026-09-02T19:00:00Z" } ``` ## BOLD Calendar Read and write a calendar, ask when somebody is free, and take a booking. The free/busy maths already carves shifts out of availability, so a booking can never land on time that is already spoken for. ### calendar.list `GET https://boldstudios.io/api/v1/calendar/calendars` - Scope: `calendar:read` - Price: free - Every calendar you can see Calendars you own and calendars somebody shared with you, newest ownership first. personal marks the one BOLD made for your own week; it is not counted against your plan. ### calendar.create `POST https://boldstudios.io/api/v1/calendar/calendars` - Scope: `calendar:write` - Price: 2c per calendar - Make a calendar Creates an empty calendar you own, with you on it as an admin. Timezone matters: every time you send afterwards is read as wall clock in this zone, which is what keeps "9am Tuesday" meaning that through a daylight saving change. ### calendar.list_events `GET https://boldstudios.io/api/v1/calendar/calendars/{calendar_id}/events` - Scope: `calendar:read` - Price: free - What is on a calendar Entries between two dates, oldest first, in the calendar's own timezone. Absent dates read the next sixty days. A range longer than a year is clamped rather than refused, so one call can never walk a whole history. ### calendar.create_event `POST https://boldstudios.io/api/v1/calendar/calendars/{calendar_id}/events` - Scope: `calendar:write` - Price: 1c per entry - Put something on a day Adds one entry. Times are wall clock in the calendar's zone, not instants. This never opens the entry for volunteers to sign up for: that is a different act with a different shape, and defaulting to it would attach every job on the calendar to whatever you just posted. ### calendar.slots `GET https://boldstudios.io/api/v1/calendar/booking-types/{booking_type_id}/slots` - Scope: `calendar:read` - Price: free - When you are free to be booked Open start times for one booking type, day by day, as absolute instants. Availability windows, the notice period, the daily cap, the gap after a meeting, bookings already taken and any shift on the calendar are all applied before you see this, so anything returned here is genuinely bookable. ### calendar.book `POST https://boldstudios.io/api/v1/calendar/booking-types/{booking_type_id}/bookings` - Scope: `calendar:write` - Price: 5c per booking - Take a booking Books one of the open times. Two people racing for the same slot is settled in the database, not in your code, so the loser gets slot_gone rather than a double booking. The manage_token comes back exactly once and is the booker's only way to change or cancel it later, so pass it on. ### calendar.list_bookings `GET https://boldstudios.io/api/v1/calendar/bookings` - Scope: `calendar:read` - Price: free - Bookings on your calendars Upcoming bookings across every calendar you can see, soonest first. Defaults to the next ninety days. ### calendar.cancel_booking `POST https://boldstudios.io/api/v1/calendar/bookings/{booking_id}/cancel` - Scope: `calendar:write` - Price: free - Cancel a booking Cancels a booking on one of your calendars and emails the person who made it. Cancelling somebody's meeting and letting them turn up anyway is worse than not offering cancellation at all, so the email is not optional. Cancelling an already cancelled booking is not an error. ## BOLD AI Chat, generation and transcription. ### ai.chat `POST https://boldstudios.io/api/v1/ai/chat` - Scope: `ai:chat` - Price: 1c per 1k tokens - A chat completion Charged per 1,000 tokens in and out combined, rounded up. Send model flash, pro or max, or an exact model name. A failed or refused generation is never charged. ```bash curl -X POST 'https://boldstudios.io/api/v1/ai/chat' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"model":"pro","max_tokens":512,"messages":[{"role":"user","content":"Write one line of launch copy for a coffee subscription."}]}' ``` ```json { "content": "Freshly roasted, on your counter before you run out.", "model": "claude-sonnet-5", "stop_reason": "end_turn", "usage": { "input_tokens": 18, "output_tokens": 12, "total_tokens": 30 } } ``` ### ai.generate_image `POST https://boldstudios.io/api/v1/ai/images` - Scope: `ai:images` - Price: 25c per image - Generate an image Returns a signed URL valid for 24 hours, stored in a private bucket. Standard is one unit, hd is four. The URL contains nothing that identifies the account it belongs to. ```bash curl -X POST 'https://boldstudios.io/api/v1/ai/images' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"text":"A flat lay of a coffee bag on pale linen","style":"clean","quality":"standard","size":"1024x1024"}' ``` ```json { "url": "https://fdyhcokgnymevgtixwuf.supabase.co/storage/v1/object/sign/boldapi-media/1f8b0c2a-4d76-4e91-8b03-7c5a2e9d1046/9a4c7e13-2b58-4f60-a1d9-3e07c8b45f22.png?token=eyJhbGciOiJIUzI1NiJ9.example", "expires_in_seconds": 86400, "prompt": "the prompt actually sent to the model", "style": "clean", "quality": "standard", "size": "1024x1024" } ``` ### ai.transcribe `POST https://boldstudios.io/api/v1/ai/transcribe` - Scope: `ai:transcribe` - Price: 5c per minute - Transcribe audio or video Give it a public audio or video URL. Waits up to 90 seconds for the result and returns it inline; if the file takes longer than that you get a job id and poll ai.transcript. Set wait_seconds to hold for less; anything above 90 is clamped to 90. Charged once, per minute of audio, whichever call first sees it finished. ```bash curl -X POST 'https://boldstudios.io/api/v1/ai/transcribe' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"audio_url":"https://example.com/episode.mp3","language_code":"en","speaker_labels":false}' ``` ```json { "status": "completed", "id": "6f2c1b3d-8a45-4e12-9c07-5b3e2d1a9f04", "language": "en", "audio_duration_seconds": 214, "minutes_billed": 4, "word_count": 512, "text": "The full transcript, as one string." } ``` ### ai.transcript `GET https://boldstudios.io/api/v1/ai/transcribe/{id}` - Scope: `ai:transcribe` - Price: 5c per minute - Fetch a transcript Poll a transcription started earlier. Charged once, per minute of audio, on the first response that is complete. Polling a job that is still running is free, and polling a finished one again is free. ```bash curl 'https://boldstudios.io/api/v1/ai/transcribe/{id}?words=true' -H 'Authorization: Bearer bold_live_...' ``` ```json { "status": "completed", "id": "6f2c1b3d-8a45-4e12-9c07-5b3e2d1a9f04", "audio_duration_seconds": 214, "minutes_billed": 4, "text": "The full transcript, as one string.", "words": [ { "text": "The", "start_ms": 120, "end_ms": 260, "confidence": 0.99 } ] } ``` ## BOLD Studios Account, wallet and usage. ### studios.usage `GET https://boldstudios.io/api/v1/usage` - Scope: `studios:read` - Price: free - Your calls and spend What this account has called and what it has spent, rolled up over the last days days: total calls, how many failed, spend in cents, and a per operation breakdown sorted by spend. days defaults to 30 and caps at 365. It is free, so it is the cheapest way to reconcile a bill or find the operation that is costing the most, but it reads at most the 5,000 most recent calls in the window, so a very busy account sees a shorter window than it asked for. ```bash curl 'https://boldstudios.io/api/v1/usage?days=30' -H 'Authorization: Bearer bold_live_...' ``` ```json { "period_days": 30, "calls": 412, "errors": 3, "spend_cents": 388, "spend": "$3.88", "by_operation": [ { "operation": "ai.chat", "calls": 380, "units": 388, "cents": 388, "errors": 3 } ] } ``` ### studios.balance `GET https://boldstudios.io/api/v1/balance` - Scope: `studios:read` - Price: free - Your BOLD balance Your cleared BOLD wallet balance in cents and as a formatted string, plus whether auto reload is on and the threshold and top up amount it uses. This is the same number every paid call is authorized against, and the call is free, so it is the safe way to prove a key works before spending anything. Only cleared ledger entries count, so credit that is still settling does not appear here yet. ```bash curl 'https://boldstudios.io/api/v1/balance' -H 'Authorization: Bearer bold_live_...' ``` ```json { "balance_cents": 1911, "balance": "$19.11", "currency": "usd", "auto_reload": { "enabled": false }, "add_credit_url": "https://boldstudios.io/developers#credits" } ``` ## BOLD Sites Read and edit the live content of a BOLD Studios site, then publish it. Drafts and live values are separate, every publish is a version, and any version can be put back. Free: the site platform is paid for by its care plan, not per call. ### site.list_projects `GET https://boldstudios.io/api/v1/site/projects` - Scope: `site:read` - Price: free - Every site you can edit The sites this key may work on, with how many fields each has and how many are waiting to be published. Start here: nothing else in this product works without a project id. ```bash curl 'https://boldstudios.io/api/v1/site/projects' -H 'Authorization: Bearer bold_live_...' ``` ```json { "projects": [ { "project_id": "6d0d810c-6e9e-4304-a0dd-0ed1988f3eeb", "name": "Open Hands Ministry", "site_url": "https://openhandsministry.example", "status": "live", "locales": [ "en" ], "fields": 24, "unpublished": 2, "last_published_at": "2026-09-04T18:22:10Z" } ] } ``` ### site.get_content `GET https://boldstudios.io/api/v1/site/projects/{project_id}/content` - Scope: `site:read` - Price: free - The editable fields of a site Every editable field, draft and live side by side, with the label, group and character limit the site itself registered. This is the schema and the content in one call, so an agent can edit a page without being told the shape first. ```bash curl 'https://boldstudios.io/api/v1/site/projects/{project_id}/content?locale=en' -H 'Authorization: Bearer bold_live_...' ``` ```json { "project_id": "6d0d810c-6e9e-4304-a0dd-0ed1988f3eeb", "locale": "en", "unpublished": 1, "fields": [ { "key": "programs.hero.title", "kind": "text", "label": "Programmes headline", "group": "Programmes", "help_text": "Shown at the top of the programmes page.", "max_length": 80, "draft": "Serving our neighbours every week", "live": "Serving our neighbours", "changed": true, "published_at": "2026-09-04T18:22:10Z" } ] } ``` ### site.save_content `POST https://boldstudios.io/api/v1/site/projects/{project_id}/content` - Scope: `site:write` - Price: free - Save drafts Saves values as drafts. Nothing here reaches a visitor: publishing is a separate call, on purpose, so an agent can write a whole page and a person can still be the one who decides it goes live. A key the site has not registered is still saved, because a site can add a field before it next boots. ```bash curl -X POST 'https://boldstudios.io/api/v1/site/projects/{project_id}/content' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"locale":"en","edits":{"programs.hero.title":"Serving our neighbours every week","programs.hero.body":"Hot meals on Tuesdays and Fridays, no questions asked."}}' ``` ```json { "saved": 2, "unpublished": 2 } ``` ### site.publish `POST https://boldstudios.io/api/v1/site/projects/{project_id}/publish` - Scope: `site:write` - Price: free - Make the drafts live Copies every changed draft onto the live site and records the result as a numbered version, so any publish can be put back later. Publishing with nothing changed is not an error, it answers published 0. ```bash curl -X POST 'https://boldstudios.io/api/v1/site/projects/{project_id}/publish' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"note":"Autumn programme times"}' ``` ```json { "published": 2, "version": 7, "version_id": "e21a6273-94f4-43b9-ab71-c4b56dac3a9e" } ``` ### site.discard `POST https://boldstudios.io/api/v1/site/projects/{project_id}/discard` - Scope: `site:write` - Price: free - Throw the drafts away Puts every draft back to what visitors are already seeing. The undo for a save that should not have happened, and it cannot touch the live site. ```bash curl 'https://boldstudios.io/api/v1/site/projects/{project_id}/discard' -H 'Authorization: Bearer bold_live_...' ``` ```json { "discarded": 2 } ``` ### site.versions `GET https://boldstudios.io/api/v1/site/projects/{project_id}/versions` - Scope: `site:read` - Price: free - What has been published Published versions, newest first, so a caller can pick one to go back to. ```bash curl 'https://boldstudios.io/api/v1/site/projects/{project_id}/versions?limit=5' -H 'Authorization: Bearer bold_live_...' ``` ```json { "versions": [ { "version": 7, "note": "Autumn programme times", "keys_changed": 2, "created_at": "2026-09-05T09:14:02Z" }, { "version": 6, "note": null, "keys_changed": 1, "created_at": "2026-08-28T16:40:55Z" } ] } ``` ### site.rollback `POST https://boldstudios.io/api/v1/site/projects/{project_id}/rollback` - Scope: `site:write` - Price: free - Put an earlier version back Restores a published version to both live and draft. It is recorded as a NEW version rather than by rewinding history, so the fact somebody rolled back is itself in the record and can be rolled back in turn. ```bash curl -X POST 'https://boldstudios.io/api/v1/site/projects/{project_id}/rollback' \ -H 'Authorization: Bearer bold_live_...' \ -H 'Content-Type: application/json' \ -d '{"version":6}' ``` ```json { "restored_from": 6, "version": 8, "keys_changed": 2 } ``` ### site.status `GET https://boldstudios.io/api/v1/site/projects/{project_id}/status` - Scope: `site:read` - Price: free - Whether the site is live and editable Hosting and editing state, and when either is due to pause if the account is behind. A site that has gone quiet always says why rather than simply failing. ```bash curl 'https://boldstudios.io/api/v1/site/projects/{project_id}/status' -H 'Authorization: Bearer bold_live_...' ``` ```json { "project_id": "6d0d810c-6e9e-4304-a0dd-0ed1988f3eeb", "state": "active", "hosting_live": true, "marketing_allowed": true, "editing_allowed": true, "at_risk": false, "past_due_since": null, "days_past_due": 0, "enforced": true } ```