Debugging

Every request you send to Flint leaves a trail you can query. Each response carries a request id, every authenticated call is recorded in your request logs, and every resource keeps a timeline that stitches your API calls and Flint's webhook deliveries into one ordered history. You debug with the same API and the same key you integrate with: no support ticket required, no separate logging product, no guessing about what your code actually sent.

This page is about investigating requests after they happen. For handling failures in code as they happen (the error envelope, error types, and retry strategy), see Error Handling.

Every Response Carries a Request ID#

Every response, success or failure, returns an X-Request-Id header. The same value appears in the body: on success as a top-level request_id next to data, and on failure inside the error envelope.

Say an order create fails with a 400:

HTTP
HTTP/1.1 400 Bad Request
Content-Type: application/json
X-Request-Id: bce56cba-0827-44aa-bb56-4f200ba15ee6
Flint-Mode: test
Flint-Sandbox-Id: test_01KNB061AGD0CYYF16M5QAQE3N
JSON
{
  "error": {
    "type": "validation_error",
    "code": "UNKNOWN_FIELD",
    "message": "Unknown request field: line_itemz.",
    "param": "line_itemz",
    "request_id": "bce56cba-0827-44aa-bb56-4f200ba15ee6"
  }
}

That request_id is the thread you pull. Log it alongside your own application logs on every call, and quote it in support requests. If you already have request ids of your own, send them: Flint echoes a caller-supplied X-Request-Id header back instead of generating one, so your existing trace ids work end to end.

Three identifiers appear throughout the debugging surfaces. They look similar but name different things:

IdentifierLooks likeWhat it names
request_idbce56cba-0827-...One HTTP exchange. Returned in the X-Request-Id header, the success body, and error.request_id.
api_request_log_idrlog_01KWM6WAHJ...The stored log record for that exchange. Pass it to the detail endpoint.
correlation_idrlog_01KWM6WAHJ...The same value as api_request_log_id, stamped onto related records such as webhook events, so you can join a downstream effect back to the API call that caused it.

The Debugging Loop#

Most investigations follow the same four moves:

1

Capture the request id#

From the error body, the X-Request-Id header, or your own logs. No id in hand? The log filters below find requests by status, path, or resource instead.

2

Find the request in your logs#

List your request logs filtered by request_id or by status_bucket=client_error, and confirm what was actually sent and what came back.

3

Read the diagnosis and reproduce#

The log detail shows the request exactly as Flint received it, a plain-language diagnosis of the error, and a ready-made curl to replay it.

4

Confirm the aftermath#

Pull the resource's timeline to see every API call and webhook delivery that touched it, in order, and verify the fix landed.

List Your Recent Requests#

GET/v1/developer/request-logs

List request logs for the API key making the call. Requires developer.logs.self.read.

Start with the requests that failed:

Bash
curl "https://api.withflintpay.com/v1/developer/request-logs?status_bucket=client_error&page_size=5" \
  -H "Authorization: Bearer YOUR_API_KEY"
JSON
{
  "data": [
    {
      "api_request_log_id": "rlog_01KWM6WAHJAXHCHE87C0KBBY7G",
      "request_id": "bce56cba-0827-44aa-bb56-4f200ba15ee6",
      "correlation_id": "rlog_01KWM6WAHJAXHCHE87C0KBBY7G",
      "http_method": "POST",
      "path": "/v1/orders",
      "route_pattern": "POST /v1/orders",
      "status_code": 400,
      "latency_milliseconds": 6,
      "resource_type": "order",
      "error_code": "UNKNOWN_FIELD",
      "error_category": "validation",
      "error_summary": "The request payload or query parameters did not pass validation.",
      "retryable": false,
      "recommended_action": "Fix the field identified by the error response, then retry with the same idempotency key if this was the same logical operation.",
      "created_at": "2026-07-03T14:42:16Z"
    },
    {
      "api_request_log_id": "rlog_01KWM6VKT2RCSYCW7FAQEP1TAV",
      "request_id": "f0d1dcda-3fb9-44c2-b36d-972a38917608",
      "correlation_id": "rlog_01KWM6VKT2RCSYCW7FAQEP1TAV",
      "http_method": "GET",
      "path": "/v1/orders/ord_00000000000000000000000000",
      "route_pattern": "GET /v1/orders/{order_id}",
      "status_code": 404,
      "latency_milliseconds": 11,
      "resource_type": "order",
      "resource_id": "ord_00000000000000000000000000",
      "error_code": "RESOURCE_NOT_FOUND",
      "error_category": "request_error",
      "error_summary": "The requested resource could not be found for the authenticated merchant and environment.",
      "retryable": false,
      "recommended_action": "Check the resource ID, mode, and merchant context, then retry.",
      "created_at": "2026-07-03T14:41:52Z"
    }
  ],
  "next_page_token": "eyJzb3J0X2ZpZWxkIjoiY3JlYXRlZF9hdCIs...",
  "request_id": "acc8946e-e817-4218-8f4a-af84e60e7585"
}

