API reference
feedback
/v1/feedbackReport an outcome labeled by eventId
Feedback is asynchronous: it lives on the outcome path, not the request path. Request path: verify → allow / challenge / deny. Outcome path: account + eventId → outcome → feedback. Protection does not need to know synchronously whether the action later burned credits, converted, or became abusive.
Security invariant: feedback cannot create attribution. It can only attach an outcome to an existing verify eventId for your tenant. Join only on that stored id; never invent a join from email, IP, or other subject fields. Unknown or cross-tenant event ids return 404 unknown_event_id (the invariant working, not a soft miss).
Append-only, not latest-wins: multiple outcomes may attach to one eventId. Exact duplicate bodies share one feedbackId. Compatible labels coexist (e.g. credit_burn + converted). Conflicting pairs quarantine the new observation (audit retained, excluded from effective ledger totals) without deleting prior evidence. Example: converted then abuse_confirmed leaves converted effective and quarantines abuse_confirmed. HTTP { ok: true } still acknowledges quarantined ingest; do not read 200 as "sole effective label."
Prefer fast labels (credit_burn, multi_account_cluster, abuse_confirmed) so the pilot can prove value. Outcomes are labeled feedback for attribution (join on eventId), evaluation (Agent Farming Ledger, weekly exports, drift, backtests), and scoring calibration (weekly human-in-the-loop decisionCosts / threshold changes). They are not a payments product, and the pilot does not automatically retrain a model from these labels on the verify path.
`value` / `unit`: value is the measured economic magnitude for this outcome, expressed in unit. Not intrinsically dollars. unit is an allowlist today: usd | credits | count (default usd when value is set). Requires value. Unknown units return 400. Magnitudes with different units are not converted or comparable; weekly ledger dollar totals only sum credit_burn where unit is usd. Per-outcome convention: for converted, when value is set it is realized first-year ARR at conversion (12 × MRR or annual contract amount), not expected ARR, multi-year TCV, or LTV. Pick one definition per tenant and stick to it. Optional source is label provenance (billing, usage_metering, trust_queue, manual_review, customer_system, automated_detector) and is included in warehouse identity when set.
`false_positive` meaning: a previously attributed abuse signal on this eventId was reviewed and determined not to represent abuse (T&S clear / overturn). It conflicts with abuse-positive labels (abuse_confirmed, multi_account_cluster, credit_burn, chargeback). It is not "Chitmark challenged or denied a legitimate user": that cost trade-off is context.decisionCosts.falseChallenge / falseDeny on verify. It is not a generic "external classifier was wrong" unless the correction is about abuse attribution joined on this eventId. Misusing it contaminates labeled feedback.
Idempotency-Key (or body idempotencyKey) dedupes exact retries. If both are sent, the header takes precedence. Same key + same payload fingerprint → same acknowledgement; same key + different fingerprint → 409 idempotency_conflict (generate a new key). Keys do not create observations: warehouse feedbackId ignores the key. Same eventId + same outcome with different keys and an identical body collapse to one feedbackId; a different value, observedAt, or source is a second append-only observation.
Without an idempotency key, exact duplicate bodies still share one warehouse feedbackId and never double-count value. Same body means the same eventId, outcome, value (or omitted), unit (default usd), and normalized observedAt. Different observedAt values are separate observations. Omitting observedAt uses server receipt time, so bare retries are not duplicates unless those fields match exactly.
Headers
Bearer API key
application/json
Dedupes exact retries. If this header and body idempotencyKey are both sent, the header takes precedence. 409 when the key is already tied to a different fingerprint (eventId/outcome/value/unit/source/observedAt): create a new key for the new request.
Request body
From a prior verify for this tenant. Only join key: feedback cannot create attribution; unknown/cross-tenant → 404
Business label for this event. false_positive = prior abuse signal reviewed and cleared (not verify falseChallenge/falseDeny). See Outcome enum in OpenAPI / SDK.md.
Measured economic magnitude for this outcome, expressed in unit. Not intrinsically dollars. Cap 1_000_000. For converted: realized first-year ARR at conversion (not LTV / expected ARR). Omit when the label has no magnitude.
usd | credits | count (default usd when value is set). Allowlist only, not free-form. Requires value. Different units are not comparable; ledger $ totals only sum credit_burn with unit: usd.
Optional provenance: billing | usage_metering | trust_queue | manual_review | customer_system | automated_detector. Included in warehouse identity when set.
When the outcome was observed (ISO-8601). Part of warehouse identity: different timestamps are separate observations. If omitted, server receipt time is used (retries a moment later are not duplicates).
Body form of Idempotency-Key; header takes precedence. 409 when the key is already associated with a different fingerprint. Prefer this for connector retries even when bodies match.
Response
Acknowledgement
Echo of join key
Notes
- 400 bad request, 401 unauthorized, 403 missing
feedback:write, 404 unknown eventId, 409 idempotency conflict, 413 payload too large, 429 rate limited, and 503 ledger unavailable return the Error schema. - Exact retries with the same Idempotency-Key and fingerprint replay the original acknowledgement.
409 idempotency_conflict: key already associated with a different fingerprint; do not reuse; generate a new key for the new request. - Without a key, warehouse identity is tenantId + eventId + outcome + value + unit + source (or null) + observedAt. Different observedAt or source values are separate observations; omitted observedAt uses server receipt time. Different Idempotency-Key values do not create observations by themselves: identical bodies still share one feedbackId.
curl -s https://api.chitmark.com/v1/feedback \
-H "Authorization: Bearer $CHITMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "eventId": "evt_2f9c…", "outcome": "credit_burn", "value": 42.5, "unit": "usd", "observedAt": "2026-03-15T12:00:00Z", "idempotencyKey": "fb:evt_2f9c:credit_burn"}'{
"ok": true,
"eventId": "evt_2f9c…"
}