Call it
#curl https://playcode.io/api/graphql \
-H 'Authorization: Bearer pc_live_...' \
-H 'Content-Type: application/json' \
-H 'x-playcode-via: api' \
-d '{"query":"query { projectSearch(first: 5) { edges { node { uid name title projectType } } } }"}'
| Part | Value |
|---|---|
| Endpoint | POST https://playcode.io/api/graphql |
| Credential | Authorization: Bearer with an access token (pc_live_...), or the token of a tool signed in through Sign in with Playcode. See Access tokens and permissions |
x-playcode-via |
Optional: api names your door in the workspace's activity |
Idempotency-Key |
Optional, on a write: see Idempotency |
How operations are named
#An operation is a resource and a verb: projectGet, domainUpdate, projectPublishCreate.
| Shape | Rule | Examples |
|---|---|---|
| The five verbs | Create, Get, Search (a list), Update, Delete |
projectGet, dnsZoneUpdate |
| Actions | One consequential thing to one resource | domainConnect, domainMove |
| Jobs | Anything that can take longer: Create starts it, Get reads its status, Cancel stops it |
projectPublishCreate, commandRunCreate |
| Settings | Fields of Update, never a verb of their own |
domainUpdate with autoRenew |
Every operation has an effect: READ changes nothing, WRITE changes something, MONEY charges the workspace.
Find an operation
#The registry is in the API itself, free for every token:
| Operation | What it answers |
|---|---|
apiOperationSearch(query, family) |
The best matches for words like "buy a domain": each with its effect, whether it needs consent, the permission it needs and its arguments |
apiOperationGet(name) |
One operation: its arguments as a JSON schema, an example, what it returns, and a ready query document |
The CLI shows the same: playcode <family> lists a family, and playcode <family> <operation> --help prints the arguments. Over MCP, the tools are search_operations and describe_operation.
| Family | What it holds | Permission a token needs |
|---|---|---|
projects |
Projects, files, commands, publishes | projects:read to read; projects:vm to change |
domains |
Names to buy, domains, DNS zones, orders | domains:read to read; domains:write to change |
chats |
Chats with Playcode's agent in a project | chats:read to read; chats:write to write |
events |
The workspace's activity | None |
consent |
Proposals | None |
api |
The registry | None |
tokens, webhooks |
Access tokens, webhook endpoints | A signed-in person only, never a token |
apps |
An app's email, secrets and services | Not reachable with a token today |
Errors
#A refusal comes back in errors, each with extensions.code from a fixed list, a reason, and metadata:
{ "errors": [ { "message": "...", "extensions": { "code": "missing_scope", "reason": "domainUpdate needs the domains:write permission", "metadata": { "needs": "domains:write" } } } ] }
| Code | Means |
|---|---|
invalid_key |
The token is unknown, revoked or expired |
missing_scope |
The token lacks a permission or does not reach the target. metadata.needs names the permission |
access_denied |
You have no access to this resource |
not_found |
No such resource |
validation |
An argument is wrong; field names it when it can |
consent_required |
The operation needs a proposal: see Consent |
proposal_drifted |
The proposal was made for other arguments |
proposal_expired |
The proposal is too old |
proposal_declined |
The proposal was declined |
proposal_consumed |
The proposal was already used |
idempotency_conflict |
The Idempotency-Key was used with other arguments |
idempotency_in_flight |
The call with this key is still running |
rate_limited |
Too many requests |
insufficient_balance |
The workspace is out of credits |
confirmation_required |
The person must confirm it's them again in the app |
killed, disabled |
Playcode stopped this traffic on purpose |
internal |
Something failed on Playcode's side |
refused |
The service refused; reason says why |
Consent
#An operation marked with consent, like buying a domain (domainCreate) or editing DNS records (dnsZoneUpdate), never applies on a bare call. It takes two:
proposalCreate(operation, args)with the operation's exact arguments. It answers the proposal's id and itsterms, written by the service that will charge or change.- The operation itself, with the same arguments and
proposalIdset to that id.
A money proposal lives 15 minutes, any other 1 hour. Changed arguments need a new proposal, and a proposal works once. proposalDecline ends one.
The API does not ask anyone in between. A tool that acts for a person must show them the terms and wait for their yes before the second call: see Docs for agents.
Idempotency
#Send Idempotency-Key with a write, and the API stores its answer for 24 hours: the same key with the same call answers the stored result instead of running again. The key is 1 to 255 characters of letters, digits, underscore, dot, colon or dash. For an operation that needs consent, the proposal plays this part.
Limits
#- A token works in one workspace. A call that names another workspace, or a project outside the token's reach, is refused.
- Reads do not land in the workspace's activity; every successful write does.
- Lists are bounded; each list operation's description states its bound.
- The generated reference of every operation is not published as a docs page yet: use
apiOperationSearchandapiOperationGet.