Each entry is a summary: what was called, what came back, and how long it took, newest first. On failures, Flint adds a diagnosis derived from the error catalog: error_category and error_summary say what kind of failure it was in plain language, retryable says whether retrying unchanged can ever help, and recommended_action says what to do instead.

Logs are per key

The list covers requests made with the key you are calling with, in that key's environment. It is a debugging view for the integration you are running, not a merchant-wide audit log across every key. If the list comes back empty, this key has not hit a client error yet: drop status_bucket to see every request, or run any example on this site first.

Narrowing the List#

ParameterWhat it does
status_bucketall (default), success, client_error (4xx), or server_error (5xx).
request_idExact match on the id from a response header or error body.
http_methodFilter by method, such as POST.
path_querySubstring match on the path, such as /v1/orders.
resource_type, resource_idThe resource a request touched, such as order and ord_..., even when the id is buried in the body.
created_after, created_beforeRFC 3339 bounds, such as 2026-07-03T00:00:00Z.
page_size, page_tokenCursor pagination; 1 to 100 per page, default 20.

Unknown parameters are rejected with 400 UNSUPPORTED_QUERY_PARAM rather than ignored, so a typo cannot silently widen your query.

Inspect One Request#

GET/v1/developer/request-logs/{api_request_log_id}

Retrieve one request in full. Requires developer.logs.self.detail.read.

The list tells you a request failed. The detail shows you why, using the api_request_log_id from the list:

Bash
curl "https://api.withflintpay.com/v1/developer/request-logs/rlog_1kmn0aExample" \
  -H "Authorization: Bearer YOUR_API_KEY"
JSON
{
  "data": {
    "api_request_log_id": "rlog_01KWM6WAHJAXHCHE87C0KBBY7G",
    "request_id": "bce56cba-0827-44aa-bb56-4f200ba15ee6",
    "correlation_id": "rlog_01KWM6WAHJAXHCHE87C0KBBY7G",
    "http_method": "POST",
    "path": "/v1/orders",
    "query_params": [],
    "route_pattern": "POST /v1/orders",
    "status_code": 400,
    "latency_milliseconds": 6,
    "request_headers": {
      "Accept": "*/*",
      "Authorization": "[REDACTED]",
      "Content-Type": "application/json",
      "User-Agent": "curl/8.7.1"
    },
    "request_body": {
      "line_itemz": []
    },
    "response_body": {
      "error": {
        "type": "validation_error",
        "code": "UNKNOWN_FIELD",
        "message": "Unknown request field: line_itemz.",
        "param": "line_itemz",
        "request_id": "bce56cba-0827-44aa-bb56-4f200ba15ee6"
      }
    },
    "response_content_type": "application/json",
    "resource_type": "order",
    "error_code": "UNKNOWN_FIELD",
    "error_category": "validation",
    "error_summary": "The request payload or query parameters did not pass validation.",
    "retryable": false,
    "recommended_action": "Fix the field identified by the error response, then retry with the same idempotency key if this was the same logical operation.",
    "created_at": "2026-07-03T14:42:16Z",
    "reproduction": {
      "http_method": "POST",
      "path": "/v1/orders",
      "query_params": [],
      "headers": {
        "Accept": "*/*",
        "Authorization": "Bearer <API_KEY>",
        "Content-Type": "application/json",
        "Idempotency-Key": "<NEW_IDEMPOTENCY_KEY>"
      },
      "body": {
        "line_itemz": []
      },
      "curl": "curl -X POST https://api.withflintpay.com/v1/orders -H \"Accept: */*\" -H \"Authorization: Bearer <API_KEY>\" -H \"Content-Type: application/json\" -H \"Idempotency-Key: <NEW_IDEMPOTENCY_KEY>\" --data '<REDACTED_OR_CAPTURED_BODY>'",
      "safe_to_reproduce": false,
      "reproduction_complete": false,
      "redactions_present": true,
      "recommended_environment": "test",
      "warnings": [
        "This request may mutate live data; reproduce in test mode unless you have verified it is safe."
      ]
    }
  },
  "request_id": "58249894-584d-4e6f-a20c-14a8e7efc4d7"
}

This is the whole story in one object. request_body is the payload exactly as Flint received it, so the line_itemz typo is visible instead of hypothetical, and response_body pairs it with the error your code saw, including the offending param. No more reconstructing "what did we actually send" from application logs.

