The HTTP API
Every endpoint Creora serves, grouped by what it is for. The handlers are under src/app/api/; the work they call is in src/lib/. For how it all fits together see architecture.
Conventions
- The browser never calls these directly.
src/lib/api-client.tsowns every URL, every response shape and the map from an error code to the sentence a toast shows. It is the only client code that touchesfetch. - Bodies are JSON, capped and validated.
readJsoninsrc/lib/http.tsanswers 415 unless the media type isapplication/json, 413 once the body passes the route's cap (checked against the declaredContent-Lengthand again against the bytes streamed), and 400validationfor bad JSON or a schema failure, with the flattened zod errors indetails. Caps run 4 KB for a small body, 64 KB for most, 3 MB for a project patch. An image is sent as bytes, not a form, and its type is sniffed rather than believed. - Every non GET checks the origin.
Origin, or failing that the origin ofReferer, must equalNEXT_PUBLIC_APP_URL; anything else is 403csrf. The public data API is the one exception, because it reads no cookie. - The session is a cookie: an HTTP only JWT,
__Host-auth_tokenin production,SameSite=Lax, seven days, carrying a session version so storing a key or deleting an account ends every other session. Without it a signed in route is 401unauthorized; a database that cannot be reached is 503unavailable. - Errors have one shape:
{ "error": { "code", "message", "details?", "requestId?" } }withCache-Control: no-store. The codes are the closed list insrc/lib/contracts/errors.ts, each with the sentence a person reads; the DTOs and limits both sides share sit beside it insrc/lib/contracts/. - Rate limits answer 429
rate_limitedwithdetails.retryAfterMsand aRetry-Afterheader. They key on the user id where there is a session and on the address otherwise, and they live in this process's memory, so they multiply behind more than one instance. WithoutTRUSTED_PROXY=1every address shares one bucket. - Ownership is part of the service query on every project scoped route, so a foreign id answers 404 exactly as a missing one does. An
x-request-idheader is echoed in the error body when it looks like an id.
Auth
POST /api/auth/wallet/nonce. The single use nonce a wallet signs into its SIWE message. Public, 30 per minute; the row expires after five minutes.POST /api/auth/wallet/verify. Checks the message and signature, creates the user on first sign in with a generateduser_<8 hex>name, sets the cookie. Public, 10 per minute. A contract wallet is checked throughEIP-1271against the RPC for its chain.GET /api/auth/wallet/session. The user behind the cookie, its address and chain, or nulls. Public, never cached.POST /api/auth/wallet/logout. Clears the cookie, 204. No session needed: a client whose token is already invalid still has to drop it.
Users and profiles
GET /api/users/me. The signed in user.PATCH /api/users/me. Changes the username, the notification address, or both. 10 per hour. A taken name is 409username_taken, one nobody may hold 409username_reserved; giving up a chosen name holds it for 30 days.DELETE /api/users/me. Deletes the account; the body must repeat the username exactly. Runs the deletion cascade in a fixed order, 204, cookie cleared.PUT /api/users/me/api-key. Stores the caller's Anthropic key and workspace id. The pair is checked against the Anthropic API first, the key is stored encrypted and never returned, and the session version is bumped, so the answer carries a fresh cookie and ends every other session. 10 per hour; 503api_keys_disabledwithoutENCRYPTION_KEY.DELETE /api/users/me/api-key. Removes it, even after the encryption key has been withdrawn.GET,PATCH /api/users/me/profile. The text half of the public profile. 60 edits per hour; an absent field means leave it as it is.PUT /api/users/me/profile/image/[kind].avatarorbanner, anything else 404. The body is the image, capped at 1 MB and 4 MB, 20 uploads per hour.DELETEon the same path clears it and is idempotent.
Projects
POST /api/projects. Creates a project from a template. 201, 30 per hour. There is no list endpoint: the dashboard is a server component.GET /api/projects/[id]. The project with its files. Owner.PATCH /api/projects/[id]. Rename, emoji, or file writes under aversionprecondition: a stale version is 409stale_project, a file write while a run holds the project 409run_in_progress. Cap 3 MB.DELETE /api/projects/[id]. The project and its messages, 204.GET /api/projects/[id]/messages. One page of the transcript: the newestlimitmessages belowbefore, ascending, at most 200.
The agent
POST /api/projects/[id]/agent. Starts a run. Owner, 30 per hour, one run per project and one per user, andAGENT_MAX_CONCURRENT_RUNS(20) across the process, which is 429server_busywhen full. Body: the message and the project version. Everything that can fail before the model answers is an HTTP status (409stale_project, 412no_api_key, 429budget_exceeded, 402out_of_credits, 502 upstream); after that the response becomestext/event-streamand the run's events travel on it.maxDurationis 800 seconds.POST /api/projects/[id]/agent/abort. Stops the caller's run for that project, 204, or 404 when there was nothing of theirs to stop. It only reaches a run this instance holds.
Checkpoints and assets
GET /api/projects/[id]/checkpoints. The project's undo points, newest first. Owner.POST /api/projects/[id]/checkpoints. Restores one by sequence number, which itself records where you were. 20 per minute.GET /api/projects/[id]/assets. The project's image library, how many slots are left, whether pinning is configured.POST /api/projects/[id]/assets. One image, up to 4 MB, 60 per hour;?pin=1also offers it to the content addressed store. 201.DELETE /api/projects/[id]/assets/[assetId]. Row and bytes.GET /api/assets/[id]. Serves stored bytes by id. Public and session free, so a signed out visitor sees an avatar on a public profile. ETag,nosniff, and a one year immutable cache, because a new image is a new id.
Publishing and sites
POST /api/projects/[id]/publish. Snapshots what the server already holds and points the site at it. Owner, 20 per hour, under aversionprecondition; a project that fails the publish gate is 409not_publishable. No files are taken from the client. 201.PATCH /api/projects/[id]/publish. Rolls the site back to a snapshot it already has.DELETE /api/projects/[id]/publish. Takes the page down, answering the new site state rather than 204.POST /api/projects/[id]/cover. Stores the picture of a site, drawn by the app itself in the browser. Owner, 40 per hour.POST /api/sites/[id]/view. Counts one visit to a published page. No session, 600 per minute per site, 204 whatever happened.POST /api/remix. Copies somebody's published app into a project of your own, addressed by username and slug rather than by a project id. Signed in, 30 per hour, 201. The explore feed itself needs no endpoint; those pages are server components.
Data
The owner surface behaves like the rest of the API. See databases.
GET /api/projects/[id]/data. Tables, row counts, migrations, the key, the quota and the app's accounts in one call. Provisions the schema on first look. 120 per minute.GET /api/projects/[id]/data/client. The generated client module, or null. Never provisions.POST /api/projects/[id]/data/key. Mints a new publishable key before revoking the old one;PATCHsets the origins it may be used from. 20 per hour.POST /api/projects/[id]/data/rows. Reads rows, with an exact total. A POST because the query is a JSON document and a GET would put row values in every access log.PATCHinserts, updates one row, or deletes several. 120 per minute.POST /api/projects/[id]/data/migrations. Applies one structured schema operation, or reverts a numbered migration by appending its inverse. 30 per minute, 201.GET /api/projects/[id]/data/users. The app's own accounts.PATCHandDELETEon/api/projects/[id]/data/users/[endUserId]disable one, set a password, or remove it; both changes bump the account's token version. 30 per minute.
The public surface is what a generated app calls, cross origin, with the publishable x-creora-key header and an optional Authorization: Bearer end user token. No route here reads or writes a cookie, which is why there is no origin check and why that is safe. Each answers OPTIONS for CORS, never sends credentials, and gives a disallowed origin no CORS headers at all. Its limits are counted in MongoDB, so they hold across instances: 600 reads and 60 writes per minute per key.
POST /api/data/query. Reads rows, answering{ rows, nextCursor }and deliberately no total.POST /api/data/insert. Inserts up to the row cap, every value bound, an owner column filled from the token rather than the body. 201.POST /api/data/update. Changes one row by primary key, atomically. Never a filter.POST /api/data/delete. Deletes one row by primary key.POST /api/data/auth/register. An account inside the generated app. 20 per hour per key.POST /api/data/auth/signin. Five attempts per minute per email per key, checked before the password hash runs.GET /api/data/auth/me. Who the bearer token belongs to, re read from the row every time.POST /api/data/auth/signout-all. Bumps the token version, ending every session that account holds.
GitHub
Signed in, and 503 github_disabled where the server has no ENCRYPTION_KEY. See github.
GET /api/github/account. The connection and whether the feature is on.PUTproves a token against GitHub and stores it encrypted, answering the login, the scopes and the last four characters;DELETEdisconnects, and works with the encryption key withdrawn. 10 changes per hour.GET /api/github/repos. The repositories the token reaches, withhasMorewhen the page cap stopped the walk. 60 per hour.GET /api/github/branches?owner=&repo=. The branches and the default one; also how the push dialog learns a branch's head after a conflict.POST /api/github/import/plan. What an import would bring across. Three GitHub requests, no downloads, 30 per hour.POST /api/github/import. Starts it: 202 with a job, 10 per hour. The plan the browser saw is not sent and would not be trusted; only the commit sha crosses.POST /api/projects/[id]/github/push. Starts a push: 202 with a job, 30 per hour, refused with 409 while a run holds the project or the version has moved.GET /api/github/jobs/[id]. One GitHub job with its typed result.
Jobs
GET /api/jobs/[id]. Status, progress and step for one of the caller's background jobs. There is no POST: a job is always enqueued by the route that owns the work, because only that route knows the payload, the idempotency key and the quota.
Billing
GET /api/billing/account. The caller's credit balance, plan and period; reading it lands the free monthly grant if this month's has not arrived. No rate limit, because it is the cheap poll that notices a balance change. See credits.
Forum
Reading needs no endpoint: the index, the categories and the threads are server components. See community.
POST /api/forum/threads. Starts a discussion. Signed in, 12 requests per hour, and five successful threads a day, which is the cap that keeps binding on a second instance. 201.PATCH /api/forum/threads/[id]. The author changes the title and body; a moderator, meaning a wallet inADMIN_ADDRESSES, pins, locks, removes and gives a reason. A field the caller does not hold is refused rather than dropped. 30 per hour.DELETEis author or moderator, 204.POST /api/forum/threads/[id]/replies. Replies. 5 per minute; a locked thread is 409forum_locked.PATCH /api/forum/replies/[id]. Three authorities on one endpoint: the reply's author editing, the thread's author marking it helpful or as the answer, a moderator removing it. 60 per hour.POST /api/forum/reports. Reports a thread or reply. Signed in and attributable on purpose, 20 per day, 204 whether it was the first report or the fifth.
Launches
One line each; the whole feature is in the launchpad's app page. Everything that only reads is public, because everything it answers is on the chain.
GET /api/launches. The list, newest first.?mine=1narrows to the caller's,?state=0skips the chain reads.POST /api/launches. Records a launch that already exists on the chain, checked against the wallet on the session. Signed in, 20 per hour, 201.GET /api/launches/launchpad. The launchpad's figures and addresses;?fresh=1skips the short cache.GET /api/launches/referrals?address=. One address's referral code and earnings. 60 per minute.PUT /api/launches/image. A token's picture, stored unclaimed before the launch exists. Signed in, 1 MB, 20 per hour.GET /api/launches/[id]. One launch, with its state read fresh from the chain.GET /api/launches/[id]/trades. Every trade, priced, read forward from the last block seen.GET /api/launches/[id]/holders. The largest holders and how many there are.GET /api/launches/[id]/holdings?address=. What one address holds and is owed. 120 per minute.GET /api/launches/[id]/stream. Server sent events, one per new block, for five minutes at a time.GET /api/projects/[id]/launch. What a project's launch form starts from, and whether the site already has a token. Owner;{ enabled: false }where no launchpad address is configured.
Search
GET /api/search?q=. One search across people, published apps and, with a session, your own projects. No session required: the first two lists are public and the third is empty without one. The query is truncated to 64 characters rather than refused, because a search box sends on every keystroke. 60 per minute.
Content Security Policy reports
POST /api/csp-report. Receives the browser's violation reports for the policysrc/proxy.tssets and logs them. Acceptsapplication/csp-reportas well as JSON, 60 per minute, keeps at most 512 characters of a field, drops the policy text, answers 204.