velven
Docs
Menu

REST API

The REST API publishes, lists and manages spaces on a creator's behalf. Every endpoint answers JSON. An error answers with a code in error, except the hosting endpoints, whose error is a sentence and code the code. The calls the Velven page makes for a space, such as sign-in and saves, are not part of it.

View as Markdown

Authentication

Send your API token in the authorization header as Bearer <token>. The examples on these pages keep it in $VELVEN_TOKEN. An agent gets one through the device-code sign-in, which the creator approves in their browser; the token lasts 90 days. A 401 unauthorized means it is missing, expired or revoked: sign in again.

Terminal
curl -s https://velven.ai/api/agent/me \  -H "authorization: Bearer $VELVEN_TOKEN"

A token works only on its own creator's spaces. For another creator's space, the board endpoints answer 404.

Endpoints

Every endpoint, with the credential it takes and a link to its details:

Endpoint
POST /api/agent/login
Auth
None
What it does
Starts a login; answers a device_code and a link for the creator. Details
Endpoint
POST /api/agent/token
Auth
None
What it does
Exchanges the device_code for a token once the creator approves. Details
Endpoint
GET /api/agent/me
Auth
Token
What it does
The creator the token belongs to.
Endpoint
POST /api/spaces
Auth
Token
What it does
Lists a space. Details
Endpoint
GET /api/spaces?mine=1
Auth
Token
What it does
The creator's listed spaces. Details
Endpoint
POST /api/spaces/verify
Auth
Token
What it does
Claims a space Velven listed. Details
Endpoint
GET, PATCH /api/spaces/{slug}
Auth
Token
What it does
Reads or changes a space's fields and its page. Details
Endpoint
POST /api/spaces/{slug}/move
Auth
Token
What it does
Moves a space to a new URL. Details
Endpoint
POST /api/spaces/recapture
Auth
Token
What it does
Asks for a new clip. Details
Endpoint
POST /api/v1/hosting/versions
Auth
Token or none
What it does
Starts a version of a hosted space: its files and velven.json. Details
Endpoint
POST /api/v1/hosting/versions/{id}/finish
Auth
Token or claim
What it does
Makes the uploaded version a preview, or sends it to the safety check. Details
Endpoint
GET /api/v1/hosting/versions/{id}
Auth
Token or claim
What it does
A version's status. Details
Endpoint
GET /api/v1/hosting/claim
Auth
Claim
What it does
The unlisted page a claim token opens, and when it is deleted: { pageUrl, expiresAt }. Details
Endpoint
GET /api/v1/hosting/spaces/{slug}/versions
Auth
Token
What it does
A hosted space's versions. Details
Endpoint
POST /api/v1/hosting/spaces/{slug}/rollback
Auth
Token
What it does
Puts an earlier version live again. Details
Endpoint
POST /api/v1/hosting/versions/{id}/preview
Auth
Token
What it does
A new 24-hour preview link. Details
Endpoint
POST /api/v1/hosting/versions/{id}/review
Auth
Token
What it does
Asks a person at Velven to review a refused or unjudged version. Details
Endpoint
POST /api/v1/hosting/spaces/{slug}/reset
Auth
Token
What it does
Deletes the space's sandbox data. Details
Endpoint
GET, PUT /api/spaces/{slug}/boards
Auth
Token
What it does
Reads or writes the space's boards. Details
Endpoint
GET, POST /api/spaces/{slug}/secret
Auth
Token
What it does
The board secret's status, or a new secret. Details
Endpoint
GET, PUT /api/spaces/{slug}/achievements
Auth
Token
What it does
Reads or writes the space's stats and achievements. Details
Endpoint
DELETE /api/spaces/{slug}/scores/{id}
Auth
Token
What it does
Deletes one submission. Details
Endpoint
GET, POST, DELETE /api/spaces/{slug}/bans
Auth
Token
What it does
Lists, adds or lifts bans. Details
Endpoint
POST /api/v1/scores
Auth
Secret
What it does
A space's own server posts a score. Details
Endpoint
POST /api/v1/achievements, POST /api/v1/stats
Auth
Secret
What it does
A space's own server unlocks an achievement or sets a stat. Details
Endpoint
GET /.well-known/jwks.json
Auth
None
What it does
The public keys player tokens are signed with.

