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
| Method | Path | Token | Purpose |
|---|---|---|---|
POST | /api/v1/projects | browser session only | Create a project. |
GET | /api/v1/projects | any token | List your projects. |
GET | /api/v1/projects/{projectId} | any token | Get a project. |
PUT | /api/v1/projects/{projectId} | browser session only | Update a project. |
DELETE | /api/v1/projects/{projectId} | browser session only | Delete a project and everything in it. |
GET | /api/v1/projects/{projectId}/plan | any token | The project’s plan and current-month capture count. |
Endpoints
| Method | Path | Token | Purpose |
|---|---|---|---|
POST | /api/v1/projects/{projectId}/endpoints | read-write token | Create an endpoint. |
GET | /api/v1/projects/{projectId}/endpoints | any token | List endpoints. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId} | any token | Get an endpoint. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/capture-stats | any token | Capture counts for the endpoint. |
PUT | /api/v1/projects/{projectId}/endpoints/{endpointId} | browser session only | Update an endpoint. |
DELETE | /api/v1/projects/{projectId}/endpoints/{endpointId} | browser session only | Delete an endpoint and its captures. |
PUT | /api/v1/projects/{projectId}/endpoints/{endpointId}/signature | browser session only | Configure inbound signature validation. |
PUT | /api/v1/projects/{projectId}/endpoints/{endpointId}/signing-key | read-write token | Set the endpoint’s signing key. The value is accepted once and never returned. |
PUT | /api/v1/projects/{projectId}/endpoints/{endpointId}/suspended | browser session only | Suspend or resume an endpoint. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/sections | any token | Room sections, roster and orientation. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/canon | any token | What stands ratified per section. Supports ETag and 304. |
Captures
| Method | Path | Token | Purpose |
|---|---|---|---|
GET | /api/v1/endpoints/{endpointId}/captured-requests | any token | List captures, newest first. See the parameters below. |
GET | /api/v1/endpoints/{endpointId}/captured-requests/wait | any token | Long-poll for the next capture after a cursor (default 20 seconds). |
GET | /api/v1/endpoints/{endpointId}/captured-requests/count | any token | Count only. |
GET | /api/v1/endpoints/{endpointId}/captured-requests/{captureId} | any token | One capture with its full body. |
GET | /api/v1/endpoints/{endpointId}/captured-requests/{captureId}/executions | any token | Replay executions for one capture. |
PUT | /api/v1/endpoints/{endpointId}/captured-requests/{captureId} | browser session only | Update a capture’s label. |
POST | /api/v1/endpoints/{endpointId}/captured-requests/{captureId}/lock | browser session only | Lock a capture so retention never removes it (Captain). |
POST | /api/v1/endpoints/{endpointId}/captured-requests/{captureId}/unlock | browser session only | Unlock it. |
GET | /api/v1/projects/{projectId}/captures/digest | any token | Grouped counts by endpoint, event type, provider, label and hour. |
GET | /api/v1/projects/{projectId}/locked-captures | any token | Locked captures across the project. |
Listing captures
| Parameter | Default | Meaning |
|---|---|---|
take | 50 | Page size. |
skip | 0 | Offset paging. Ignored when a cursor is given. |
since | Only captures at or after this time. | |
until | Only captures at or before this time. | |
eventType | Filter by the provider event type, for example checkout.session.completed. | |
label | Filter by the exact user label. | |
after | Opaque cursor. Returns only captures that landed after it, oldest first, so a script can catch up without missing one. | |
includeBody | false | Inline 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
| Method | Path | Token | Purpose |
|---|---|---|---|
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets | read-write token | Create a target. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets | any token | List targets. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId} | any token | Get a target. |
PUT | /api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId} | read-write token | Update a target. |
DELETE | /api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId} | browser session only | Delete a target. |
PUT | /api/v1/projects/{projectId}/endpoints/{endpointId}/replay-targets/{targetId}/headers | read-write token | Set 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}/headers | any token | Read headers. Credential-shaped values are masked for token callers. |
Replay
| Method | Path | Token | Purpose |
|---|---|---|---|
POST | /api/v1/replay/single | read-write token | Replay one capture to one target and wait for the answer. |
POST | /api/v1/replay/enqueue | read-write token | Queue one capture to many targets. Returns execution ids. |
POST | /api/v1/replay/enqueue-batch | read-write token | Queue many captures to many targets (Bosun and up). Returns execution ids. |
POST | /api/v1/replay/sequences | read-write token | Create an ordered sequence (Bosun and up). |
GET | /api/v1/replay/sequences/{sequenceId} | any token | Get a sequence and its item statuses. |
GET | /api/v1/projects/{projectId}/replay-executions | any token | List executions, paged. |
GET | /api/v1/projects/{projectId}/replay-executions/{executionId} | any token | One execution: what the target answered. |
POST | /api/v1/replay/record | any token | Used 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
| Method | Path | Token | Purpose |
|---|---|---|---|
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections | read-write token | Create a collection. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections | any token | List collections. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId} | any token | Get a collection and its ordered captures. |
PUT | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId} | read-write token | Rename or describe it. |
DELETE | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId} | read-write token | Delete it. The captures stay. |
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/captures | read-write token | Add captures. |
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/remove-captures | read-write token | Remove captures. |
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/replace-item | read-write token | Swap one capture for another in place. |
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/reorder | read-write token | Reorder. |
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/collections/{collectionId}/run | read-write token | Run 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
| Method | Path | Token | Purpose |
|---|---|---|---|
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/transformations | any token | List transformations. |
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/transformations | read-write token | Create one. |
GET | /api/v1/projects/{projectId}/transformations/{transformationId} | any token | Get one. |
PUT | /api/v1/projects/{projectId}/transformations/{transformationId} | read-write token | Update. Edits create a new version; bindings keep the one they were armed with. |
DELETE | /api/v1/projects/{projectId}/transformations/{transformationId} | browser session only | Delete. |
POST | /api/v1/projects/{projectId}/transformations/preview | any token | Dry-run an expression against a capture. |
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/predicate-evaluate | any token | Evaluate a JSONata predicate over recent captures. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/shape-catalog | any token | Output shapes a transformation can target. |
GET | /api/v1/projects/{projectId}/endpoints/{endpointId}/bindings | any token | List bindings. |
POST | /api/v1/projects/{projectId}/endpoints/{endpointId}/bindings | read-write token | Bind a transformation to a target. |
PUT | /api/v1/projects/{projectId}/bindings/{bindingId} | browser session only | Update a binding. |
DELETE | /api/v1/projects/{projectId}/bindings/{bindingId} | browser session only | Delete a binding. |
Watches
| Method | Path | Token | Purpose |
|---|---|---|---|
POST | /api/v1/endpoints/{endpointId}/watches | read-write token | Register a standing predicate. |
GET | /api/v1/endpoints/{endpointId}/watches | any token | List watches with match counts and the last error. |
PUT | /api/v1/endpoints/{endpointId}/watches/{watchId} | read-write token | Rename, or change the predicate or label. |
PUT | /api/v1/endpoints/{endpointId}/watches/{watchId}/enabled | read-write token | Pause or resume without losing history. |
DELETE | /api/v1/endpoints/{endpointId}/watches/{watchId} | browser session only | Delete. |
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
| Status | When | Body |
|---|---|---|
| 400 | A malformed request or id. | { "Error": "..." } |
| 401 | No credential, or an expired or invalid one. | empty |
| 403 | A token without the right scope, or a token on a browser-only route. | { "Error": "..." } |
| 404 | The resource does not exist, or belongs to someone else, or has aged out. | empty |
| 409 | A conflict, for example a listener already attached to a target. | problem details |
| 422 | A validation failure, with the failing fields. | problem details |
| 429 | Rate 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.