A studio keeps its Arena54 listings up to date from its own build or release tools. It follows the same rules as the site’s forms: a new game or event waits for a person to approve it, a change to a live one shows at once and is recorded, and a rejected one goes back into review when you change it.
A key that may write
In the dashboard, create a key and tick May write. A key made without it can only read, and that cannot be changed later.
Keep it on a server, for example as ARENA54_WRITE_KEY. A call that comes from a page is refused.
A test key checks your input and answers what would happen, with dry_run: true. It writes nothing. A live key writes.
You can write only to your own: a game under a studio or person page you submitted or claimed, and events you submitted.
Calls
Method
Path
Purpose
POST
/v1/games
Submit a game under one of your own studio or person pages. It waits for review
PATCH
/v1/games/{slug}
Change your own game. Send only the fields that change
kind is jam, meetup, tournament, showcase or conference. Dates are days, as YYYY-MM-DD.
Refusals
Code
Status
When
write_not_allowed
403
The key can only read. Create one with “May write” ticked.
server_only
403
The call came from a page. A key that can write stays on a server.
not_yours
403
`creator` is not the handle of a page that is yours.
not_found
404
No game or event of yours has that slug.
invalid
400
A field is not acceptable. `fields` says which and why.
bad_json
400
The body is not a JSON object.
too_many_pending
409
You already have the most submissions allowed waiting for review.
limit
429
The plan’s writes for the day are used up.
closed
503
Submissions are paused, or the site is in read-only mode.
module_off
503
Writing through the API is switched off for now.
400
{ "success": false, "error": { "code": "invalid", "message": "Some fields are not acceptable. See `fields`." },
"fields": { "title": "Add a title.", "release_year": "Enter a year like 2024." }, "meta": { … } }
Limits
Writes a day come from your plan, with the usual calls a minute per key. There is no delete: to take a listing down, use the site or write to us. See pricing.
Build it with AI
Paste one of these into Claude Code, Codex or another coding agent, inside your project. It carries the real contract, so the agent does not have to guess.
Add Arena54’s write API
# Add Arena54’s write API to this project
Goal: Submit and update our own games and events on Arena54 from this project’s own tools.
You are a coding agent working inside my project. Do the work. Do not ask me anything you can find out by reading the project. Do not invent an endpoint, a field or an error code that is not written below.
## 1. Look at the project first
- Find the framework and language, how the project is run and tested, and where the project keeps the facts about our games (title, description, genres, platforms, release year, links), and any release or build script that could send them.
- Find how the project reads configuration and environment variables, and which files git ignores.
- Any backend or script will do. Use the HTTP client and the router the project already has, or the runtime’s own if it has none. If the project has nowhere to run code off the page, stop and tell me.
- If the project has no environment example file, add one, and load the environment the way the runtime provides.
- If the project already has an Arena54 client or key variable, reuse it.
- Tell me in two or three lines what you found and what you will change, then carry on.
## 2. The contract
Base URL: `https://developers.arena54.com/api/v1` (called $BASE below).
Every answer is JSON in one envelope:
```json
{ "success": true, "data": …, "meta": { "request_id": "req_…", "mode": "test" } }
{ "success": false, "error": { "code": "…", "message": "…" }, "meta": { "request_id": "req_…", "mode": "test" } }
```
Branch on `error.code`. Log `meta.request_id` with any failure.
Keys:
- Use an Arena54 key that was created with “May write” ticked. A key without it can only read. Read it from the environment variable `ARENA54_WRITE_KEY`, on a server or in a script. Never put it in a page or a commit: a call that carries an `Origin` header is refused.
- A TEST key (`a54_test_…`) checks the input and answers what would happen, with `dry_run: true`. It writes nothing. A LIVE key (`a54_live_…`) writes.
- Send `Authorization: Bearer <key>` and a JSON body.
Calls:
- POST /games: Submit a game under one of your own studio or person pages. It waits for review.
- PATCH /games/{slug}: Change your own game. Send only the fields that change.
- POST /events: Submit an event. It waits for review.
- PATCH /events/{slug}: Change your own event.
A game’s fields: `creator` (the handle of our own studio or person page on Arena54; needed to create), `title` (needed), `tagline`, `description`, `genres` (up to three keys from GET /genres), `platforms` (any of web, android, ios, windows, mac, linux, switch, playstation, xbox, vr), `stage` (`released`, `early_access` or `in_development`), `release_year`, `trailer_url` (https), `links` (an object whose keys are any of play, steam, playStore, appStore, itch, epic, xbox, playstation, nintendo, website, each an https address). Pictures are not taken by the API.
An event’s fields. Needed, even for an online event: `title`, `kind` (`jam`, `meetup`, `tournament`, `showcase` or `conference`), `starts_at` as `YYYY-MM-DD`, `country_code` (the two-letter code of an African country) and `city`. Optional: `ends_at`, `online` (true or false), `url`, `description`.
What happens: a new game or event is `pending` and is not public until a person at Arena54 approves it. A PATCH may send only the fields that change, or all of them; a live listing changes at once, and a rejected one goes back into review. The slug never changes. There is no delete.
A refused body answers 400 `invalid`. Beside `error` and `meta`, at the top level of the answer, is a `fields` object: `{ "success": false, "error": {…}, "fields": { "title": "Add a title." }, "meta": {…} }`. A name can be a path into a list or an object, such as `genres.0` or `links.play`.
With a TEST key every accepted call answers 200 with `{ "dry_run": true, "would": "create" | "update", "status": … }` and no slug. With a LIVE key a create answers 201 with `{ "slug": "…", "status": "pending", "url": "…", "note": "…" }` and an update answers 200 with `{ "slug", "status", "url" }`. A test key cannot prove an update of a game that does not exist yet: that PATCH answers 404.
A key that may write can also read, for example GET /genres, which answers `[{ key, name, games }]`. `Content-Type` is not required.
Refusals:
- 401 missing_key: There is no Authorization header, or what follows “Bearer” is not shaped like an Arena54 key.
- 401 invalid_key: The key is shaped like one but is unknown, revoked, or past its grace period after a rotation.
- 429 rate_limited: The key has made too many calls this minute. Retry-After says how long to wait.
- 503 api_unavailable: The API is switched off for a while. Try again later.
- 403 write_not_allowed: The key can only read. Create one with “May write” ticked.
- 403 server_only: The call came from a page. A key that can write stays on a server.
- 403 not_yours: `creator` is not the handle of a page that is yours.
- 404 not_found: No game or event of yours has that slug.
- 400 invalid: A field is not acceptable. `fields` says which and why.
- 400 bad_json: The body is not a JSON object.
- 409 too_many_pending: You already have the most submissions allowed waiting for review.
- 429 limit: The plan’s writes for the day are used up.
- 503 closed: Submissions are paused, or the site is in read-only mode.
- 503 module_off: Writing through the API is switched off for now.
## 3. Do this, in order
1. Add ARENA54_WRITE_KEY to the project’s environment example file with an empty value.
2. Write one small client `arena54Write(method, path, body)` that returns `data` and throws an error carrying `error.code`, `error.message` and `fields`.
3. Write a command or script `publish-to-arena54` that builds a game’s body from the project’s own facts and sends POST /games the first time, then PATCH /games/{slug} after. Store `data.slug` from the first LIVE answer in a small file in the project; a dry run returns no slug, so store nothing then.
4. Print what the API answered in plain words: created and waiting for review, updated, or which fields were refused and why.
5. Run it with the TEST key first and show me the dry run.
## 4. Prove it works
Set `BASE` and `ARENA54_WRITE_KEY` (my TEST key made with “May write”) in the shell. If I have not given you a key, ask me for one once, and for nothing else. `BASE=https://developers.arena54.com/api/v1`.
- Run: `curl -X POST "$BASE/games" -H "Authorization: Bearer $ARENA54_WRITE_KEY" -d '{"creator":"<our handle>","title":"Prove"}'`. Expect HTTP 200 with `"dry_run": true` and `"would": "create"`. A 403 `not_yours` means the handle is not a page of ours: ask me for the right one.
- Send the same with `"title": ""`. Expect HTTP 400 `invalid` and a `fields.title` message.
- Send it with a key that cannot write, if I gave you one. Expect HTTP 403 `write_not_allowed`.
- Run the project’s own command with the test key and show me its output.
Show me the output of each check.
## 5. Rules you must not break
- The write key never reaches a page or a commit.
- Send only facts the project really holds. Do not invent a description, a genre or a link.
- Use the test key until I say to go live.
- On 429 `limit`, the day’s writes are used up: stop and tell me.
- A new listing is not public until it is approved. Do not tell anyone it is live because the API answered 201.
## 6. Done when
- One command creates the listing the first time and updates it after.
- Refused fields are shown by name with their reason.
- The key is read from the environment and is in no commit.
- The curl checks give the answers above.
Then give me a short list of the files you changed and anything I must do by hand.
For a person, not needed for this task: https://developers.arena54.com/docs · This prompt: https://developers.arena54.com/prompts/write-api.server.md
Tried on 12 Oct 2026: the “My own server” version, pasted cold into a fresh Claude agent session on a small sample project with a test key. A dry run, a refused field and the project’s own publish command worked. Corrections from that run are in the text above; the corrected text and the Next.js version have not been run again. A live create and an update were not tried.