What the detail captures:

  • Headers are stored for every request, with credentials replaced: Authorization and other sensitive headers always read [REDACTED].
  • Bodies are captured for standard write requests (POST, PATCH, PUT) up to 64 KB, after redaction. Reads store no bodies, and sensitive writes such as creating an API key or rotating a webhook secret never capture bodies at all.
  • Query parameters are stored with sensitive values redacted.

Reproduce It Safely#

The reproduction object turns the log into a rerunnable request. Copy reproduction.curl, substitute your key for <API_KEY>, and you are replaying the exact call. The flags around it keep you honest:

safe_to_reproduceboolean

true only for read requests (GET, HEAD, OPTIONS). Anything that writes is marked unsafe by default because replaying it may mutate data.

reproduction_completeboolean

false when redaction removed something the original request contained. The replay may need you to fill a value back in.

recommended_environmentstring

Always test. Reproduce with a sandbox-bound test key first, even when the original request was live.

Write reproductions come with a fresh Idempotency-Key placeholder rather than the original key. Replaying with the original key would return the cached response instead of exercising the code path you are debugging.

Rebuild What Happened to a Resource#

GET/v1/developer/resource-timelines/{resource_id}

One ordered history of API requests, webhook events, and delivery attempts for a single resource. Requires developer.resource_timelines.read plus the resource's own read scope, such as commerce.orders.read.

Request logs answer "what did this request do". Timelines answer the harder production question: "what happened to this order". Pass any resource id and Flint stitches together every API call that touched it and every webhook it produced, newest first:

Bash
curl "https://api.withflintpay.com/v1/developer/resource-timelines/ord_1kmn0aExample" \
  -H "Authorization: Bearer YOUR_API_KEY"
JSON
{
  "data": {
    "resource_id": "ord_01KWJZ8ZD3EDT7F78T82TZ6JXD",
    "resource_type": "order",
    "environment_id": "test_01KNB061AGD0CYYF16M5QAQE3N",
    "test": true,
    "entries": [
      {
        "entry_type": "webhook_delivery_attempt",
        "resource_timeline_entry_id": "watt_01KWJZ9910Q6H1M3F0Z34B0K1R",
        "occurred_at": "2026-07-03T03:09:44Z",
        "test": true,
        "webhook_delivery_attempt_id": "watt_01KWJZ9910Q6H1M3F0Z34B0K1R",
        "webhook_event_id": "whev_01KWJZ98YB7GXQ0S6E29Z4K3VD",
        "attempt_number": 1,
        "status": "delivered",
        "delivery_trigger": "automatic_delivery",
        "status_code": 200,
        "duration_milliseconds": 98
      },
      {
        "entry_type": "webhook_event",
        "resource_timeline_entry_id": "whev_01KWJZ98YB7GXQ0S6E29Z4K3VD",
        "occurred_at": "2026-07-03T03:09:43Z",
        "test": true,
        "resource_type": "order",
        "resource_id": "ord_01KWJZ8ZD3EDT7F78T82TZ6JXD",
        "api_request_log_id": "rlog_01KWJZ988BAEK5R5RZ025PX0S5",
        "correlation_id": "rlog_01KWJZ988BAEK5R5RZ025PX0S5",
        "webhook_event_id": "whev_01KWJZ98YB7GXQ0S6E29Z4K3VD",
        "webhook_endpoint_id": "whep_01KWJ8Q0V2N4X7B1D9F3H5J6KM",
        "event_type": "order.payment_succeeded",
        "status": "delivered",
        "event_source": "business_event",
        "automatic_attempt_count": 1,
        "max_attempts": 9,
        "delivered_at": "2026-07-03T03:09:44Z"
      },
      {
        "entry_type": "api_request",
        "resource_timeline_entry_id": "rlog_01KWJZ988BAEK5R5RZ025PX0S5",
        "occurred_at": "2026-07-03T03:09:42Z",
        "test": true,
        "resource_type": "order",
        "resource_id": "ord_01KWJZ8ZD3EDT7F78T82TZ6JXD",
        "api_request_log_id": "rlog_01KWJZ988BAEK5R5RZ025PX0S5",
        "correlation_id": "rlog_01KWJZ988BAEK5R5RZ025PX0S5",
        "request_id": "7dd09c80-6fe4-4db3-aa9b-3ac5c83d48ff",
        "http_method": "POST",
        "path": "/v1/orders/ord_01KWJZ8ZD3EDT7F78T82TZ6JXD/charges",
        "route_pattern": "POST /v1/orders/{order_id}/charges",
        "status_code": 200,
        "latency_milliseconds": 139
      },
      {
        "entry_type": "api_request",
        "resource_timeline_entry_id": "rlog_01KWJZ8ZCJHCAZK2XBKGRQXB4A",
        "occurred_at": "2026-07-03T03:09:20Z",
        "test": true,
        "resource_type": "order",
        "resource_id": "ord_01KWJZ8ZD3EDT7F78T82TZ6JXD",
        "api_request_log_id": "rlog_01KWJZ8ZCJHCAZK2XBKGRQXB4A",
        "correlation_id": "rlog_01KWJZ8ZCJHCAZK2XBKGRQXB4A",
        "request_id": "3ee2d706-1274-472b-8b5a-629e18208e84",
        "http_method": "POST",
        "path": "/v1/orders",
        "route_pattern": "POST /v1/orders",
        "status_code": 201,
        "latency_milliseconds": 84
      }
    ]
  },
  "request_id": "b7b7c0f6-8599-4398-9451-61fce005379e"
}

