← Zyplo
Zyplo · Developers

Integrate Zyplo

Who this is not for

If you export multi-file WebGL from Godot, Unity or GameMaker, Zyplo will not host it. The format is one self-contained HTML file — a .wasm plus a .pck cannot be uploaded, today or on a roadmap. Better you know now than after a week of trying.

Publishing a game

Zyplo speaks MCP. Connect it once and your assistant can create a game, validate it, upload it as a draft, edit it and publish it — without you leaving the chat.

Auth is OAuth with dynamic client registration and PKCE. Legacy zyplo_sk_ keys still work if you already have one.

Over HTTP

One POST publishes a game. Nothing else is required — no SDK, no build step, no script tag inside your game.

curl -X POST https://zyplo.io/api/upload-game \
  -H "Authorization: Bearer $ZYPLO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Tap the Comet","description":"One tap. Do not miss.","html_content":"<!doctype html>...","tags":["arcade"],"published":false}'

ZYPLO_KEY is a personal key (zyplo_sk_…) or an OAuth access token (zyplo_at_…). Upload as a draft first (published: false) and look at it before it goes live. openapi.json

Check before you upload

Add ?dry_run=true and the same call validates without creating anything. It reports every problem at once instead of stopping at the first, and writes nothing — no file, no game, no rate-limit stamp — so you can iterate as fast as you like.

curl -X POST "https://zyplo.io/api/upload-game?dry_run=true" \
  -H "Authorization: Bearer $ZYPLO_KEY" \
  -H "Content-Type: application/json" \
  -d @game.json

Answers 200 whether or not the game is valid: the status says the check ran, ok says the verdict. A bad token still answers 401. It does not check the rate limit or the cover image, so ok: true means the game is valid, not that the upload is guaranteed to succeed.

The format

These are the real rules, read straight out of the same module the uploader and the MCP prompt use. If this page and the API ever disagree, that's a bug — tell us.

Hard rules — the upload rejects violations

Design rules — not enforced, but ignore them and the feed buries you

A game that passes

A minimal game that satisfies every rule above. Play it, then read it — it is short on purpose. Play the example

It is a single file, so your browser's View Source shows you the whole game.

Why an upload gets rejected

Every failure answers with a stable code, the field at fault, and what to do about it. Branch on code, never on the message text — messages can be reworded, codes will not.

{
  "error":    "Zyplo acepta HTML autocontenido: ...",
  "code":     "external_script",
  "field":    "html_content",
  "detail":   "https://cdn.example/phaser.js",
  "fix":      "Inline the library into a <script> block.",
  "docs_url": "https://zyplo.io/en/integrate#error-external_script"
}

The error string is unchanged from before codes existed, so nothing that reads it breaks. code is internal_error when the failure is one we have not described yet — that is our bug, and we want to hear about it.

