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/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
{
"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:
| Identifier | Looks like | What it names |
|---|---|---|
request_id | bce56cba-0827-... | One HTTP exchange. Returned in the X-Request-Id header, the success body, and error.request_id. |
api_request_log_id | rlog_01KWM6WAHJ... | The stored log record for that exchange. Pass it to the detail endpoint. |
correlation_id | rlog_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:
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.
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.
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.
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#
/v1/developer/request-logsList request logs for the API key making the call. Requires developer.logs.self.read.
Start with the requests that failed:
curl "https://api.withflintpay.com/v1/developer/request-logs?status_bucket=client_error&page_size=5" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"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#
| Parameter | What it does |
|---|---|
status_bucket | all (default), success, client_error (4xx), or server_error (5xx). |
request_id | Exact match on the id from a response header or error body. |
http_method | Filter by method, such as POST. |
path_query | Substring match on the path, such as /v1/orders. |
resource_type, resource_id | The resource a request touched, such as order and ord_..., even when the id is buried in the body. |
created_after, created_before | RFC 3339 bounds, such as 2026-07-03T00:00:00Z. |
page_size, page_token | Cursor 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#
/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:
curl "https://api.withflintpay.com/v1/developer/request-logs/rlog_1kmn0aExample" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"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:
Authorizationand 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_reproducebooleantrue only for read requests (GET, HEAD, OPTIONS). Anything that writes is marked unsafe by default because replaying it may mutate data.
reproduction_completebooleanfalse when redaction removed something the original request contained. The replay may need you to fill a value back in.
recommended_environmentstringAlways 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#
/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:
curl "https://api.withflintpay.com/v1/developer/resource-timelines/ord_1kmn0aExample" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"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#
| Parameter | What it does |
|---|---|
include | Comma-separated entry types: requests, webhooks, attempts. Defaults to all three. attempts requires webhooks. |
resource_type | Optional check that the id is the type you expect; mismatches return 400 INVALID_RESOURCE_TYPE. |
occurred_after, occurred_before | RFC 3339 bounds on entry time. Note the name: timelines use occurred_*, not created_*. |
page_size, page_token | Cursor 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, including403scope 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, not403, 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#
| Scope | Grants |
|---|---|
developer.logs.self.read | Listing request log summaries. |
developer.logs.self.detail.read | Full request detail, including the reproduction. Not implied by the list scope. |
developer.resource_timelines.read | Resource 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#
| Symptom | Start here |
|---|---|
401 | Not in your logs; the request never authenticated. Read the error body and the Authentication error table. |
403 | The log detail and error message name the missing scope. Grant it or use a different key. |
400 validation error | error.param names the field; the log detail's request_body shows what you actually sent. |
409 conflict | Usually an idempotency replay or reuse; check the Idempotency-Replayed header and see Idempotency. |
429 | Honor the Retry-After header; see Rate Limits. |
| Webhook never arrived | The 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 exists | Almost 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 change | Pull 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#
- Error Handling: the error envelope, error types, and retry strategy.
- Webhooks: endpoints, signatures, delivery retries, and resending events.
- Idempotency: safe retries and what replays return.
- Rate Limits: request budgets and backoff.
- Authentication: 401 versus 403, and every auth error code.
- Sandboxes & Test Mode: environments, sandbox-bound keys, and test data.
- API request logs reference: the reference entry for this surface.
