API docs

The Loophound API for studios.

For indie-studio engineers wiring Loophound into CI: every endpoint below is one Loophound key away — sent as X-Loophound-Key: <api-key> or as a Bearer header. The page splits the public surface into pushes, sweeps, and tickets — each block carries method, path, required headers, payload, and a curl recipe to copy.

Authentication

One API key in a header. Studios wire it once.

For indie-studio engineers wiring Loophound into CI, every endpoint below expects a Loophound key on the request — present it once per runner, then forget about auth for the rest of the build pipeline. The canonical future surface is X-Loophound-Key: <api-key> (or Authorization: Bearer <api-key>). Today the live router also accepts the signed-in studio's better-auth session as Authorization: Bearer <session-token>, so a CI runner following these docs keeps working without waiting for the API-key program to ship.

Every request is sent against https://app.loophound.gg. Treat the host as the only base URL — there is no per-region endpoint and no per-environment override.

Auth at a glance

curl https://app.loophound.gg/api/builds \
  -H "Authorization: Bearer <api-key>" \
  -H "X-Loophound-Key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "buildUrl": "https://cdn.example.com/frostmarch-pc.zip",
    "platform": "pc"
  }'

Pushes

Push a build, list the ones you have already sent.

Pushes are the only write path into Loophound — register a new build for monitoring and read back the catalog your studio has pushed so far. The list endpoint pairs with GET /api/sweeps/[id] to fetch the checks and tickets for any given build.

POST
/api/builds

Register a new build with Loophound for monitoring. The server records the row against your studio and returns the freshly minted BuildItem plus the deterministic sweep id you can poll for results.

AuthBearer <api-key> · X-Loophound-Key: <api-key> · requireAuth()

Required headers

Authorizationrequired
Bearer <token>
Canonical future form is X-Loophound-Key: <api-key>; today the live router also accepts the signed-in studio’s better-auth session as a Bearer token, so a CI runner following these docs keeps using Authorization: Bearer <session-token> until the API-key program ships.
Content-Typerequired
application/json
Required whenever the request carries a JSON body.

Request body

buildUrlrequired
string (URL)
Publicly hosted URL to the build artifact (https://…). Max 2048 chars.
platformrequired
'pc' | 'mobile' | 'web'
Target platform for this build.
notes
string
Free-form context for the monitoring run. Max 2000 chars.

Response body

itemrequired
BuildItem
The persisted build record.
idrequired
string
CUID for the build row.
buildUrlrequired
string (URL)
Echoed from the request.
platformrequired
'pc' | 'mobile' | 'web'
Echoed from the request.
notes
string
Echoed from the request when present.
queuedAtrequired
ISO 8601 datetime
When the server persisted the build.
sweepIdrequired
string
Identifier for the monitoring sweep that picked up this build.

curl

curl -X POST https://app.loophound.gg/api/builds \
  -H "Authorization: Bearer <api-key>" \
  -H "X-Loophound-Key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "buildUrl": "https://cdn.example.com/frostmarch-pc.zip",
    "platform": "pc",
    "notes": "Frostmarch chapter test nightly"
  }'

Request body

{
  "buildUrl": "https://cdn.example.com/frostmarch-pc.zip",
  "platform": "pc",
  "notes": "Frostmarch chapter test nightly"
}

Response

{
  "item": {
    "id": "clx1abc2de3fghij4klm5nop6",
    "buildUrl": "https://cdn.example.com/frostmarch-pc.zip",
    "platform": "pc",
    "notes": "Frostmarch chapter test nightly",
    "queuedAt": "2026-08-09T08:15:42.000Z"
  },
  "sweepId": "smp-cluster-1"
}
GET
/api/builds

List every build the signed-in studio has pushed, newest first. Pair each entry with GET /api/sweeps/[id] to read the checks and tickets the agents surfaced for that build.

AuthBearer <api-key> · X-Loophound-Key: <api-key> · requireAuth()

Required headers

Authorizationrequired
Bearer <token>
Canonical future form is X-Loophound-Key: <api-key>; today the live router also accepts the signed-in studio’s better-auth session as a Bearer token, so a CI runner following these docs keeps using Authorization: Bearer <session-token> until the API-key program ships.

Response body

itemsrequired
BuildItem[]
Every build the signed-in studio has pushed, newest first.
idrequired
string
CUID for the build row.
buildUrlrequired
string (URL)
Publicly hosted URL to the build artifact.
platformrequired
'pc' | 'mobile' | 'web'
Target platform; matches the value POSTed.
notes
string
Free-form context for the monitoring run; omitted when not set on POST.
queuedAtrequired
ISO 8601 datetime
When the server persisted the build.

curl

curl https://app.loophound.gg/api/builds \
  -H "Authorization: Bearer <api-key>"

Response

