Requests and Reliability
The API is JSON over HTTP. The exact fields vary by endpoint, but integrations should use the same request conventions everywhere: send a Bearer token, use idempotency on retried writes, follow pagination links, and poll asynchronous resources at a reasonable cadence.
Authentication
Send your API token in the Authorization header.
export WALLFACER_TOKEN="wf_tok_..."
export WALLFACER_API="https://api.wallfacer.ai/v1"
curl -s "$WALLFACER_API/accounts" \
-H "Authorization: Bearer $WALLFACER_TOKEN"Keep tokens outside source code. In local scripts, use environment variables. In CI, store them as encrypted secrets.
Headers
Use these on JSON requests:
Authorization: Bearer YOUR_TOKEN
Accept: application/json
Content-Type: application/jsonContent-Type matters only when you send a body. For GET and DELETE requests with no body, omit it if your HTTP client prefers.
For write requests that may be retried, add an idempotency key:
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $REQUEST_ID" \
-d '{"title":"Fix flaky checkout test"}'Generate the key once for the logical operation and reuse it across retries. If your retry layer creates a new key on every attempt, it is not protecting you from duplicate writes.
Response Shape
Successful resource responses wrap the payload in data.
{
"data": {
"id": "019dd5b7-cba4-70f3-9f3b-425fbfb2ac3c"
}
}List responses include data and usually include links and meta.
{
"data": [],
"links": {
"next": null
},
"meta": {
"per_page": 25
}
}Error responses include errors. Branch on code, not on the human message.
{
"errors": [
{
"message": "The environment_id field is required.",
"code": "validation_error"
}
]
}Pagination
List endpoints accept per_page with a maximum of 100. Pagination metadata is not identical across every endpoint, so handle both link-based and cursor-based responses.
Follow links.next when it is present. Cursor-paginated responses also include meta.next_cursor and meta.prev_cursor; when meta.next_cursor is present, pass it back as the next request's cursor value.
async function* paginate(firstUrl, token) {
let url = firstUrl;
while (url) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
const body = await response.json();
for (const item of body.data ?? []) {
yield item;
}
if (body.links?.next) {
url = body.links.next;
} else if (body.meta?.next_cursor) {
const nextUrl = new URL(url);
nextUrl.searchParams.set('cursor', body.meta.next_cursor);
url = nextUrl.toString();
} else {
url = null;
}
}
}Use per_page=100 for batch jobs. Use the default page size for UI views that only render the first page.
Retries
Retry only failures that can plausibly succeed without changing the request.
| Status or failure | Strategy |
|---|---|
429 Too Many Requests | Respect Retry-After when present, otherwise back off. |
502 Bad Gateway | A dependency the endpoint calls replied with a failure. Back off and retry. Use an idempotency key for writes. |
503 Service Unavailable | Back off and retry. Use an idempotency key for writes. |
502 with errors[].code: "kms_error" on a credential write | The key service could not seal the value and nothing was stored. Retry the same request. |
| Network timeout | Retry with the same idempotency key for writes. |
409 Conflict from an in-flight idempotency key | Wait, then retry the same request with the same key. |
Do not retry validation or authorization failures without changing something.
| Status | Meaning |
|---|---|
400 | The request shape is wrong for the endpoint. |
401 | The token is missing, expired, revoked, or malformed. |
403 | The token is valid but cannot access this account or operation. |
404 | The resource does not exist or is hidden from this token. |
422 | The body failed validation or a required dependency is missing. |
502 | A service the endpoint depends on replied with a failure. The request shape was fine. |
503 | A service the endpoint depends on could not be reached. |
The difference between 502 and 503 is which way the dependency failed: 502 means it answered with a failure, 503 means we could not reach it. Both are worth retrying.
Credential routes answer 502 with errors[].code: kms_error when the key service behind stored values fails. Nothing is written on a create, and an existing credential keeps its stored value and name on an update, so a retry is safe.
Polling
Several operations are asynchronous:
| Operation | Poll |
|---|---|
| Environment snapshot generation | GET /v1/accounts/{account}/environments/{environment} until base_snapshot.status is ready or failed. |
| Direct VM boot | GET /v1/accounts/{account}/vms/{vm} until ready is true or status is failed. |
| Session boot | GET /v1/accounts/{account}/tasks/{task}/sessions/{session} until status is active, idle, or closed. |
| iOS rebuild | GET /v1/accounts/{account}/vms/{vm}/simulator/logs until the build event stream shows completion or failure. |
Poll VMs every few seconds. Poll snapshot generation less aggressively; it can take minutes because setup runs before the disk is captured.
Debugging
Capture the HTTP status code and the errors[].code value. For boot and setup failures, the useful details are usually in logs rather than the original create response.
- Use environment events to see snapshot generation progress.
- Use snapshot logs for failed environment generation.
- Use VM logs for direct infrastructure failures.
- Use session logs and messages for managed coding failures.
See Snapshots and Logs for the diagnostic workflow.