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/json

Content-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 failureStrategy
429 Too Many RequestsRespect Retry-After when present, otherwise back off.
502 Bad GatewayA dependency the endpoint calls replied with a failure. Back off and retry. Use an idempotency key for writes.
503 Service UnavailableBack off and retry. Use an idempotency key for writes.
502 with errors[].code: "kms_error" on a credential writeThe key service could not seal the value and nothing was stored. Retry the same request.
Network timeoutRetry with the same idempotency key for writes.
409 Conflict from an in-flight idempotency keyWait, then retry the same request with the same key.

Do not retry validation or authorization failures without changing something.

StatusMeaning
400The request shape is wrong for the endpoint.
401The token is missing, expired, revoked, or malformed.
403The token is valid but cannot access this account or operation.
404The resource does not exist or is hidden from this token.
422The body failed validation or a required dependency is missing.
502A service the endpoint depends on replied with a failure. The request shape was fine.
503A 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:

OperationPoll
Environment snapshot generationGET /v1/accounts/{account}/environments/{environment} until base_snapshot.status is ready or failed.
Direct VM bootGET /v1/accounts/{account}/vms/{vm} until ready is true or status is failed.
Session bootGET /v1/accounts/{account}/tasks/{task}/sessions/{session} until status is active, idle, or closed.
iOS rebuildGET /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.