Move a space

Points the space at a new URL and keeps everything else. The new page passes the checks a listing does and, on a claimed space, carries the proof tag naming its owner. The owner may move a space, and an admin any space; each space gets 6 tries in 10 minutes. Move to another host says what to change on the new host.

Terminal
curl -s -X POST https://velven.ai/api/spaces/<slug>/move \  -H "authorization: Bearer $VELVEN_TOKEN" \  -H "content-type: application/json" \  -d '{"url":"https://orbit-dodger.vercel.app"}'
Status
200
Body
The space's fields, velven_url and previous_url
Meaning
Moved. The fields are those GET /api/spaces/{slug} answers, with the new url.
Status
401
Body
{ "error": "unauthorized" }
Meaning
The API token is missing, expired or revoked. Sign in again.
Status
403
Body
{ "error": "blocked", "message" }
Meaning
The new URL cannot be listed on Velven.
Status
404
Body
{ "error": "not_found", "message" }
Meaning
No space with that slug on this account.
Status
409
Body
{ "error": "unverified", "message", "instruction", "snippet" }
Meaning
The new page does not carry the proof tag naming the space's owner. Add snippet to its head, deploy, and call again.
Status
409
Body
{ "error": "duplicate", "url", "message" }
Meaning
Another space on Velven already uses that URL, before or after its redirects; url is its page.
Status
409
Body
{ "error": "same_url", "message" }
Meaning
The new URL is the space's own. Nothing to move.
Status
409
Body
{ "error": "removed", "message" }
Meaning
The space is off Velven. Put it back first.
Status
409
Body
{ "error": "held", "message" }
Meaning
The space is under review. It can move once the review is done.
Status
409
Body
{ "error": "conflict", "message" }
Meaning
The space changed while it was being moved. Read it again, then retry.
Status
422
Body
{ "error": "invalid", "issues" }
Meaning
The body was not JSON or not { url }, the URL is not a web address, or Velven does not list its host.
Status
422
Body
{ "error": "unreachable", "message", "instruction" }
Meaning
The new page did not answer with a page, or asks visitors to sign in (a locked preview answers 401): make it public, or use the production URL. instruction comes only with a locked page.
Status
422
Body
{ "error": "unframeable", "message", "instruction", "snippet" }
Meaning
The new page's headers refuse frames. Make the change that instruction describes (the steps for each host), deploy, and call again.
Status
429
Body
{ "error": "rate_limited", "message" }
Meaning
The space has had its 6 tries in 10 minutes. Wait a few minutes; do not retry in a loop.

Boards

GET answers { "boards": [...] }, every board as it stands. PUT takes the same shape as the page's boards block and writes only the boards named; the page's next sync overwrites a key the page also names.

Terminal
curl -s https://velven.ai/api/spaces/<slug>/boards \  -H "authorization: Bearer $VELVEN_TOKEN"curl -s -X PUT https://velven.ai/api/spaces/<slug>/boards \  -H "authorization: Bearer $VELVEN_TOKEN" \  -H "content-type: application/json" \  -d '{"boards":[{"key":"main","trust":"client","metric":"points","sort":"desc"}]}'
Status
200
Body
{ "boards": [...] }
Meaning
The boards after the write.
Status
401
Body
{ "error": "unauthorized" }
Meaning
The API token is missing, expired or revoked. Sign in again.
Status
404
Body
{ "error": "not_found", "message" }
Meaning
No space with that slug on this account.
Status
409
Body
{ "error": "too_many", "message" }
Meaning
The write would take the space past its board limit.
Status
422
Body
{ "error": "invalid", "issues" }
Meaning
A board did not validate; issues names the field.
Status
503
Body
{ "error": "failed", "message" }
Meaning
The boards could not be saved just now. Try again.

Hosting

The calls the Velven CLI makes, for a tool of your own that publishes. A version is made in 3 calls: create it with the file list, upload the files Velven lacks, then finish it. The files are named by their SHA-256, so a file Velven already has is never sent again. Hosting has the rules.

