REST API

FlurryPORT has two HTTP surfaces and they do different jobs. The capture URL receives webhooks from providers and is documented on the webhook API reference. The REST API, this page, manages what happens around them: projects, endpoints, captures, replay targets, replay, collections, transformations and watches. If you are pointing a provider at us, you want the other page. If you are scripting FlurryPORT itself, you want this one.

https://api.flurryport.io/api/v1/...

Authentication

Browser session. The dashboard authenticates with a JWT cookie and sends a matching csrf header on every request. If you are calling the API from a signed-in browser session, this is what you already have. Some routes, marked "browser session only" below, accept nothing else.

Personal access tokens. For scripts, CI and anything running without a person present. Mint one in the app under Settings and send it as a bearer credential. No CSRF header is needed.

curl https://api.flurryport.io/api/v1/projects \
  -H "Authorization: Bearer $FLURRYPORT_TOKEN"

A token carries two scopes, both on by default when you mint it: read-only and redact PII. Both fail closed. A read-only token that hits a write route gets a 403 with a JSON body explaining that write access is granted by the account owner, never by the caller. A token of any scope that hits a route marked browser-only gets a 403 as well. Redaction masks emails, card numbers, phone numbers and IP addresses in capture reads, best effort; forwards to your own machine are untouched so signatures keep verifying.

Treat a token like a password. It is not a webhook secret and it is not a capture URL, and unlike both of those it can change your account.

Identifiers

Every id in a URL is a Base62 string, the same form the dashboard shows. Ids in JSON bodies are plain GUIDs. A malformed URL id answers 400 with { "Error": "Invalid identifier" }.

Projects

MethodPathTokenPurpose
POST/api/v1/projectsbrowser session onlyCreate a project.
GET/api/v1/projectsany tokenList your projects.
GET/api/v1/projects/{projectId}any tokenGet a project.
PUT/api/v1/projects/{projectId}browser session onlyUpdate a project.
DELETE/api/v1/projects/{projectId}browser session onlyDelete a project and everything in it.
GET/api/v1/projects/{projectId}/planany tokenThe project’s plan and current-month capture count.

Endpoints

MethodPathTokenPurpose
POST/api/v1/projects/{projectId}/endpointsread-write tokenCreate an endpoint.
GET/api/v1/projects/{projectId}/endpointsany tokenList endpoints.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}any tokenGet an endpoint.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/capture-statsany tokenCapture counts for the endpoint.
PUT/api/v1/projects/{projectId}/endpoints/{endpointId}browser session onlyUpdate an endpoint.
DELETE/api/v1/projects/{projectId}/endpoints/{endpointId}browser session onlyDelete an endpoint and its captures.
PUT/api/v1/projects/{projectId}/endpoints/{endpointId}/signaturebrowser session onlyConfigure inbound signature validation.
PUT/api/v1/projects/{projectId}/endpoints/{endpointId}/signing-keyread-write tokenSet the endpoint’s signing key. The value is accepted once and never returned.
PUT/api/v1/projects/{projectId}/endpoints/{endpointId}/suspendedbrowser session onlySuspend or resume an endpoint.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/sectionsany tokenRoom sections, roster and orientation.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/canonany tokenWhat stands ratified per section. Supports ETag and 304.

Captures

MethodPathTokenPurpose
GET/api/v1/endpoints/{endpointId}/captured-requestsany tokenList captures, newest first. See the parameters below.
GET/api/v1/endpoints/{endpointId}/captured-requests/waitany tokenLong-poll for the next capture after a cursor (default 20 seconds).
GET/api/v1/endpoints/{endpointId}/captured-requests/countany tokenCount only.
GET/api/v1/endpoints/{endpointId}/captured-requests/{captureId}any tokenOne capture with its full body.
GET/api/v1/endpoints/{endpointId}/captured-requests/{captureId}/executionsany tokenReplay executions for one capture.
PUT/api/v1/endpoints/{endpointId}/captured-requests/{captureId}browser session onlyUpdate a capture’s label.
POST/api/v1/endpoints/{endpointId}/captured-requests/{captureId}/lockbrowser session onlyLock a capture so retention never removes it (Captain).
POST/api/v1/endpoints/{endpointId}/captured-requests/{captureId}/unlockbrowser session onlyUnlock it.
GET/api/v1/projects/{projectId}/captures/digestany tokenGrouped counts by endpoint, event type, provider, label and hour.
GET/api/v1/projects/{projectId}/locked-capturesany tokenLocked captures across the project.