{
  "items": [
    {
      "id": "clx1abc2de3fghij4klm5nop6",
      "buildUrl": "https://cdn.example.com/frostmarch-pc.zip",
      "platform": "pc",
      "notes": "Frostmarch chapter test nightly",
      "queuedAt": "2026-08-09T08:15:42.000Z"
    },
    {
      "id": "clx7yz8w9v0qrstu1opq2rst",
      "buildUrl": "https://cdn.example.com/frostmarch-mobile.zip",
      "platform": "mobile",
      "queuedAt": "2026-08-08T11:42:01.000Z"
    }
  ]
}

Sweeps

Poll a sweep by id — sweep ↔ build is 1:1, so that is the list-by-build reference too.

The agents run a sweep against every pushed build. Each sweep id covers exactly one build, so reading a sweep by id doubles as reading the full state of the build that drove it — including the ticket set the agents surfaced from that build.

GET
/api/sweeps/[id]

Poll a sweep’s results — keep calling until overall resolves from "running" into pass / fail / partial, then read checks and the ticket refs for the build that drove the sweep.

AuthBearer <api-key> · X-Loophound-Key: <api-key> · requireAuth()

Required headers

Authorizationrequired
Bearer <token>
Canonical future form is X-Loophound-Key: <api-key>; today the live router also accepts the signed-in studio’s better-auth session as a Bearer token, so a CI runner following these docs keeps using Authorization: Bearer <session-token> until the API-key program ships.

Response body

idrequired
string
Sweep id (also returned by POST /api/builds as sweepId).
buildIdrequired
string
Build row this sweep monitored.
buildNamerequired
string
Human-readable label set by the build-intake form.
buildUrl
string (URL)
Public URL to the build artifact. Omitted for sample-fixture sweeps that have no recorded build.
platformrequired
string
Human-readable label (e.g. "PC", "Web").
platformValuerequired
'pc' | 'mobile' | 'web'
Zod-enum raw value — preferred for routing and analytics.
engine
'unity' | 'unreal' | 'godot' | 'other'
Engine declared on the running sweep. Omitted when not set on ingest.
startedAtrequired
ISO 8601 datetime
finishedAtrequired
ISO 8601 datetime
When monitoring reached its terminal state.
durationSecondsrequired
integer (>=0)
Wall-clock time from startedAt to finishedAt.
overallrequired
'pass' | 'fail' | 'partial'
Terminal sweep outcome — keep polling until this is no longer "running".
checksrequired
SweepCheck[]
Individual probes the agents completed for this sweep.
idrequired
string
Stable check id within the sweep.
namerequired
string
Human-readable probe name.
grouprequired
string
Probe group (lifecycle, multiplayer, gameplay, persistence, perf, input…).
statusrequired
'pass' | 'warn' | 'fail'
Outcome of this probe.
detailrequired
string
One-line summary of the run.
ticketsrequired
SweepTicketRef[]
Tickets the agents surfaced from this sweep. Sweep ↔ build is 1:1, so this array is the list-by-build reference — resolve each id via GET /api/flagged-tickets/[id] for full repro steps.
idrequired
string
Ticket id — case-insensitive on GET.
titlerequired
string
Short human summary.
severityrequired
'low' | 'medium' | 'high' | 'critical'

curl

curl https://app.loophound.gg/api/sweeps/smp-cluster-1 \
  -H "Authorization: Bearer <api-key>"

Response

{
  "id": "smp-cluster-1",
  "buildId": "bld_8f2a",
  "buildName": "Combat tutorial — storm weather",
  "buildUrl": "https://cdn.example.com/frostmarch-pc.zip",
  "platform": "PC",
  "platformValue": "pc",
  "engine": "unity",
  "startedAt": "2026-08-09T15:00:00.000Z",
  "finishedAt": "2026-08-09T15:14:32.000Z",
  "durationSeconds": 872,
  "overall": "fail",
  "checks": [
    {
      "id": "c1",
      "name": "Boot in under 8s",
      "group": "lifecycle",
      "status": "pass",
      "detail": "6.4s median"
    },
    {
      "id": "c2",
      "name": "Lobby join",
      "group": "multiplayer",
      "status": "pass",
      "detail": "0 retries across 30 sessions"
    },
    {
      "id": "c3",
      "name": "Match start",
      "group": "multiplayer",
      "status": "fail",
      "detail": "WebSocket drops at t+12s on EastCoast shard"
    },
    {
      "id": "c4",
      "name": "Respawn flow",
      "group": "gameplay",
      "status": "fail",
      "detail": "NullReferenceException in Player.Respawn"
    }
  ],
  "tickets": [
    {
      "id": "lh-00422",
      "title": "WebSocket drops three seconds after joining EastCoast lobby",
      "severity": "critical"
    },
    {
      "id": "lh-00428",
      "title": "Inventory item vanishes after fast-travel during a storm",
      "severity": "high"
    }
  ]
}