not_html 400
The payload does not start like an HTML document. Field: html_content Fix: Send the whole document, starting with <!doctype html>. Not a fragment, not JSON, not a base64 blob.
external_script 400
A <script src="..."> points somewhere else. Field: html_content Fix: Inline the library into a <script> block. If it is too big to inline, the game does not fit Zyplo.
relative_asset 400
An asset uses a relative path, which has nothing to resolve against once the file is served on its own. Field: html_content Fix: Embed the asset as a data: URI, or point at an absolute https:// URL.
forbidden_tag 400
The document contains <iframe>, <frame>, <frameset>, <object>, <embed>, <applet>, <form> or <base>. Field: html_content Fix: Remove it. Games run inside a sandboxed iframe already, so nesting one or posting a form cannot work.
meta_refresh 400
A <meta http-equiv="refresh"> would navigate the player away from the feed. Field: html_content Fix: Remove it and restart the game in JavaScript instead.
dangerous_url 400
A URL uses a scheme that is not allowed (javascript:, vbscript:, file:, ftp:, or plain http:). Field: html_content Fix: Use https://, data: or blob:. For behaviour on click, attach a listener instead of a javascript: URL.
network_api 400
The code calls fetch, XMLHttpRequest, WebSocket, EventSource, importScripts, eval, new Function, navigator.sendBeacon, document.cookie or window.open. Field: html_content Fix: Remove the call. Games are offline by design: keep state in memory or in localStorage. There is no in-game backend and no in-game SDK.
missing_token 401
No Authorization header was sent. Fix: Send Authorization: Bearer <token> with a zyplo_sk_ key or a zyplo_at_ OAuth token.
invalid_token_format 401
The Bearer token is neither a zyplo_sk_ key nor a zyplo_at_ OAuth token. Fix: Check you are not sending a Supabase JWT or a raw session token. Zyplo tokens start with zyplo_.
invalid_api_key 401
The zyplo_sk_ key does not exist or was revoked. Fix: Issue a new key from Creator Profile → API & Connectors, or connect over OAuth instead.
invalid_oauth_token 401
The OAuth access token expired or was revoked. Fix: Refresh the token. If your client cannot, remove and re-add the Zyplo connector.
account_suspended 401
The account behind this token is suspended, so it cannot upload or edit games. Fix: Nothing to retry. Get in touch if you think the suspension is a mistake.
game_not_found 401
No game with that game_id belongs to this account. It may have been deleted, or the id may belong to someone else. Fix: Call list_my_games (MCP) to get the ids this account actually owns. Do not retry with the same id.
invalid_body 400
The request arrived without a JSON object as its body. Field: body Fix: Send the fields as a JSON object with Content-Type: application/json. An empty request, a bare string and an array all land here; detail says which one arrived.
missing_field 400
A required field was absent or empty. Fix: See field for which one. Send it with a non-empty value.
conflicting_fields 400
Two fields were sent that cannot both apply. Fix: Send cover_url or cover_data_url, never both.
unsupported_tag 400
A tag is not one of the twenty Zyplo accepts. Field: tags Fix: Pick from the enum in the OpenAPI spec (arcade, puzzle, skill, casual, humor, meme, snake, breakout, tetris, dodge, memory, logic, reflex, timing, precision, runner, retro, action, platformer, strategy). Tags past the tenth are dropped silently.
no_updates 400
The update call carried no changed field, so there is nothing to write. Fix: Include at least one of title, description, tags, cover_url or cover_data_url.
unknown_action 400
The action field is not one this endpoint handles. Field: action Fix: Use update, set_visibility or delete.
invalid_dry_run 400
The dry_run query parameter carried a value that is neither a yes nor a no. Field: dry_run Fix: Send ?dry_run=true (or 1) to validate, ?dry_run=false (or 0) to upload, or leave it out. An unrecognised value is refused rather than guessed: guessing wrong would upload the game for real.
method_not_allowed 405
This endpoint only answers POST. Fix: Send the request as POST with a JSON body.
html_too_large 400
The HTML document is over the size limit. Field: html_content Fix: Shrink the document. Embedded base64 assets are usually what pushes it over — generate art with canvas or SVG instead.
cover_too_large 400
The cover image is over the size limit. Field: cover_url Fix: Send a smaller image.
plan_limit_reached 400
The account already holds the maximum number of games its plan allows. Fix: Delete a game before uploading another. Retrying will not help.
rate_limited 400
Uploads from this account are coming in too fast. Fix: Wait the number of seconds named in the message, then retry the same request once.
game_under_review 400
The game is held for moderation review, so it cannot be edited or published. Fix: Wait for the review to finish. Retrying immediately will fail the same way.
game_file_missing 400
The stored HTML file for this game is gone, so its source cannot be read back. Fix: Re-upload the game. This one is ours — tell us it happened.
cover_invalid_type 400
The cover is not a JPG, PNG or WebP. Field: cover_url Fix: Convert it to one of those three.
cover_invalid_data 400
The bytes behind the cover do not decode as the image type they claim. Field: cover_url Fix: Re-export the image. A renamed extension or a truncated download both land here.
cover_fetch_failed 400
The cover_url could not be downloaded. Field: cover_url Fix: Send a public https URL that returns the image directly. Zyplo refuses http, private and loopback addresses, and follows at most 3 redirects, each of which must also be public https.
invalid_report 400
The report payload is malformed. Fix: Send a valid game_id (UUID) and a known reason.
duplicate_report 409
This reporter already reported this game. Fix: Nothing to do — the first report stands.
report_rate_limited 429
Too many reports from this source. Fix: Wait before reporting again.
storage_upload_failed 500
Zyplo could not write the file to storage. Fix: Retry once. If it happens again it is a fault on our side — tell us.
server_misconfigured 500
A server-side credential is missing. Fix: Nothing you can do. Tell us.
internal_error 500
Something failed on our side that we did not anticipate. Fix: Retry once, then tell us.

Stuck?

Email me with what you sent and what came back. A person answers — usually the one who wrote this page.