Listing captures

ParameterDefaultMeaning
take50Page size.
skip0Offset paging. Ignored when a cursor is given.
sinceOnly captures at or after this time.
untilOnly captures at or before this time.
eventTypeFilter by the provider event type, for example checkout.session.completed.
labelFilter by the exact user label.
afterOpaque cursor. Returns only captures that landed after it, oldest first, so a script can catch up without missing one.
includeBodyfalseInline a body preview per row.

Each row carries the method, headers, query string, content type and length, remote IP, timestamps, provider hint and event type, your label, replay flags, a rejection reason if the capture was refused, and an opaque Cursor. With includeBody the row also carries Body, a UTF-8 text preview truncated to 4,096 characters; a binary payload leaves it null. The response ends with TotalCount and NextCursor.

To get a whole body, fetch the single capture. Its BodyBytes field is the full payload, base64 encoded, whatever the content type. That is the endpoint to use for anything you intend to verify a signature over.

Replay targets

MethodPathTokenPurpose
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targetsread-write tokenCreate a target.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targetsany tokenList targets.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId}any tokenGet a target.
PUT/api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId}read-write tokenUpdate a target.
DELETE/api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId}browser session onlyDelete a target.
PUT/api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId}/headersread-write tokenSet custom headers. A token may only reference secrets as $secrets.NAME, never paste a literal credential.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId}/headersany tokenRead headers. Credential-shaped values are masked for token callers.

Replay

⚠️ Replay is the destructive operation on this API: it sends real requests to real targets. Server-side replay works only against targets registered on the endpoint, and every route here needs a read-write token.
MethodPathTokenPurpose
POST/api/v1/replay/singleread-write tokenReplay one capture to one target and wait for the answer.
POST/api/v1/replay/enqueueread-write tokenQueue one capture to many targets. Returns execution ids.
POST/api/v1/replay/enqueue-batchread-write tokenQueue many captures to many targets (Bosun and up). Returns execution ids.
POST/api/v1/replay/sequencesread-write tokenCreate an ordered sequence (Bosun and up).
GET/api/v1/replay/sequences/{sequenceId}any tokenGet a sequence and its item statuses.
GET/api/v1/projects/{projectId}/replay-executionsany tokenList executions, paged.
GET/api/v1/projects/{projectId}/replay-executions/{executionId}any tokenOne execution: what the target answered.
POST/api/v1/replay/recordany tokenUsed by the CLI to record what your local server answered to a forward.

A single replay takes { "CapturedRequestId", "ReplayTargetId" } and answers with the target’s status code, its response headers (secrets redacted), a body preview, the duration in milliseconds, and an Error field that is set instead when the delivery could not be attempted: a missing secret, an unverified domain, a transformation that failed, a connection that was refused. A target on your own machine is refused here with a message pointing at flurryport listen, because the server cannot reach localhost.

The queued forms return only execution ids. Poll the execution to learn the outcome; the same fields land there once the replay processor has run it.

Collections

MethodPathTokenPurpose
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/collectionsread-write tokenCreate a collection.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/collectionsany tokenList collections.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}any tokenGet a collection and its ordered captures.
PUT/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}read-write tokenRename or describe it.
DELETE/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}read-write tokenDelete it. The captures stay.
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/capturesread-write tokenAdd captures.
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/remove-capturesread-write tokenRemove captures.
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/replace-itemread-write tokenSwap one capture for another in place.
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/reorderread-write tokenReorder.
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/runread-write tokenRun the collection as a sequence (Bosun and up). Returns the sequence and execution ids.

