#GhostUser API

Trigger ghost-user tests on any URL and get back usability findings plus a score, programmatically. Point GhostUser at a page, and opinionated AI "ghosts" with different personas try it like real users — then report what's confusing, broken, or annoying, with an overall score out of 10.

The API mirrors the product: kick off a run, poll until it's done, and read the structured report. Wire it into CI to catch regressions on every deploy, schedule nightly checks, or build it into your own dashboard.

Base URL
https://ghostuser.io/v1
Auth
Bearer gu_live_…
Format
JSON over HTTPS

Every request must include your API key as a Bearer token in the Authorization header. Keys look like gu_live_xxx and are created in the dashboard under Account → API keys at ghostuser.io.

cURL
curl https://ghostuser.io/v1/runs \
  -H "Authorization: Bearer gu_live_xxx"
Keep keys secret. Treat gu_live_ keys like passwords — use them server-side only, never in browser or mobile client code. If a key leaks, revoke it in the dashboard and issue a new one.

Three steps: start a test, poll it, and read the result. Runs are asynchronous — a real run drives a live browser and can take a few minutes — so you start a run, then poll GET /v1/runs/{id} until status is done.

  1. 1Start a runPOST /v1/runs with your URL. You get back a run id and status: "running".
  2. 2Poll the runGET /v1/runs/{id} every few seconds until status flips to done (or error).
  3. 3Read the result — pull overall_score, counts, and the issues array. Open report_url for the full visual report.
shell
# 1. Start a run
curl -X POST https://ghostuser.io/v1/runs \
  -H "Authorization: Bearer gu_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://yourapp.com","tier":"economy"}'
# → { "id": "ab12cd34", "status": "running", ... }

# 2. Poll until status is "done"
curl https://ghostuser.io/v1/runs/ab12cd34 \
  -H "Authorization: Bearer gu_live_xxx"
# → { "id": "ab12cd34", "status": "done", "overall_score": 7.5, ... }

A run is the core resource: one ghost-user test of one URL. Create a run to start a test, fetch a run to read its report, and list runs to see your recent history. A run moves through runningdone (or error).

POST /v1/runs

Starts a new ghost-user test on a URL. Returns immediately with 201 Created and a run in the running state — poll GET /v1/runs/{id} for the result.

Body parameters

NameTypeRequiredDescription
url string required The URL the ghosts start from. Use a deep link to land them on a specific page or flow.
tier string optional Depth/cost of the run. One of quick, economy, balanced, premium. Defaults to economy.
personas string[] optional Persona keys to run, e.g. first_timer, mobile_first_user. Defaults to a sensible set for your product. See Personas.
scenario string optional A focus for the run, e.g. "test the signup flow". Omit to let ghosts explore and find the core flow.

Example request

shell
curl -X POST https://ghostuser.io/v1/runs \
  -H "Authorization: Bearer gu_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com",
    "tier": "economy",
    "personas": ["first_timer", "mobile_first_user"],
    "scenario": "test the signup flow"
  }'

Response · 201 Created

json
{
  "id": "ab12cd34",
  "status": "running",
  "url": "https://yourapp.com",
  "tier": "economy",
  "created_at": "2026-06-23T14:00:00Z",
  "report_url": "https://ghostuser.io/report/ab12cd34"
}
GET /v1/runs/{id}

Returns a run and, once finished, its full report. While running, status is running and report fields may be absent. Poll this endpoint until status is done (terminal states are done and error).

Path parameters

NameTypeRequiredDescription
id string required The run id returned by POST /v1/runs, e.g. ab12cd34.

Example request

shell
curl https://ghostuser.io/v1/runs/ab12cd34 \
  -H "Authorization: Bearer gu_live_xxx"

Response · 200 OK

json
{
  "id": "ab12cd34",
  "status": "done",
  "url": "https://yourapp.com",
  "tier": "economy",
  "created_at": "2026-06-23T14:00:00Z",
  "overall_score": 7.5,
  "counts": { "critical": 0, "major": 1, "minor": 2 },
  "num_ghosts": 2,
  "headline": "Solid core flow, but the signup CTA is easy to miss on mobile.",
  "issues": [
    {
      "severity": "major",
      "title": "Signup button is below the fold on mobile",
      "detail": "On a 375px viewport the primary CTA sits under three scrolls of copy.",
      "quote": "Wait, where do I actually sign up? I had to scroll forever.",
      "fix": "Pin a sticky 'Get started' button or move it above the fold on small screens."
    }
  ],
  "report_url": "https://ghostuser.io/report/ab12cd34"
}

