XpectraFlow docs
Console guides

Errors and debugging

Every error code, what it actually means, and what to send us when you are stuck.

The envelope

Every failure has the same shape:

{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key lacks the datasets:write scope.",
    "details": [],
    "request_id": "31592257-1558-48ed-8328-a5086ccfa4ed"
  }
}

Branch on code. It is stable for a given condition. message is prose for a human reading logs and may be reworded.

Codes

StatuscodeMeansFirst thing to check
400invalid_requestMalformed body or parametersdetails — it names the field
401unauthorizedMissing, malformed, expired or revoked keyIs x-api-key set? Is the key live?
403insufficient_scopeAuthenticated, but the key lacks the scopeThe message names the scope
403forbiddenScoped, but still not allowedRare. Usually an ownership rule
404not_foundNo such resource — or it is not yoursThe id, and the key's workspace
409conflictA state conflictThe message says which state
413payload_too_largeBody exceeded the limitBatch size
429rate_limitedToo many attemptsRetry-After
500internal_errorOur faultSend us the request_id

The three that confuse people

Validation details

On a 400, details carries per-field messages:

"details": [
  { "field": "channels.0.name", "message": "String must contain at least 1 character(s)" },
  { "field": "rows.12.values",  "message": "Expected 3 values, received 2" }
]

The path is into the body you sent, so rows.12.values is the thirteenth row.

Request ids

Send X-Request-Id and it is echoed back and used in our logs. Send nothing and one is generated. Either way it appears on every response — success as well as failure — and inside every error body.

curl -si https://app.xpectraflow.com/api/experiments/list \
  -H "x-api-key: $XPECTRA_API_KEY" \
  -H "X-Request-Id: nightly-sync-42"

Using your own ids — a job name, a run number — makes your logs and ours line up without correlating timestamps.

Debugging a webhook that never arrives

Covered step by step on Lifecycle and retries. The short version: check the command's status first, then GET /api/webhooks/deliveries?commandId=…, and remember that a 4xx from your receiver is final.

Debugging a signature that will not verify

In order of likelihood:

You are signing a re-serialised body. The signature covers the raw request bytes. If your framework parsed the JSON and you re-encoded it, key order and number formatting differ. Use the raw body.

You stripped whsec_ before keying. The key is the whole secret string, prefix included.

You are only checking the first v1= value. During a rotation two are sent, and either may be the one you know.

Your clock is off. The timestamp is inside the signed string and must be within ±300 seconds.

What to send us

  • The request_id
  • The code and the full error body
  • The endpoint and roughly when
  • The key's keyPrefix — never the key itself

With a request_id we can find the exact request. Without one, we are correlating timestamps.

On this page