The run body is the CI hook:

curl -X POST https://api.flurryport.io/api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/run \
  -H "Authorization: Bearer $FLURRYPORT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"StopOnFailure": true, "DelayMs": 0}'

ReplayTargetId is optional; omitted, the run goes to every auto-forward target on the endpoint. StopOnFailure halts the sequence on the first non-2xx answer. DelayMs spaces the items out, up to 30,000. RetryUntilSuccess turns it into a run-until-200 (First Mate and up).

Transformations and bindings

MethodPathTokenPurpose
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/transformationsany tokenList transformations.
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/transformationsread-write tokenCreate one.
GET/api/v1/projects/{projectId}/transformations/{transformationId}any tokenGet one.
PUT/api/v1/projects/{projectId}/transformations/{transformationId}read-write tokenUpdate. Edits create a new version; bindings keep the one they were armed with.
DELETE/api/v1/projects/{projectId}/transformations/{transformationId}browser session onlyDelete.
POST/api/v1/projects/{projectId}/transformations/previewany tokenDry-run an expression against a capture.
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/predicate-evaluateany tokenEvaluate a JSONata predicate over recent captures.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/shape-catalogany tokenOutput shapes a transformation can target.
GET/api/v1/projects/{projectId}/endpoints/{endpointId}/bindingsany tokenList bindings.
POST/api/v1/projects/{projectId}/endpoints/{endpointId}/bindingsread-write tokenBind a transformation to a target.
PUT/api/v1/projects/{projectId}/bindings/{bindingId}browser session onlyUpdate a binding.
DELETE/api/v1/projects/{projectId}/bindings/{bindingId}browser session onlyDelete a binding.

Watches

MethodPathTokenPurpose
POST/api/v1/endpoints/{endpointId}/watchesread-write tokenRegister a standing predicate.
GET/api/v1/endpoints/{endpointId}/watchesany tokenList watches with match counts and the last error.
PUT/api/v1/endpoints/{endpointId}/watches/{watchId}read-write tokenRename, or change the predicate or label.
PUT/api/v1/endpoints/{endpointId}/watches/{watchId}/enabledread-write tokenPause or resume without losing history.
DELETE/api/v1/endpoints/{endpointId}/watches/{watchId}browser session onlyDelete.

Secrets and domains

Project secrets (/api/v1/projects/{projectId}/secrets) are browser-only end to end: a token can reference a secret by name in a target header but can never create, read or change one. Domain verification (/api/v1/domains) is the same, except that a token may list verified domains. See secrets.

Rate limits

Every request carrying a personal access token counts against a sliding window of 300 requests a minute per token. Past that the API answers 429 with no body and no Retry-After header, so back off for a few seconds and try again. The capture URL has its own limits, per endpoint and per plan, described on the plans and limits page.

Errors

StatusWhenBody
400A malformed request or id.{ "Error": "..." }
401No credential, or an expired or invalid one.empty
403A token without the right scope, or a token on a browser-only route.{ "Error": "..." }
404The resource does not exist, or belongs to someone else, or has aged out.empty
409A conflict, for example a listener already attached to a target.problem details
422A validation failure, with the failing fields.problem details
429Rate limited.empty

Business errors use the application/problem+json shape: title is a stable machine code such as burst_limit_exceeded, detail is a sentence for a person, and validation failures add an errors map keyed by field.

A 404 is deliberately uninformative. A capture that has aged out under retention, a capture that never existed and a capture owned by another account all answer the same empty 404, so ids cannot be probed. If you need to know whether something once existed, keep your own record of the ids you were given.

What this API is not

It does not receive webhooks. Providers send to the capture URL, which has its own limits, its own signature validation and its own page: the webhook API reference. And it is not the whole product: rooms, recipes and the anonymous try-it session are driven through the CLI and MCP server, whose tools are the supported interface for those.