Terminal
curl -s -X POST https://velven.ai/api/v1/hosting/versions \  -H "authorization: Bearer $VELVEN_TOKEN" \  -H "content-type: application/json" \  -d '{"space":"star-hop","fields":{"title":"Star Hop","space_type":"game","devices":["desktop"]},       "files":[{"path":"index.html","size":1843,"sha256":"<hex>"}]}'

It answers 201 { version: { id, number }, space: { slug, url }, uploads: [{ sha256, url }], limits }. PUT each file's bytes to its upload url, with no authorization header; a link lasts 2 hours. Then finish:

Terminal
curl -s -X POST https://velven.ai/api/v1/hosting/versions/<id>/finish \  -H "authorization: Bearer $VELVEN_TOKEN" \  -H "content-type: application/json" \  -d '{"preview":false}'
  • The create body: space (a slug of yours, left out for a new space), fields (the listing, named as spaceFieldsSchema names them: title, space_type, devices, description, engine, ai_tools, models, how_made, source_url), files (1 to 2,000 of { path, size, sha256 }; a velven.json, a dotfile, a dot-folder or node_modules, in any folder and any case, is refused, naming the path, since none is ever published), and velven.json's entry, spa, sdk, start, toasts, boards, achievements and stats as they are.
  • Finish takes { preview }, true by default: a preview answers { status: "preview", previewUrl, watchUrl }; false answers { status: "checking", watchUrl } and the version goes live when the check passes. Finishing twice answers as the first did; a file not yet uploaded answers 409 missing_uploads; a version made before the space moved to a live URL answers 409 not_hosted for false, since it can only be previewed.
  • GET /api/v1/hosting/versions/{id} answers { id, number, status, reason?, shownOnTake?, pageUrl, previewUrl?, createdAt, liveAt?, reviewRequested, stopped? }. Poll it every few seconds for the verdict: live, refused or unjudged. stopped: true on a checking version means its check ended without a verdict (the space was hidden, held or moved, or its checks are over the hourly limit) and none is queued: stop polling and publish again.
  • GET .../spaces/{slug}/versions answers the last 100 as { versions: [{ id, number, status, createdAt, live, files, size, reason?, liveAt?, rollback, leftBehind }] } (leftBehind: made before the space moved to a live URL, so it can only be previewed). POST .../rollback { version } answers { version: { number }, status: "live", pageUrl }, or not_allowed for a version that was never live, and held or removed while the space is off Velven.
  • POST .../versions/{id}/review answers { requestedAt }, once per refused or unjudged version, and never for one older than the live version (superseded). POST .../spaces/{slug}/reset { categories?, player? } answers { deleted }, the categories being scores, saves, achievements, content and rooms.
  • Without a token, create makes an unlisted page and adds pageUrl, expiresAt and, the first time, claimToken. Send the token as claim in later create and finish bodies, and as the x-velven-claim header on reads. Finish with preview: false answers 403 sign_in_required.