Read bottom to top: the order was created, a charge was applied, the charge produced an order.payment_succeeded webhook event, and the first delivery attempt reached your endpoint and got a 200 back. When a customer says "I paid but nothing shipped", this is the view that tells you in one call whether the payment happened, whether the event fired, and whether your server accepted it.

The entries share the same correlation keys as the request logs: a webhook_event entry carries the api_request_log_id of the API call that triggered it, so you can jump straight from an event back to the full request detail.

Shaping the Timeline#

ParameterWhat it does
includeComma-separated entry types: requests, webhooks, attempts. Defaults to all three. attempts requires webhooks.
resource_typeOptional check that the id is the type you expect; mismatches return 400 INVALID_RESOURCE_TYPE.
occurred_after, occurred_beforeRFC 3339 bounds on entry time. Note the name: timelines use occurred_*, not created_*.
page_size, page_tokenCursor pagination, 1 to 100 per page, default 20.

Timelines work for the resources you interact with most: orders, payment intents, payment methods, refunds, invoices, subscriptions and plans, checkout sessions, payment links, products and variants, coupons, customers, balances and balance transactions, payouts, and disputes. An id that is not a supported resource returns 400 UNSUPPORTED_RESOURCE_TYPE.

The wrong-environment 404 diagnoses itself

Timelines are environment-scoped, and the classic "this id works in test but 404s in live" confusion is caught explicitly: if the resource exists in a different environment than your key, the 404 carries a remediation block with reason RESOURCE_IN_DIFFERENT_ENVIRONMENT telling you to retry with a key for the right mode. Check the Flint-Mode and Flint-Sandbox-Id headers on your responses when in doubt.

What Is Captured, and What Never Is#

Request logs are designed so you can debug production without your logs becoming the vulnerability:

  • Only authenticated requests are recorded. A request that never authenticates (a 401) leaves no log entry; debug those from the error body and the authentication error table. Everything after successful authentication is recorded, including 403 scope failures and validation errors.
  • Sensitive values never reach storage. Credentials, tokens, signatures, card and bank numbers, and personal data such as emails, phone numbers, and address lines are replaced with [REDACTED] before the log is written. You cannot recover a customer's card number from your own logs, by design.
  • Bodies are bounded. Captured bodies are truncated at 64 KB with a [TRUNCATED] marker.
  • Retention is tiered. Full detail (headers and bodies) is kept for 14 days; summaries remain queryable for 90 days.
  • Visibility is per key and per environment. A key sees only its own traffic, in its own sandbox or live environment. Requesting another key's log id returns 404, not 403, so log ids do not leak across keys.
  • Reading logs never creates logs. The three endpoints on this page are excluded from capture, as is demo-session traffic.

Scopes#

ScopeGrants
developer.logs.self.readListing request log summaries.
developer.logs.self.detail.readFull request detail, including the reproduction. Not implied by the list scope.
developer.resource_timelines.readResource timelines, together with the resource's own read scope.

A key missing the needed scope gets 403 INSUFFICIENT_SCOPE with the scope named in the message. See the scope catalog for how these fit into a key's grants.

Where to Look First#

SymptomStart here
401Not in your logs; the request never authenticated. Read the error body and the Authentication error table.
403The log detail and error message name the missing scope. Grant it or use a different key.
400 validation errorerror.param names the field; the log detail's request_body shows what you actually sent.
409 conflictUsually an idempotency replay or reuse; check the Idempotency-Replayed header and see Idempotency.
429Honor the Retry-After header; see Rate Limits.
Webhook never arrivedThe resource timeline's webhook_delivery_attempt entries show each attempt's status code and error; see Webhooks for retries and resending.
404 for an id you know existsAlmost always a mode or sandbox mismatch. Check Flint-Mode and Flint-Sandbox-Id on the response, and let the timeline's wrong-environment remediation confirm it.
Duplicate or missing state changePull the resource timeline: every request and event that touched the resource, in order.

Debug From the Dashboard#

Everything on this page is also available point-and-click in the dashboard under Developers: request logs with the same filters, webhook events with per-attempt delivery results and a resend button, and endpoint management. The API and the dashboard read the same data, so use whichever is closer to hand.

Next Steps#

Rate this doc