{"name":"Antideploy API","version":"v1","base":"https://antideploy.com","start":{"summary":"Log in once with the device flow, create an application, push the project. Steps 1 and 2 are skipped on every later deploy of the same project.","steps":[{"step":1,"what":"Get an account token, if ~/.antideploy/config.json does not already have one.","how":"POST https://antideploy.com/api/v1/device/code, show the user the verificationUriComplete it returns, then poll POST https://antideploy.com/api/v1/device/token with the deviceCode until it stops answering authorization_pending.","humanAction":"The user opens the link and clicks Approve. This is the only thing a human does in the entire flow.","critical":"Write the token to a file with a redirect. Never echo it, never paste it into a chat, never put it in the project directory — it is an account credential, not a project key."},{"step":2,"what":"Create the application, if the project has no .antideploy.json yet.","how":"POST https://antideploy.com/api/v1/applications with {\"name\":\"<folder-name>\"}. Save the returned applicationId to .antideploy.json in the project. That file holds no secret and can be committed.","note":"409 name_taken means you already have it — the response carries the existing applicationId, so use that."},{"step":3,"what":"Push the project and watch it build.","how":"tar the directory and POST it to https://antideploy.com/api/v1/deploy?applicationId=<id>, then poll the watch URL until status is succeeded or failed."}],"example":["# 1. login (once ever)","curl -sS -X POST https://antideploy.com/api/v1/device/code -H 'content-type: application/json' -d '{\"clientName\":\"Claude Code\"}' -o /tmp/ad-login.json","#    show the user .verificationUriComplete, then poll:","curl -sS -X POST https://antideploy.com/api/v1/device/token -H 'content-type: application/json' -d \"{\\\"deviceCode\\\":\\\"$DEVICE_CODE\\\"}\"","#    on success, write .token straight to ~/.antideploy/config.json - do not print it","","# 2. create the project (once per project)","curl -sS -X POST https://antideploy.com/api/v1/applications -H \"authorization: Bearer $TOKEN\" -H 'content-type: application/json' -d '{\"name\":\"my-app\"}'","","# 3. deploy (every time)","tar czf - --exclude=.git --exclude=node_modules . | curl -sS -X POST \"https://antideploy.com/api/v1/deploy?applicationId=$APP_ID\" -H \"authorization: Bearer $TOKEN\" -F \"archive=@-\""]},"authentication":{"scheme":"Authorization: Bearer <credential>","kinds":{"account":{"prefix":"adu_","obtainedBy":"The device flow. See `start` and `login`.","can":["create applications","deploy, set secrets and poll status on any application you own"],"cannot":["read secret values back","delete an application","mint another account token"],"storage":"~/.antideploy/config.json, mode 0600. Never in the project directory — this one is not safe to commit.","revoke":"Immediately, from Account tokens in the dashboard.","note":"Requests must name the application: `?applicationId=<id>`."},"project":{"prefix":"ad_","obtainedBy":"The dashboard, or an account token minting one.","scope":"Exactly one application, decided by the key itself — so no applicationId is sent, and one that leaks costs you that project rather than the account.","note":"Built to survive being committed. Use this one for CI."}}},"login":{"begin":{"method":"POST","path":"/api/v1/device/code","body":"{\"clientName\":\"Claude Code\"}","returns":"deviceCode (poll with this), userCode (show this), verificationUriComplete (open this), expiresIn, interval"},"poll":{"method":"POST","path":"/api/v1/device/token","body":"{\"deviceCode\":\"...\"}","interval":5,"returns":"200 {token} once approved.","errors":"400 with the machine code in `error` (RFC 8628) and a sentence in `error_description`. authorization_pending — keep polling. access_denied — the user refused. expired_token — start again. `code` carries the same value, for clients written against the older shape.","singleUse":"A poll that succeeds is the redemption. Capture the token from that response or the code is spent for nothing."},"handling":"Redirect the granted token into a file. It must not reach terminal output or an agent transcript."},"applications":{"create":{"method":"POST","path":"/api/v1/applications","body":"{\"name\":\"my-app\",\"subdomain\":\"my-app\"}","auth":"Account token only.","returns":"201 {applicationId, name, subdomain, dashboard}","conflicts":"409 name_taken, carrying the existing applicationId.","subdomain":{"optional":true,"description":"The address the app is published at: <subdomain>.antideploy.com. Omit it and one is derived from the name, numbering past a collision.","ask":"ASK YOUR USER what they want this to be, and show them the full URL before you send it. It is the address they will share, and picking it for them is picking their public name for them.","rules":"2 to 40 characters, lowercase letters, digits and hyphens, no leading, trailing or doubled hyphen. Some words are reserved.","check":"GET /api/v1/hostnames/check?label=<label> before creating, to avoid a wasted 409.","conflicts":"409 subdomain_taken if somebody already has it, 400 invalid_subdomain if it breaks a rule. Neither is retryable with the same value: ask your user for another.","change":"PATCH /api/v1/applications/<applicationId> with {\"subdomain\":\"new-name\"}. The old address stops working immediately."}},"list":{"method":"GET","path":"/api/v1/applications","description":"Everything you own. A project key sees only its own, which makes this safe to call with either credential."}},"deploy":{"method":"POST","path":"/api/v1/deploy","query":{"applicationId":"Required for account tokens, optional for project keys. In the query string rather than the form so a 404 or a 409 costs a header instead of a finished upload."},"encoding":"multipart/form-data","preferred":{"field":"archive","description":"A tar of the project directory, optionally gzipped. Carries the directory tree itself, so nothing can drift out of sync.","example":"tar czf - --exclude=.git --exclude=node_modules . | curl -X POST \"https://antideploy.com/api/v1/deploy?applicationId=$APP_ID\" -H \"Authorization: Bearer $KEY\" -F \"archive=@-\""},"alternative":{"fields":["files","paths"],"description":"One `files` part and one `paths` part per file, in the same order. Send every file in the project, not just the entry point. Mismatched counts are rejected rather than silently flattened."},"optional":{"env":"JSON object or .env-formatted string of environment variables. Write-only; never readable back.","force":"\"true\" to rebuild even when the content hash is unchanged."},"responses":{"200":"{status:\"unchanged\"} — identical content hash to the last push, nothing built.","202":"Queued. Returns taskId, contentHash, fileCount, files (the manifest, first 50), and a `watch` URL.","400":"empty | bad_request | bad_archive | paths_mismatch | too_large | application_required. A `too_large` from the per-file ceiling also carries `files: [{path, bytes}]` naming every file over it.","401":"unauthorized — unknown or revoked credential.","404":"not_found — no such application, or not yours.","409":"deploy_in_progress — one deploy at a time per application.","429":"rate_limited"}},"poll":{"method":"GET","path":"/api/v1/deployments/{taskId}","description":"Status, per-step progress, the analyzed spec, warnings and hazards. Poll until status is `succeeded` or `failed`."},"secrets":{"write":{"method":"PUT","path":"/api/v1/secrets"},"list":{"method":"GET","path":"/api/v1/secrets","description":"Key names only. Values are never returned, by design."},"note":"Account tokens must pass ?applicationId=<id>."},"inspect":{"note":"All three take ?applicationId=<id> with an account token. A project key is already scoped to one application and may omit it.","history":{"method":"GET","path":"/api/v1/deployments","query":{"limit":"Default 10, maximum 50."},"description":"Recent deploys for this application, newest first: status, commit, url, and the full failure sentence in `error`.","why":"Read this before redeploying after a failure. The most common outcome on this platform is a build that succeeds and an app that never listens on its port, and the sentence in `error` names that, redeploying unchanged reproduces it exactly."},"logs":{"method":"GET","path":"/api/v1/logs","query":{"limit":"Default 100, maximum 300."},"description":"What the running container has printed, newest last.","note":"`setupNeeded` in the response means our own log access is misconfigured, not that the application is broken. Do not act on it as a deploy failure."},"health":{"method":"GET","path":"/api/v1/health","description":"Last observed state: healthy, degraded, down or unknown, with `checkedAt`.","why":"A reading from a sweep that runs every five minutes, not a probe taken now. Compare `checkedAt` against your deploy before concluding anything from it."}},"limits":{"deploysPerHour":20,"maxFiles":4000,"maxFileBytes":29360128,"maxTotalBytes":29360128,"concurrentDeploysPerApplication":1},"notes":["Send the whole project directory. A push containing only an entry point builds and deploys successfully, then serves a page whose assets 404 — the most common failure on this API.","Warnings and hazards in the poll response are structured data meant to be read and relayed, not decoration.","A file larger than maxFileBytes fails the whole push with 400 too_large and names it. Nothing is dropped and built around, so a 202 means every file you sent is in the build — check `files` in the response if you want the manifest back.","Credentials belong in files, never in output. An account token printed to a terminal is a live credential in scrollback and in the transcript of whatever agent is reading along."]}