Code
missing_fields, invalid_fields
Meaning
The listing lacks a required field or has a bad one; fields names them.
Code
invalid_request, invalid_files, invalid_declaration
Meaning
The body, a file path, a file's size (it must be the file's real bytes) or a board, stat or achievement did not validate.
Code
no_entry
Meaning
No file at entry, index.html by default.
Code
too_large, too_many_files
Meaning
Over the version limits; the largest files are named.
Code
too_many
Meaning
velven.json and what the achievements or boards API declared would take the space past 50 stats, 50 achievements or 10 boards. Declare fewer in velven.json.
Code
not_owner, not_found, removed, held
Meaning
Not your space, no such space or version, or the space is off Velven.
Code
rate_limited
Meaning
Too many publishes; Retry-After says when to try again.
Code
checks_busy
Meaning
429 on finish: too many of your versions are already waiting for the safety check. Nothing is lost: finish again after the seconds Retry-After gives.
Code
sign_in_required, claim_not_found, claim_expired, claim_no_profile, claim_banned
Meaning
Production needs an account; the claim token is wrong, used or past its 7 days; the account claiming has no handle yet, or cannot claim.
Code
claim_spent
Meaning
409 without an account, on a create, a finish, a version read or GET /api/v1/hosting/claim: the claim token's page was claimed (during the check, say). Its versions go on under that account; sign in as it and publish again.
Code
unauthorized, no_handle, forbidden
Meaning
The token does not resolve (run velven login again); the account has no handle yet; the account cannot publish.
Code
cross_site
Meaning
A publish without an account that another site's page sent. Publish from Velven itself or the CLI.
Code
not_allowed, not_hosted, not_previewable, not_refused, superseded, space_unavailable, no_player
Meaning
A rollback to a version never live, or on a space now at a live URL, or to a version from before the space moved to a live URL; a review, or a finish for production, of a version uploaded before the space moved to a live URL; a preview of a version not uploaded or refused; a review of a version neither refused nor unjudged; a review of a version older than the one live, which a review could not take live (publish a new version instead); a preview while the space is held, removed or hidden; a reset for a handle with no player.
Code
missing_uploads
Meaning
Finish before every file was uploaded; the paths are named.
Code
hosting_unavailable, server_error
Meaning
Velven's file store did not answer, or Velven had a problem on its side. Try again in a moment.

Achievements

GET answers the space's stats and achievements. PUT takes { "stats": [...], "achievements": [...] } in the shape of the page's block, writes only the keys named, stats first since a trigger names one, and answers the declarations after the write. An icon here is an https URL. 422 invalid with issues for a field that does not validate, 422 unknown_stat for a trigger on a stat the space does not declare, 409 too_many when the write would take the space past 50 stats or 50 achievements, 503 failed when they could not be saved just now (try again).

Terminal
curl -s -X PUT https://velven.ai/api/spaces/<slug>/achievements \  -H "authorization: Bearer $VELVEN_TOKEN" \  -H "content-type: application/json" \  -d '{"stats":[{"key":"kills","label":"Kills"}],"achievements":[{"key":"centurion","title":"Centurion","trigger":{"stat":"kills","atLeast":100}}]}'

Board secret

A server board takes posts only with the space's secret. POST issues one and answers 201 with { "secret", "message" }; the secret is shown this once, only its hash is kept, and issuing again replaces it. GET answers { "secret": { "created_at", "rotated_at" } }, or { "secret": null } before the first, never the secret itself.

Terminal
curl -s -X POST https://velven.ai/api/spaces/<slug>/secret \  -H "authorization: Bearer $VELVEN_TOKEN"curl -s https://velven.ai/api/spaces/<slug>/secret \  -H "authorization: Bearer $VELVEN_TOKEN"

Moderation

Delete one submission; the player is re-ranked from what is left. Or ban a player, by handle, from every board of the space, and lift the ban later.

No endpoint lists submissions yet. The latest are on the Leaderboards tab of the space's edit page, each with Remove.

Terminal
# delete one submissioncurl -s -X DELETE https://velven.ai/api/spaces/<slug>/scores/<id> \  -H "authorization: Bearer $VELVEN_TOKEN"# list, add and lift banscurl -s https://velven.ai/api/spaces/<slug>/bans \  -H "authorization: Bearer $VELVEN_TOKEN"curl -s -X POST https://velven.ai/api/spaces/<slug>/bans \  -H "authorization: Bearer $VELVEN_TOKEN" \  -H "content-type: application/json" -d '{"handle":"@mara"}'curl -s -X DELETE https://velven.ai/api/spaces/<slug>/bans \  -H "authorization: Bearer $VELVEN_TOKEN" \  -H "content-type: application/json" -d '{"handle":"@mara"}'
Status
200
Body
{ "deleted": true } or { "banned", "handle" }
Meaning
Done.
Status
400
Body
{ "error": "invalid" }
Meaning
The submission id is not a positive integer.
Status
404
Body
not_found or no_such_player
Meaning
No such submission on this space's boards, or no account has that handle.
Status
422
Body
{ "error": "invalid", "issues" }
Meaning
The body was not { handle }, or it named you.