Response fields

FieldTypeDescription
statusstringOne of running, done, error. Poll until done.
overall_scorenumberUsability score from 0–10. ≥7 is good, 4–7 mid, <4 needs work.
countsobjectIssue counts by severity: critical, major, minor.
num_ghostsnumberHow many ghost personas tested the URL.
headlinestringA one-line human summary of the run.
issuesobject[]Findings, each with severity, title, detail, quote (a ghost's words), and a suggested fix.
report_urlstringLink to the full visual report in the dashboard.
GET /v1/runs

Returns a compact list of your most recent runs, newest first — handy for dashboards and history views.

Example request

shell
curl https://ghostuser.io/v1/runs \
  -H "Authorization: Bearer gu_live_xxx"

Response · 200 OK

json
{
  "runs": [
    {
      "id": "ab12cd34",
      "status": "done",
      "url": "https://yourapp.com",
      "overall_score": 7.5,
      "created_at": "2026-06-23T14:00:00Z",
      "report_url": "https://ghostuser.io/report/ab12cd34"
    },
    {
      "id": "ef56gh78",
      "status": "running",
      "url": "https://yourapp.com/pricing",
      "overall_score": null,
      "created_at": "2026-06-23T14:08:00Z",
      "report_url": "https://ghostuser.io/report/ef56gh78"
    }
  ]
}

Each ghost is a persona — a distinct kind of user with their own goals, patience, and blind spots. Pass personas as an array of keys on POST /v1/runs to choose who tests your app. Omit it to use a sensible default set tuned for your product.

first_timer

Never seen your product — judges onboarding cold.

mobile_first_user

Lives on a phone; flags anything that breaks on small screens.

skeptical_student

Impatient and unimpressed; bounces if value isn't obvious fast.

privacy_skeptic

Reads the fine print; wary of data, tracking, and permissions.

accessibility_user

Tests contrast, labels, and keyboard/screen-reader friendliness.

busy_professional

Time-starved; wants the core action done in seconds, no fluff.

dev_security_conscious

A developer's eye — scrutinizes API keys, scopes, docs, and integration safety.

This is an illustrative subset. The full persona catalog — plus the per-product default set and industry-specific ghosts — lives in the dashboard.

Runs cost credits, billed on completion by actual usage. The tier trades depth for cost — higher tiers let each ghost take more steps and dig deeper, at a higher credit cost. Pick the tier that fits how thorough you need the run to be.

TierRelative costBest for
quick¢A fast smoke test — does the core flow work at all?
economy$The default. Solid coverage at the lowest real cost.
balanced$$Deeper exploration for important flows and releases.
premium$$$Maximum depth — the most thorough, patient ghosts.
Credits are charged on the run's actual usage, not a fixed price, and economy runs are cheapest. Check your balance and top up in the dashboard. A run that would overdraw your balance is rejected with 402 insufficient_credits — see Errors.

Errors use standard HTTP status codes and return a consistent JSON shape with a machine-readable code and a human-readable message.

json
{
  "error": {
    "code": "invalid_api_key",
    "message": "The API key provided is invalid or has been revoked."
  }
}
HTTPCodeMeaning
400bad_requestMalformed request — missing url, bad JSON, or an unknown tier / persona key.
401invalid_api_keyMissing, malformed, or revoked API key in the Authorization header.
402insufficient_creditsNot enough credits to start the run. Top up in the dashboard.
404not_foundNo run exists with that id for your account.
429rate_limitedToo many requests too quickly. Back off and retry. See Rate limits.
500server_errorSomething went wrong on our side. Safe to retry; if it persists, get in touch.

The API allows a few requests per second per key — plenty for normal use. Because runs are asynchronous, you don't hold a connection open while a test runs: you start a run, then poll GET /v1/runs/{id} on an interval.

A polling interval of every 5 seconds is a good default — runs take minutes, not milliseconds, so faster polling just burns requests. If you exceed the limit you'll get 429 rate_limited; back off and retry. Need higher limits? Reach out via the dashboard.