Tickets

Open a ticket, fetch its full payload, mark progress on it.

Tickets are the unit of work Loophound hands back to your team. Open one off a sweep, fetch the full reproduction details on demand, and ride the status-update path to track the thread your team is working through.

POST
/api/sweeps/[id]/tickets

OPEN — open a new flagged ticket off a sweep. The path id selects the sweep; the server derives title, severity, signature, environment, and steps from the sweep’s persisted checks. Returns the new ticket id so the caller can navigate to the full payload in one round-trip.

AuthBearer <api-key> · X-Loophound-Key: <api-key> · requireAuth()

Required headers

Authorizationrequired
Bearer <token>
Canonical future form is X-Loophound-Key: <api-key>; today the live router also accepts the signed-in studio’s better-auth session as a Bearer token, so a CI runner following these docs keeps using Authorization: Bearer <session-token> until the API-key program ships.

Request body

(no body)
No request body — the path id selects the sweep and the server derives title, severity, signature, environment, and steps from the sweep’s persisted checks.

Response body

ticketIdrequired
string
The new flagged ticket id. Fetch via GET /api/flagged-tickets/{ticketId} for the full payload.

curl

curl -X POST https://app.loophound.gg/api/sweeps/smp-cluster-1/tickets \
  -H "Authorization: Bearer <api-key>" \
  -H "X-Loophound-Key: <api-key>"

Response

{
  "ticketId": "lh-00501"
}
GET
/api/flagged-tickets/[id]

GET — fetch one ticket’s full payload. Same shape as a single item in any list endpoint — useful when ingesting via webhook so you can cache the full reproduction steps without a second round-trip.

AuthBearer <api-key> · X-Loophound-Key: <api-key> · requireAuth()

Required headers

Authorizationrequired
Bearer <token>
Canonical future form is X-Loophound-Key: <api-key>; today the live router also accepts the signed-in studio’s better-auth session as a Bearer token, so a CI runner following these docs keeps using Authorization: Bearer <session-token> until the API-key program ships.

Response body

idrequired
string
Ticket id. The path segment is case-insensitive.
titlerequired
string
Short human summary.
severityrequired
'low' | 'medium' | 'high' | 'critical'
Agent-confidence triage.
signaturerequired
string
Crash signature or error class — the join key for cluster detection.
environmentrequired
Environment
The platform / build combination the ticket was reproduced under.
platformrequired
'steam-deck' | 'windows' | 'macos' | 'switch' | 'linux'
buildHashrequired
string
Build revision.
buildChannelrequired
'internal' | 'playtest' | 'release-candidate'
stepsrequired
ReproStep[]
Ordered reproduction steps. Same shape as in any list endpoint — referenced by webhook integrations.
orderrequired
integer (>0)
textrequired
string
Step instruction.
reportedAtrequired
ISO 8601 datetime
statusrequired
'open' | 'triaged' | 'in_progress' | 'resolved' | 'closed'
Current ticket state.
updatedAtrequired
ISO 8601 datetime
When status last changed.

curl

curl https://app.loophound.gg/api/flagged-tickets/lh-00428 \
  -H "Authorization: Bearer <api-key>"

Response

{
  "id": "lh-00428",
  "title": "Inventory item vanishes after fast-travel during a storm",
  "severity": "high",
  "signature": "NullReferenceException on Player.Respawn",
  "environment": {
    "platform": "steam-deck",
    "buildHash": "a3f12c0",
    "buildChannel": "internal"
  },
  "steps": [
    {
      "order": 1,
      "text": "Start a new session and load the post-storm save at the Frostmarch waystone."
    },
    {
      "order": 2,
      "text": "Pick up the Frostglass Amulet from the cartographer NPC and stash it in the top inventory slot."
    },
    {
      "order": 3,
      "text": "Open the map and fast-travel to the eastern ridge while a Lightning Storm weather event is active."
    },
    {
      "order": 4,
      "text": "Open the inventory at the destination. The Frostglass Amulet is missing from the slot — no log, no salvage prompt."
    }
  ],
  "reportedAt": "2026-08-08T18:22:11.000Z",
  "status": "open",
  "updatedAt": "2026-08-08T18:22:11.000Z",
  "sweepId": null
}
PATCH
/api/flagged-tickets/[id]

COMMENT — drive a ticket’s status through the lifecycle. Today the comment surface rides this status-update path, so a team marks progress (in_progress → triaged → resolved, or back to closed) on a ticket the same way they would leave a comment thread in their tracker.

AuthBearer <api-key> · X-Loophound-Key: <api-key> · requireAuth()

Required headers

