API

Version v0, September 2026

This is a beta and it is unstable. Shapes may change, endpoints may be withdrawn, and there is no compatibility promise, no deprecation window, no uptime commitment and no support commitment. The first stable version will be v1, and it does not exist yet. Do not build anything you cannot afford to fix.

Describe a business in one sentence and get back a composed website you can publish. The same orchestration that runs behind the portal, reached over HTTP.

Getting a key

Sign in to the portal and say what you want. Something like create an API key is enough. Dark Orb will ask which permissions the key should carry and then show you the key once, on screen, at the moment it is made. It is never shown again and it cannot be recovered, so copy it somewhere safe before you move on. If you lose it, ask for a new one and revoke the old.

Keys belong to your account, not to you personally. If you lose access to the account, the key stops working with it.

If you run sites for clients, each client is a business in your account. Ask for a key for one of them by name, something like an API key for Hats of Leeds that can build and publish, and that key reaches only that business and its sites. Anything else in your account answers 404 to it, exactly as another account's would. If the business leaves your account, the key stops working with it.

Making a request

The base URL is https://portal.darkorb.ai. Send the key as a bearer token:

Authorization: Bearer $DARK_ORB_KEY

Every response is JSON and carries Cache-Control: no-store and X-Dark-Orb-Api-Version: v0. Errors always take the same shape:

{ "error": { "code": "scope_insufficient", "message": "This key cannot do that." } }

A worked example

Compose a site, then publish it. Two requests, start to finish:

curl -X POST https://portal.darkorb.ai/api/v0/sites \
  -H "Authorization: Bearer $DARK_ORB_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"intent":"A joinery in Leeds making bespoke kitchens and fitted wardrobes."}'

That answers 201 with the site, including its siteId and the url the site will live at. Then publish it:

curl -X POST https://portal.darkorb.ai/api/v0/sites/st_.../publish \
  -H "Authorization: Bearer $DARK_ORB_KEY"

Open the url from either response and the site is there, live, for anyone.

Endpoints

EndpointScopeLimit
GET /api/v0/whoamiany120 per hour, per key
GET /api/v0/sitessites:read120 per hour, per key
GET /api/v0/sites/{siteId}sites:read120 per hour, per key
POST /api/v0/sitessites:compose5 per hour, per account
POST /api/v0/sites/{siteId}/publishsites:publish30 per hour, per key
GET /api/v0/businessesbusinesses:read120 per hour, per key
GET /api/v0/businesses/{businessId}/usagebusinesses:read120 per hour, per key

GET /api/v0/whoami

What this key is and what it can do right now. Useful for checking a key works before you rely on it.

{
  "accountId": "acc_...",
  "keyId": "ak_...",
  "scopes": ["sites:read", "sites:compose", "sites:publish", "businesses:read"],
  "expiresAt": null,
  "businessId": null
}

businessId names the one business a key made for a client reaches, and is null for a key that reaches your whole account.

The scopes reported are the ones the key can exercise at this moment, which can be narrower than the ones it was issued with if the person who issued it has since lost the access it was drawn from.

GET /api/v0/sites

The account's sites, one page at a time:

{
  "sites": [ ... ],
  "nextCursor": "WyJ2MCIsIjIwMjYtMDkt..."
}

Ask for a page size with ?limit= (25 by default, 100 at most), and ask for the next page with ?cursor=, passing back the nextCursor you were given. Keep going until nextCursor is null, which is the end. A limit or a cursor we cannot use is refused with 422 rather than quietly changed to something else.

Add ?businessId= to see one of your businesses' sites only. A key made for one business only ever sees that business's sites, whether or not it asks.

A page can come back shorter than you asked for and still not be the last one, so read nextCursor, not the number of sites. The cursor marks your place rather than counting from the start, so a site made while you are part-way through cannot push one you have not seen out of view.

Each entry is the same shape:

{
  "siteId": "st_...",
  "slug": "brae-joinery",
  "createdAt": "2026-09-01T09:12:44.019Z",
  "updatedAt": "2026-09-01T09:12:44.019Z",
  "currentVersionId": "v_...",
  "publishedVersionId": null,
  "published": false,
  "url": "https://portal.darkorb.ai/site/brae-joinery"
}

published is true only when the version that is live is also the newest one. A site composed again after publishing reads as not published until you publish the new version.

GET /api/v0/sites/{siteId}

One site, in the shape above. A site id belonging to another account answers 404, exactly as an id that does not exist does, so the endpoint cannot be used to find out what else is out there.

POST /api/v0/sites

Compose a site. The body carries an intent, and an unknown field is refused rather than ignored:

{ "intent": "A joinery in Leeds making bespoke kitchens and fitted wardrobes." }

