Navigation

Scraping Run Feedback

Submit client-side feedback on a scraping run’s output. Feedback drives the agent self-improvement loop and per-biller observability.

Related guide: Submitting Scraping Feedback · Error catalog: Feedback errors

Per-(client × biller) rate limit. 100 submissions per hour per (client_id, biller_id) tuple. Excess returns 429 FEEDBACK_RATE_LIMITED.

The Feedback object

Response attributes

FieldTypeDescription
agent_type*stringEchoes `recording_agent` (the single agent type this endpoint covers today).
run_id*stringEchoes the path parameter.
client_id*stringYour client_id (echoed for correlation).
biller_id*stringEchoes the biller_id from the request body.
signal*stringEchoes the submitted signal.
by_user_idstringEchoes the submitted by_user_id (if provided).
notesstringEchoes the submitted notes (if provided).
submitted_at*string (ISO 8601)The server timestamp of the original submission. On idempotent replay this is the ORIGINAL timestamp, not the replay timestamp.
idempotency_key*stringEchoes the Idempotency-Key header from the request.
already_existed*booleanTrue when this exact submission was already recorded (idempotent replay). False when this submission caused a new row to be written.
Example Feedback response
JSON
{
  "agent_type": "recording_agent",
  "run_id": "run_abc123",
  "client_id": "7ae2cae3-4587-4750-941b-13a6696289a8",
  "biller_id": "biller_verizon",
  "signal": "data_incorrect",
  "by_user_id": "user_42",
  "notes": "Bill shows $0 but my paper bill says $84.32. Acct ending 1234.",
  "submitted_at": "2026-05-30T18:00:00Z",
  "idempotency_key": "idem_2026-05-30T18:00:00Z_run_abc123",
  "already_existed": false
}
POST/v1/scraping-runs/:run_id/feedback

Submit feedback on a scraping run. The :run_id path parameter matches the run identifier returned by the scraping flow. Idempotent: send the same Idempotency-Key within 24 hours to retry safely.

Request body (application/json)

FieldTypeDescription
signal*string (enum)One of: `succeeded`, `login_failed`, `bill_missing`, `data_incorrect`. The subset of the implicit signal vocabulary that clients can meaningfully observe (system-only signals like `timeout`, `two_factor_required`, `extraction_empty` are not in this enum).
biller_id*stringBiller identifier the scraping run targeted. Drives per-biller feedback aggregation + per-(client × biller) rate limit keying. Max 128 chars.
by_user_idstringThe end-user identifier within your domain who saw the result. Optional — clients without a user concept can omit it. Useful for audit + per-user dedup downstream. Max 256 chars.
notesstringFree-text notes. Never echoed back in metrics; PII-safe storage required by client. Max 4096 chars.

Returns: The feedback object on 201 Created with already_existed: false. On idempotent replay, returns the original payload (same submitted_at) with already_existed: true.

curl -X POST "https://sandbox.api.billerapi.com/v1/scraping-runs/run_abc123/feedback" \
  -H "X-Client-ID: your_client_id" \
  -H "X-Client-Secret: bb_test_xxxxxxxx" \
  -H "Idempotency-Key: idem_$(date +%s)_run_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "signal": "data_incorrect",
    "biller_id": "biller_verizon",
    "by_user_id": "user_42",
    "notes": "Bill shows $0 but my paper bill says $84.32."
  }'

Idempotency semantics

This endpoint follows the standard BillerAPI idempotency contract. The Idempotency-Key header is required (not just recommended) so retries don’t double-count.

  • Replay — same Idempotency-Key + identical body within 24h: returns the original payload with already_existed: true and the original submitted_at. Safe to retry on network failure.
  • Already submitted (different key, same run) — a different Idempotency-Key submitting for the same (client_id, run_id) returns 409 FEEDBACK_ALREADY_SUBMITTED. Each scraping run accepts exactly one feedback submission.

Rate-limit headers

Every successful response includes standard rate-limit headers. The limit is per (client_id, biller_id) tuple — separate billers consume separate quotas.

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1748722800