Authorizationrequired
Bearer <token>
Canonical future form is X-Loophound-Key: <api-key>; today the live router also accepts the signed-in studio’s better-auth session as a Bearer token, so a CI runner following these docs keeps using Authorization: Bearer <session-token> until the API-key program ships.
Content-Typerequired
application/json
Required whenever the request carries a JSON body.

Request body

statusrequired
'open' | 'triaged' | 'in_progress' | 'resolved' | 'closed'
New status for the ticket. Today the comment surface rides this status-update path — your team marks progress on a thread by transitioning in_progress → triaged → resolved (or absorbing it back to closed).

Response body

idrequired
string
Ticket id. The path segment is case-insensitive.
titlerequired
string
Short human summary.
severityrequired
'low' | 'medium' | 'high' | 'critical'
Agent-confidence triage.
signaturerequired
string
Crash signature or error class — the join key for cluster detection.
environmentrequired
Environment
The platform / build combination the ticket was reproduced under.
platformrequired
'steam-deck' | 'windows' | 'macos' | 'switch' | 'linux'
buildHashrequired
string
Build revision.
buildChannelrequired
'internal' | 'playtest' | 'release-candidate'
stepsrequired
ReproStep[]
Ordered reproduction steps. Same shape as in any list endpoint — referenced by webhook integrations.
orderrequired
integer (>0)
textrequired
string
Step instruction.
reportedAtrequired
ISO 8601 datetime
statusrequired
'open' | 'triaged' | 'in_progress' | 'resolved' | 'closed'
Current ticket state.
updatedAtrequired
ISO 8601 datetime
When status last changed.

curl

curl -X PATCH https://app.loophound.gg/api/flagged-tickets/lh-00428 \
  -H "Authorization: Bearer <api-key>" \
  -H "X-Loophound-Key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "in_progress"
  }'

Request body

{
  "status": "in_progress"
}

Response

{
  "id": "lh-00428",
  "title": "Inventory item vanishes after fast-travel during a storm",
  "severity": "high",
  "signature": "NullReferenceException on Player.Respawn",
  "environment": {
    "platform": "steam-deck",
    "buildHash": "a3f12c0",
    "buildChannel": "internal"
  },
  "steps": [
    {
      "order": 1,
      "text": "Start a new session and load the post-storm save at the Frostmarch waystone."
    },
    {
      "order": 2,
      "text": "Pick up the Frostglass Amulet from the cartographer NPC and stash it in the top inventory slot."
    },
    {
      "order": 3,
      "text": "Open the map and fast-travel to the eastern ridge while a Lightning Storm weather event is active."
    },
    {
      "order": 4,
      "text": "Open the inventory at the destination. The Frostglass Amulet is missing from the slot — no log, no salvage prompt."
    }
  ],
  "reportedAt": "2026-08-08T18:22:11.000Z",
  "status": "in_progress",
  "updatedAt": "2026-08-09T20:11:03.000Z",
  "sweepId": null
}

End-to-end example

Push a build, read the sweep, mark progress on the ticket.

Three calls in sequence: register a build, poll the sweep the agents ran against it, then transition the picked ticket through in_progress so your team can see who is on it. Together they cover the whole monitoring loop.

  1. 1

    Push the build

    POST /api/builds with the artifact URL and the platform it is targeting. The response carries the new BuildItem id and the sweepId you will poll next.

    Step 1 — curl

    curl -X POST https://app.loophound.gg/api/builds \
      -H "Authorization: Bearer <api-key>" \
      -H "X-Loophound-Key: <api-key>" \
      -H "Content-Type: application/json" \
      -d '{
        "buildUrl": "https://cdn.example.com/frostmarch-pc.zip",
        "platform": "pc",
        "notes": "Frostmarch chapter test nightly"
      }'
  2. 2

    Poll the sweep

    GET /api/sweeps/{sweepId} with the id from step 1. Read checks, then read the tickets array — each entry is the same set CI consumers see when they treat the sweep as the list-by-build reference.

    Step 2 — curl

    curl https://app.loophound.gg/api/sweeps/smp-cluster-1 \
      -H "Authorization: Bearer <api-key>"
  3. 3

    Mark the ticket in progress

    PATCH /api/flagged-tickets/{id} with status=in_progress so the thread's progress is visible from CI in place of dropping a tracker comment. The id is case-insensitive.

    Step 3 — curl

    curl -X PATCH https://app.loophound.gg/api/flagged-tickets/lh-00428 \
      -H "Authorization: Bearer <api-key>" \
      -H "X-Loophound-Key: <api-key>" \
      -H "Content-Type: application/json" \
      -d '{
        "status": "in_progress"
      }'

Not on Loophound yet

Not onboarded yet? Join the waitlist.

Drop your studio name, contact email, engine, and shipped platforms — we reach out as soon as monitoring for your stack opens.