To build it for one of your businesses, add its businessId. Leave it out and the site is built for your main business, or for the business a key made for a client belongs to. A businessId the key cannot reach answers 404 and builds nothing.

An Idempotency-Key header is required, between 16 and 128 characters. A composed site costs real money to make, so the key is what stops a retry, a timeout or a dropped connection paying for a second one. Send a fresh value per site, a UUID is ideal, and reuse the same value if you retry.

Answers 201 with the site. Repeating a request with a key that already completed replays the original answer verbatim, with Idempotency-Replayed: true, and composes nothing. Keys are held for 24 hours, and are scoped to your account, to the business the site is built for, and to this endpoint. The same key used for two different businesses is two separate requests.

The site is composed but not live. Publish it when you are happy with it.

POST /api/v0/sites/{siteId}/publish

Make the newest version of a site public. No body. Answers 200 with the site, now carrying "published": true and the url anyone can open.

GET /api/v0/businesses

The businesses this key can reach, paged exactly like the site list. A key for your whole account lists every business in it; a key made for one client lists that one.

{
  "businesses": [
    { "businessId": "bz_...", "name": "Hats of Leeds", "createdAt": "2026-09-01T09:12:44.019Z" }
  ],
  "nextCursor": null
}

GET /api/v0/businesses/{businessId}/usage

What one business has spent on AI, today and on each of the six days before, so an agency can see what each client costs. Amounts are millionths of a US dollar at the AI providers' list prices, the same measure the daily allowance counts in. dailyCapMicroUsd is the business's own daily limit, or null when only the account's allowance applies. A business this key cannot reach answers 404.

{
  "businessId": "bz_...",
  "currency": "USD",
  "unit": "micro",
  "days": [ { "date": "2026-09-26", "microUsd": 184000 } ],
  "todayMicroUsd": 184000,
  "dailyCapMicroUsd": 2500000,
  "accountDailyCapMicroUsd": 5000000
}

To give a business its own daily limit, say so in Dark Orb, for example "cap Hats of Leeds at £2 a day". It can never be more than the account's allowance, and when it is reached only that business stops for the day. The others carry on.

Scopes

A key carries the permissions you asked for when it was made, and nothing else. Ask for the narrowest set that does the job.

ScopeWhat it allows
sites:readListing sites and reading one
sites:composeComposing a new site
sites:publishPublishing a composed site
businesses:readListing the businesses the key can reach, and what each has spent

A key can never do more than the person who issued it can do. If they lose access to the account, the key loses it at the same moment, without anyone having to remember to revoke it.

Errors

StatusCodeWhat it means
400malformed_requestThe body is not valid JSON, or the Idempotency-Key header is missing or the wrong length
401unauthenticatedThe key is missing, malformed, revoked, expired or not a key at all. Every one of those answers identically, on purpose
403scope_insufficientThe key is valid but does not carry the scope this endpoint needs
404not_foundNo site or business with that id that this key can reach
409idempotency_conflictThat key is still running, or it was used for a different request. Wait, or send a new key
409client_contact_requiredA client business cannot go live until you have told Dark Orb the email address of its owner. Say it in the portal, then publish again
422invalid_requestThe body is valid JSON but wrong: a missing intent, an empty one, one that is too long, or an unknown field
429rate_limitedOver the limit for this endpoint. Retry-After carries the seconds until it resets
429budget_exhaustedThe account, or the business the request is for, has used its AI allowance for the day. It resets tomorrow, and nothing was spent on this request
503unavailableSomething Dark Orb depends on could not answer. Nothing was half-done: a compose that could not be saved is reported as a failure and its idempotency key is freed, so retrying is safe

Rate limits

Limits reset on the hour rather than easing back gradually, so a 429 carries a Retry-After that tells you when the next hour begins. Waiting less than that will only be refused again.

Composing counts against your account, because composing spends Dark Orb's own AI credits rather than yours. Reading and publishing count against the individual key. Above both sits a daily AI allowance per account, shared with the portal: one account, one meter, whichever way you reach it.

What is here, and what is not

v0 covers building a site, publishing it, and reading what you have. Correcting a site, uploading images, custom domains and the other capabilities are reachable in the portal today but not yet over the API. More to come.

Changes

DateChange
September 2026v0 opens: whoami, sites, compose and publish
September 2026Businesses: GET /api/v0/businesses, businessId on compose and the site list, and keys made for one business
September 2026What each business spends: GET /api/v0/businesses/{businessId}/usage, and a daily limit per business under the account's allowance

Questions

Write to hello@darkorb.ai. Use of the API is covered by the Terms of Service and the Acceptable Use Policy.

← Back