API keys, the automation API and the resource API

Updated Oct 15, 2026 Open the tool

Zoov.dev API keys (zv_...) are made at /app/api and work with two APIs. The automation API (https://zoov.dev/api/public/v1, Max plan only, since 2026-10-01) lets scripts and CI pipelines optimize YDR, YDD, YFT and YTD files, and (built 2026-10-02, not live yet) read every build of a clothing pack as files and hear about new builds through webhooks. The Resource API (https://zoov.dev/api/v1) lets a FiveM server script turn an image into a 3D prop and deploy it to a linked server through the zoovdev_sync resource. Everything else on zoov.dev is used through the website and Discord sign-in, not through API keys.

The automation API (Max)

Live since 2026-10-01 (site da2c2db8, docs page 1114088d, conversion server 7e2b72f).

Base URL https://zoov.dev/api/public/v1, header Authorization: Bearer zv_..., JSON in and out. Only accounts on the Max plan can use it; every other plan gets HTTP 403 plan_required ("The API is part of the Max plan"). The /app/api page lists it in the Reference panel under "Automation" with a Max chip; on other plans a "The automation API is part of Max" line with a Plans button shows there.

Routes:

  • GET /me: the key's account, plan, scopes, credit balance and Optimize allowance (concurrent is how many files may run at once, maxFileBytes the largest file).
  • POST /optimize with { "name": "prop.ydr", "bytes": 123456 }: checks the plan, the size and how many files are already running, and answers with a run id and an upload link.
  • Send the file to that link (multipart file, optional settings JSON, the same settings as the Optimize tool). The answer is the optimized file, with the result in the X-Optimize-Result header, or JSON { result } when the original was kept (nothing to change, or the new file was not smaller).
  • GET /optimize/:id: the run's state: waiting, running, done, kept, failed or expired.
  • DELETE /optimize/:id: cancels a run whose file will not be sent, freeing a slot.

The full reference is the public page https://zoov.dev/docs/api (Docs › API in the top bar of every page outside the workspace, the footer's Resources column, and the API docs button on /app/api): every call with its request and answer side by side, examples in curl, Node and Python (the choice is remembered per browser), a whole-folder script in each language, the settings, the result fields, every error code and the limits, and the Resource API further down. A machine-readable description is at https://zoov.dev/api/public/v1/openapi.json. Optimizing over the API shares the Max plan's Optimize limits with the workspace tool (100 MB per file, 5 files at once, no daily cap) and never costs credits.

The Packs API and webhooks (Max, 2026-10-02)

Built and tested, not live yet (it needs migration 0024_pack_sync.sql, INTEGRATIONS_KEY on Vercel and a deploy; see the Packs doc, "Sync pack and the Packs API"). Same base URL, same key, same Max gate and minute. Read only for now; adding items, files and builds through the API is the next round (owner, 2026-10-02).

  • GET /packs: the packs the key's account is in (owned or shared), each with its role and its latest build in the cloud. A pack kept only on one device is not listed.
  • GET /packs/:id: one pack, with how many builds are kept.
  • GET /packs/:id/builds[?limit=]: the kept numbered builds, newest first: number, resource name, edition, files, bytes, note, and players' counts.
  • GET /packs/:id/builds/:release: one build (5, v5 or latest) with its players' changelog in Discord Markdown.
  • GET /packs/:id/builds/:release/manifest: every file of the build, path inside the resource folder, SHA-256 and size. The ETag is the build's id (If-None-Match answers 304).
  • POST /packs/:id/files { sha256: [...] }: download addresses, an hour each, up to 500 hashes a call, straight from storage. A hash no kept build names is left out.

Builds are there only once the device that made them has kept them in the cloud (a moment after the build), and only while they are kept (the newest 100 on Max; named ones stay; a rotated number answers 404 "No build v7 is kept"). The public page /docs/api has a whole "Pull a build" script in curl, Node and Python that downloads only changed files into a folder and keeps the same .zoov-build.json record as Build into a folder.

Webhooks (scope webhooks; on /app/api under Webhooks with a Max chip, or GET/POST /webhooks, PATCH/DELETE /webhooks/:id, POST /webhooks/:id/test): an https address on the public internet (private addresses refused, as for server endpoints) hears build.published for one pack or every pack the account is in, shared ones too. Ten per account. Each delivery is signed the Standard Webhooks way (webhook-id, webhook-timestamp, webhook-signature: v1,<base64 HMAC-SHA256 of id.timestamp.body> keyed by the bytes of the whsec_ secret, shown once at creation and again with Signing secret on /app/api, where New secret replaces it). A 2xx within 10 s delivers it; otherwise it is tried again after 1, 5 and 30 minutes, 2, 6 and 12 hours (the /api/v1/cron/webhooks cron, every five minutes), then dropped; 50 failures in a row switch the hook off ("Off" on /app/api, its switch turns it back on). A hook whose owner is no longer on Max is skipped and says so. Send test posts a ping once and says how the endpoint answered. Delivery rows are kept 30 days.

Files never pass through zoov.dev itself. The site runs on Vercel, where one request can carry at most 4.5 MB, so the API is two calls: the JSON call to zoov.dev checks everything and opens the run, and the file goes straight to the conversion server at the link it answered. The link is good for one upload and expires after 15 minutes. Reusing it answers HTTP 409 ticket_used; an expired or altered link answers 401. Sending a different file type than the one the run was opened for answers 400 wrong_kind, and the link stays usable for the right file. The API key itself never reaches the conversion server. A link holds one of the plan's 5 running slots from the moment it is issued until its file finishes, it is cancelled (DELETE), or 15 minutes pass, so a script should ask for a link only when it is ready to send the file.

The workspace version

Not released yet (2026-09-19). /app/api is the port of this page. The linked servers come first, each with a dot for its state (Online, Link again, No answer), its address, "this machine only" for a private address, and an Unlink button. Then the keys: New key, a name, the key shown once in a row that copies it on a click, a bin to revoke with a confirmation, N of 10, and revoked keys folded away under "N revoked". The reference is a second panel on the right that folds away. Since 2026-10-01 it holds only what a person copies while setting up: "Automation" (Max chip, the base URL and the header, and on other plans "The automation API is part of Max" with a Plans button), "Resource API" (its base URL), and an "API docs" button that opens /docs/api in a new tab, where the full reference for both APIs now lives. Linking a server happens on the page and inside the deploy dialog, as four steps where every line copies on a click: 1 download zoovdev_sync.zip (with the date it was last updated), 2 the two server.cfg lines, 3 the start argument +set nodejs_allowFsWrite zoovdev_sync (it used to sit on a separate Requirements tab, and missing it is what causes "Permission denied"), 4 Get the command, which shows sync <token> with a countdown and "Waiting for the server" until it says "Server linked". The profile menu in the workspace has an API item that opens it. When the workspace goes live, /app/api redirects to /app/api (LEGACY_TO_APP).

What the API keys can and cannot do

The APIs are small and specific. Besides the automation API above (Optimize only, Max only), the Resource API exists for the zoovdev_sync FiveM resource, which uses your API key to create props from images and deploy them onto your server. The Reference panel on /app/api lists it under "Resource API". Base URL: https://zoov.dev/api/v1.

Exactly two Resource API endpoints accept a user API key (any plan may make a key; since 2026-10-13 from-image needs the account behind it on Pro or Max and charges credits):

  • POST /api/v1/fivem/props/stage-upload (get a signed upload slot for a PNG or JPG image, valid 10 minutes)
  • POST /api/v1/fivem/props/from-image (start a 3D prop generation from that image and send the result to a linked server)

There is no API for credits spending, Imagine, the Code agent, the Converter, prop or clothing conversion, the library, deploys or anything else yet. Those require a normal signed-in browser session. If a user asks to script another feature, the answer is that it is not available today; Optimize is the only file tool on the automation API.

The "Documentation" tab on /app/api states: "All API requests require a Bearer token in the Authorization header." and shows Authorization: Bearer zv_your_api_key_here. It also says "Keys with empty scopes have unrestricted access. The props:create scope is required for prop creation endpoints."

Who can create keys and how

Any signed-in account can create keys at /app/api under the "API Keys" tab; making a key has no plan check, because the Resource API works on every plan. The plan is checked when a key is used on the automation API: only Max gets in (since 2026-10-01; the Max plan card has always listed "API and CLI access for automation").

Steps: open /app/api, tab "API Keys", section "Your API Keys", click "Create key". In the "Create API Key" dialog enter a "Name" (placeholder "e.g. My FiveM Server", max 100 characters) and click "Create key". The dialog then shows "Key created" and "Copy this key now. It will not be shown again." with a "Copy" button. The key looks like zv_ followed by 40 hex characters. Only a hash is stored, so staff cannot recover a lost key; the user must create a new one.

Rules and strings:

  • Maximum 10 active keys: "Maximum of 10 active API keys allowed".
  • Missing name: "A key name is required (max 100 characters)".
  • Keys created from the page have empty scopes, which means every scope on both APIs (the automation API still needs Max), and no expiry date.
  • The key list shows the prefix (zv_xxxxxxxx...), "Created ", "Last used " when used, and an "Expired" badge if an expiry was set through the API and has passed.
  • Empty state: "No API keys yet" / "Create one to start using the zoov.dev API from your scripts."

Revoking a key

On /app/api, each active key has a trash button. Clicking it revokes the key immediately; the toast says "API key revoked". Revoked keys move to a "Revoked" list at the bottom, greyed out with "Revoked ", and stay there as a record. Revocation cannot be undone; create a new key instead. Revoked keys no longer count toward the limit of 10, but expired keys that were never revoked still do, so a user at the limit should revoke old ones.

Any request with a revoked, expired or unknown key gets HTTP 401 "Invalid or missing API key". A key that has scopes but not props:create gets HTTP 403 "API key lacks props:create scope".

Setting up zoovdev_sync

The "API Keys" tab starts with "Zoovdev Sync Setup", four steps:

  1. "Download the resource": "Download zoovdev_sync.zip" and extract it "anywhere in your server's /resources folder or subfolders."
  2. "Setup permissions in your server.cfg": paste add_ace resource.zoovdev_sync command allow into server.cfg or permissions.cfg.
  3. "Start the resource": add ensure zoovdev_sync to server.cfg or resources.cfg, or type refresh in the console and start it.
  4. "Link your account": "Generate a one-time sync token and run the sync command in your server console." Sync tokens are single-use and expire after 10 minutes. The token and its sync command are made in the workspace's Deploy dialog (Link a server, or Link another server); see the FiveM servers doc.

The "Requirements" tab adds that the server must start with +set nodejs_allowFsWrite zoovdev_sync (Windows: FXServer.exe +exec server.cfg +set nodejs_allowFsWrite zoovdev_sync; Linux: bash ./run.sh +exec server.cfg +set nodejs_allowFsWrite zoovdev_sync; txAdmin: "Settings > FXServer > Additional Arguments"). This "Grants Node.js file write access outside the resource folder". Without it deploys fail with "Permission denied".

Using the Resource API from a script

The resource exposes exports; scripts pass the API key as the first argument. Exports listed in the docs tab:

  • createPropFromImage: "Queue and auto-deploy". "Best default. Returns a task id immediately and keeps watching in the resource."
  • queuePropFromImageTask: "Queue only".
  • getPropTaskStatus: "Poll status". Returns pending, ready or failed.
  • watchPropTask: "Start background watch".
  • deployPropFromTask: "Manual deploy".
  • stopWatchingPropTask: "Stop background watch".
  • recoverPropTask: "Resume an existing task".

Parameters: apiKey (your zv_ key), imagePath ("Path to a local PNG or JPG image"), propName ("Alphanumeric + underscores, max 64 chars"), options (optional, scale, default 1.0), callback called with (success, result).

Server-side rules behind those exports: only png, jpg and jpeg ("Only PNG and JPG images are supported"); propName must match letters, digits and underscores ("propName is required (alphanumeric and underscores only)", "propName must be 64 characters or fewer"); the server must be linked ("No linked server found for this sync token"); the staged image must belong to the same account ("Storage path does not belong to this user"). Generation returns { taskId, status: "queued" }. Scale is capped at 100.

Credits: the task is created with a cost of 0 at request time. Whether the prop is charged later when it completes is not confirmed in these routes (verify with the Meshy webhook doc).

Rate limits

The automation API allows 300 calls a minute per key on its JSON routes (the same number as the conversion server's own limit on uploads, so it never binds before the 5-at-once limit does); past that it answers HTTP 429 rate_limited with retryAfter: 60. Uploads to the conversion server are not counted there (the server has its own per-address limit of 300 a minute). What an optimize may do is the Max plan's Optimize row, shared with the workspace tool: 409 concurrency_limit when 5 files are already running, 413 file_too_large over 100 MB.

The two Resource API endpoints have no rate limit of their own in the code. The only caps are the 10 minute signed upload window, the image type check, and the 10 active keys per account. If abuse appears, staff must revoke the key manually.

A suspended (banned) account's keys stop working on the automation API at once (403 account_suspended), since a key outlives every session. The Resource API routes do not check this yet.

Common questions

Is there a public API for zoov.dev? Only the Resource API for FiveM servers: two endpoints that create a 3D prop from an image and deploy it through the zoovdev_sync resource. Nothing else accepts an API key.

Do I need a paid plan to create an API key? No. Any signed-in account can create up to 10 keys at /app/api and use them with the Resource API. The automation API (optimizing files from scripts) needs the Max plan.

Can I optimize files through the API? Yes, on Max. Call POST https://zoov.dev/api/public/v1/optimize with the file's name and size, then send the file to the upload.url it answers. The answer is the optimized file. The docs at https://zoov.dev/docs/api have a ready script for a whole folder in curl, Node and Python.

Where are the API docs? At https://zoov.dev/docs/api, public, no sign-in needed. /app/api is where keys are made and servers linked; its API docs button opens the same page.

Why does my upload link say it was used or expired? Each link from POST /optimize takes one file and lasts 15 minutes. Ask for a new link for every file.

Why did I get JSON instead of a file? The original was kept: nothing could be made smaller, or the result was not smaller. result.kept says why; keep your original file.

Why does the API say plan_required? The automation API is part of the Max plan. The key works, but the account is on another plan.

I lost my key, can you show it again? No. Only a hash is stored. Revoke the old key and create a new one at /app/api.

How do I revoke a key? Go to /app/api, "API Keys", click the trash icon on the key. It moves to "Revoked" immediately.

Why do I get "Invalid or missing API key"? The Authorization: Bearer zv_... header is missing, the key was revoked or expired, or it was mistyped. Keys start with zv_.

Why do I get "Permission denied" when deploying? The server was not started with +set nodejs_allowFsWrite zoovdev_sync. See the "Requirements" tab on /app/api.

Why does it say "No linked server found for this sync token"? Run sync <token> in the server console with a fresh token. Tokens are single-use and expire after 10 minutes.

Languages

Since 2026-09-20 this part of the site is available in English, French, German, Spanish, Portuguese, Polish and Dutch. It follows the browser's language on a first visit, and the language menu (the icon beside the profile in /app, or in the top navigation on public pages) changes it. Nothing about how it works, what it costs or what a plan includes differs by language; only the words do. File names, format names (GLB, YDR, YTD), product and model names, code, and some error lines that come from the server stay in English. Details and known gaps: Languages and translation.

Topics apiapi keyszv_packs apipack buildsmanifestwebhooksstandard webhooksbuild.publishedzoovdev_syncpropsresource apibearerscopesautomationpublic apioptimize apicurlcimaxopenapi

Something missing? Ask on Discord