Scores
Read the deterministic score and its explanation.
get
/api/v1/users
public
List YOUR OWN subjects (query + export your book of business)
Lists the subjects the **calling platform itself** has reported at least one event for (your book of business), each with its current score + band, verification depth, your event counts, last-activity and subject type. This is the query surface a data platform runs its operation on, as opposed to a single-id lookup or a caller-supplied id list (`POST /users/scores`).
**Strictly your own subjects.** A `User` row is global (the same person can be a counterparty of many platforms), so "your book" is defined by YOUR ledger: results are scoped, unconditionally, to subjects you have events for; you can never see, or guess your way to, another platform's subjects. Test/live isolation is enforced the same way (a test key lists only its test universe, a live key only live data; test-namespace prefixes are stripped from responses).
**Filtering** is a closed, validated set: deliberately no free-form query language and no GraphQL, because the closed set is exactly what lets the tenant scope be guaranteed for every combination: `scoreMin`/`scoreMax`, `band`, `hasScore`, `scoreFrozen`, `subjectType`, `activeSince` (your events at/after an instant), `registeredSince`/`registeredBefore`, `hasVerifiedEvents`, `minVerifiedEvents`. **Sorting**: `sort` = `score` (default, desc) | `registered` | `externalId`, with `order`. (`lastActivity` is accepted for forward-compatibility; last-activity is exposed as a field and via `activeSince`; see note.) `eventCount`/`verifiedEventCount`/`lastActivityAt` are scoped to YOUR events, never a cross-platform total.
**Subjects with no score yet** come back with `finalScore: null` and `scoreBand: null`, never a placeholder number. They are excluded from any `scoreMin`/`scoreMax`/`band` filter (a null satisfies no comparison); use `hasScore=false` to list exactly those.
Default `format=json` is cursor-paginated (`limit` 1–1000, default 50). `format=csv` streams the whole book, paginated server-side up to 50000 rows (`BOOK_EXPORT_MAX_ROWS`; advertised in `X-Export-Max-Rows`) with an explicit `# TRUNCATED` trailer, never a silent cut. A CSV export is metered in PROPORTION to the rows returned (so it can't drain the book more cheaply than paging JSON), not as one flat call.
Read-only under `scores:read`; nothing here computes or writes a score.
Parameters
| scoreMin (query) |
number: Only subjects with `lastValidScore` ≥ this (0–100). |
| scoreMax (query) |
number: Only subjects with `lastValidScore` ≤ this (0–100). |
| band (query) |
Excellent | Good | Fair | Building | Unproven | High Risk: Only subjects in this reliability band (equivalent to the band's score range). |
| hasScore (query) |
boolean: true = only subjects whose score has been computed; false = only those still awaiting a first score. Cannot be combined with `scoreMin`/`scoreMax`/`band` (contradictory: 400). |
| scoreFrozen (query) |
boolean: true = only subjects whose score is frozen by a velocity flag; false = only unfrozen. |
| subjectType (query) |
PERSON | AGENT | ORGANIZATION: Filter to people, agents or organizations. |
| activeSince (query) |
string: Only subjects with ≥1 of YOUR events at/after this ISO instant. |
| registeredSince (query) |
string: Only subjects that first appeared in the ledger at/after this ISO instant (inclusive). |
| registeredBefore (query) |
string: Only subjects that first appeared in the ledger BEFORE this ISO instant (exclusive). |
| hasVerifiedEvents (query) |
boolean: true = only subjects with ≥1 of your VERIFIED events; false = only those with none. |
| minVerifiedEvents (query) |
integer: Only subjects with at least this many of YOUR verified events. |
| sort (query) |
score | lastActivity | registered | externalId: Sort key. Cursor-stable keys are score/registered/externalId; `lastActivity` is accepted for forward-compatibility. |
| order (query) |
asc | desc: Sort direction (defaults: desc for score/lastActivity, asc otherwise). |
| format (query) |
json | csv: 'json' (default, cursor-paginated) or 'csv' (whole-book export, row-capped with an explicit truncation marker). |
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
| limit (query) |
integer: JSON page size (1–1000, default 50). Ignored for CSV. |
Responses
| 200 | A page of your subjects (JSON by default; CSV stream when format=csv) · object |
| 400 | Invalid query (BOOK_QUERY_INVALID / BOOK_FORMAT_INVALID) · Error |
| 401 | Missing/invalid API key · Error |
| 403 | API key lacks scores:read · Error |
Example
curl https://api.credda.io/api/v1/users
get
/api/v1/users/summary
public
Size and shape a segment of your book (counts, not rows)
Answers *how big is this segment, and what does it look like?* over the **same closed filter set** as `GET /api/v1/users`, so you can size a segment before paging or exporting it, and render "1,284 subjects, median 61.4, 18% Excellent" in one call instead of walking every page.
Takes every filter `GET /api/v1/users` takes (`scoreMin`/`scoreMax`, `band`, `hasScore`, `scoreFrozen`, `subjectType`, `activeSince`, `registeredSince`/`registeredBefore`, `hasVerifiedEvents`, `minVerifiedEvents`) and returns counts + a band histogram + median/mean instead of rows. `sort`/`order`/`limit`/`cursor`/`format` are accepted and validated but have no meaning here.
**Same isolation, same code path.** The summary is built from the identical tenant-scoped `where` the listing uses, so it can never count a subject the listing would not show you; test/live isolation rides along.
**Nothing is faked.** `central.median`/`central.mean` are `null` when no subject in the segment is scored (a 0 there would read as a real, catastrophic score), and if the matched population exceeds the in-memory fold cap the exact `matched` count is still returned while the distribution comes back `null` with `aggregationSkipped` stating why; a partial aggregate is never presented as a whole-segment one.
Read-only under `scores:read`; nothing here computes or writes a score. These are counts over your own records, never a verdict on any subject.
Parameters
| scoreMin (query) |
number |
| scoreMax (query) |
number |
| band (query) |
Excellent | Good | Fair | Building | Unproven | High Risk |
| hasScore (query) |
boolean |
| scoreFrozen (query) |
boolean |
| subjectType (query) |
PERSON | AGENT | ORGANIZATION |
| activeSince (query) |
string |
| registeredSince (query) |
string |
| registeredBefore (query) |
string |
| hasVerifiedEvents (query) |
boolean |
| minVerifiedEvents (query) |
integer |
Responses
| 200 | Counts and score shape for the matched segment · BookSummary |
| 400 | Invalid query (BOOK_QUERY_INVALID / BOOK_FORMAT_INVALID) · Error |
| 401 | Missing/invalid API key · Error |
| 403 | API key lacks scores:read · Error |
Example
curl https://api.credda.io/api/v1/users/summary
post
/api/v1/users/scores
public
Batch score read (up to 100 users in one call)
Request body
Responses
| 200 | Scores · object |
| 400 | Invalid batch request · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/scores \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/users/{id}/score
public
Current score
Parameters
| id (path) required |
string |
Responses
| 200 | Score · ScoreResult |
| 404 | No score yet · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/score
get
/api/v1/users/{id}/score/history
public
Score snapshot history
Parameters
| id (path) required |
string |
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
Responses
Example
curl https://api.credda.io/api/v1/users/{id}/score/history
get
/api/v1/users/{id}/score/explain
public
Plain-language score breakdown (with adverse-action reason codes)
A per-factor breakdown of the current score in plain language. Additively includes a `reasonCodes` object: deterministic, ranked ECOA / Regulation B **reason codes** for the record: `adverseActionReasons` (ranked most-significant-first, importance-weighted) and `supportingFactors`, each a stable code from `GET /api/v1/reason-codes` with a consumer-facing description and the specific evidence behind it. A B2B2C partner draws its statement of specific reasons from `adverseActionReasons` (top `keyFactorLimit`). Every code is reproducible from the ledger; `reasonCodesVersion` pins their meaning. **Credda supplies the attribution only; it takes no action and issues no notice; see `reasonCodes.disclosures`.**
Parameters
| id (path) required |
string |
Responses
Example
curl https://api.credda.io/api/v1/users/{id}/score/explain
get
/api/v1/users/{id}/reliability
public
Reliability at dispatch: the compact hot-path record read
The one small read a staffing/marketplace platform makes before assigning a shift: current score, band and confidence, whether the score is frozen, verified-evidence counts, `noShowRate` (the breach-type share of the outcome record), the engine's on-time component, days since the last event, and the top ranked drivers, under ~1KB.
**Read-only.** Every value is either already stored on the latest `ScoreSnapshot` or a plain count over the append-only ledger. Nothing here computes or writes a score (`calculateCreddaScore` remains the sole snapshot writer) and a subject the engine has never scored reads `null` rather than triggering a computation. Absent data is always `null`, never a fake `0`.
**Evidence, never a verdict.** No field says call / don't-call, fit / unfit, or approve / deny. Credda reports the record; the platform applies its own criteria and owns the dispatch decision. **If you use this read to SELECT workers, FCRA (or local equivalents) may attach to that decision; scope it with your counsel.** The `band` labels are the factual reliability bands the deterministic formula produces.
`context` defaults to `dispatch`, the only context served today; an unrecognised value is a 400 (`DISPATCH_CONTEXT_INVALID`) rather than a silently different projection.
Parameters
| id (path) required |
string |
| context (query) |
dispatch: Which compact projection to serve. Only `dispatch` today. |
Responses
| 200 | Compact reliability read · object |
| 400 | Unknown context (DISPATCH_CONTEXT_INVALID) · Error |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/reliability
get
/api/v1/users/{id}/trust-summary
public
Evidence-based trust explanation (summary, strengths, risks)
Deterministic answer to "why is this score what it is": a one-sentence summary plus strengths and risks, every claim derived from ledger facts (completion/on-time rates, verified-evidence confidence, dispute record, platform diversity, recency, advisory risk signals). Deliberately contains NO verdict or recommendation: Credda explains evidence; it never decides hire/approve. `?narrative=1` additionally attaches an advisory AI retelling of the same facts when the AI subsystem is enabled (inert by default).
Parameters
| id (path) required |
string |
| narrative (query) |
1: Set to 1 to request the optional advisory AI narrative. |
Responses
| 200 | Trust explanation · object |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/trust-summary
get
/api/v1/users/{id}/score/delta
public
Factor-level explanation of the last score change
Parameters
| id (path) required |
string |
Responses
| 200 | Score delta · object |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/score/delta
get
/api/v1/users/{id}/score/components
public
Modular score components (Reliability, Timeliness, Trustworthiness, Verification Confidence, Consistency, Momentum)
Reframes the score as named, independently 0–100-scored components instead of one number. Pure relabeling of the same data /score/explain exposes: no new formula, cannot regress finalScore.
Parameters
| id (path) required |
string |
Responses
| 200 | Score components · object |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/score/components
get
/api/v1/users/{id}/timeline
public
Unified chronological feed of events + score changes
Merges the Event ledger and ScoreSnapshot history into one cursor-paginated, newest-first feed. Read-only: a view, not a new source of truth.
Parameters
| id (path) required |
string |
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
Responses
| 200 | Timeline · object |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/timeline
post
/api/v1/users/{id}/score/project
public
What-if score projection (read-only, never writes a snapshot)
Layer 1–20 hypothetical events on the user's current state and get the resulting score, deterministically, without writing anything. If the user id does not exist yet, the projection runs from a blank, unproven baseline, so you can simulate a trust trajectory "from zero" (e.g. what 10 verified deliveries would produce) before reporting a single event. `eventType` accepts the FULL event vocabulary, not just the platform-ingestable subset, including `CONTRACT_BREACHED` (the strongest negative signal) and the dispute outcomes the API writes itself, so the worst case is modellable without writing anything.
Parameters
| id (path) required |
string |
Request body
Responses
| 200 | Projection · object |
| 400 | Invalid projection request · Error |
| 404 | User not found · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/score/project \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/users/{id}/documents/analyze
public
Résumé / document advisory: who confirms it, and how it affects the score (writes nothing)
Given the STRUCTURED claims a résumé or work-history document describes, advises (per claim): **who the third-party witness is** (the counterparty who can confirm it), **whether it counts as verified as submitted** (only if it already names a witness), and (read-only) **how adding the claims moves the score** in two scenarios: as-you'd-submit-now vs. if every claim is confirmed (via the read-only projection). **Writes nothing**: no event, no snapshot, no `isVerified`. A claim becomes verified evidence only when its named witness actually confirms it (a Credda confirmation or reference request), never by calling this. Renders no verdict on the person. The deterministic API takes STRUCTURED claims only; turning a raw document into structured claims is the advisory-AI onboarding flow's job. `scores` scope.
Parameters
| id (path) required |
string |
Request body
Responses
| 200 | Per-claim advice + a two-scenario score projection + witness guide · object |
| 400 | Invalid request (provide 1–50 structured claims) · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/documents/analyze \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/users/{id}/risk
public
Advisory anti-gaming risk signals (never affects the score)
Parameters
| id (path) required |
string |
Responses
Example
curl https://api.credda.io/api/v1/users/{id}/risk
get
/api/v1/usage
public
Your API usage vs. tier quota
Per-day request counts (by status class) for the authenticated platform, the current tier rate limit, and `endpoints`: the busiest route patterns over the window (top 10, descending). Window: either a trailing `days` window (default 7, max 400) OR an explicit inclusive `from`/`to` date range for monthly statements, mutually exclusive (400 `USAGE_WINDOW_CONFLICT` if both are given). Live counters are metered in Redis (90-day retention); completed days are rolled up durably, so statements reach back up to 400 days; a range beyond that is clamped (the response reports the effective window and `truncated: true`). Days with no data in either store report zeros. `format=csv` returns the same statement as a flat `text/csv` attachment (`row` column discriminates day / total / endpoint rows) named `credda-usage-<from>-<to>.csv`; default remains JSON.
Parameters
| days (query) |
integer: Trailing window in days (default 7, max 400). Mutually exclusive with `from`/`to`. |
| from (query) |
string: Inclusive range start (ISO date, YYYY-MM-DD). Requires `to`; clamped to the 400-day history window (completed days beyond the 90-day live retention are served from durable daily rollups). |
| to (query) |
string: Inclusive range end (ISO date, YYYY-MM-DD). Requires `from`. |
| format (query) |
json | csv: Response format: 'json' (default) or 'csv' (flat statement, `Content-Disposition: attachment`). |
Responses
| 200 | Usage summary (JSON by default; CSV statement when format=csv) · object |
| 400 | Invalid window (USAGE_WINDOW_CONFLICT / USAGE_RANGE_INVALID / USAGE_FORMAT_INVALID) · Error |
Example
curl https://api.credda.io/api/v1/usage
get
/api/v1/usage/quota
public
Quota transparency: remaining monthly calls + reset
The one cheap read to poll "how many calls do I have left this month, and when does it reset?" without pulling the full per-day usage breakdown. Read-only: it never counts against your quota, and mirrors the exact numbers the enforcement path uses, so a client can throttle itself before it ever sees a 429. `unlimited: true` (`cap: null`) means the tier has no monthly quota configured. `secondsUntilReset` matches the `Retry-After` header sent on a 429.
Responses
Example
curl https://api.credda.io/api/v1/usage/quota
get
/api/v1/usage/meters
public
Usage as metered-billing meters (Stripe Billing Meters shape)
The BILLING view of usage: per-metered-dimension request totals over the window, ready to push to a metered-billing system. A pure reprojection of the SAME usage counters `GET /usage` serves: `dimension: total` (the primary billable quantity, value `all`), `dimension: status_class` (ok / clientError / serverError), and `dimension: endpoint` (per route pattern, mirroring the `/usage` busiest-endpoint list and its top-N bound). Same window controls as `/usage` (`days` OR `from`/`to`, mutually exclusive, clamped to retention) and `format=json|csv` (RFC-4180 `Content-Disposition` attachment). Requires the `usage` scope. Read-only: Credda emits usage; the biller prices it; no score is read or written.
Parameters
| days (query) |
integer: Trailing window in days (default 7, max 400). Mutually exclusive with `from`/`to`. |
| from (query) |
string: Inclusive range start (ISO date, YYYY-MM-DD). Requires `to`; clamped to the 400-day history window (completed days beyond the 90-day live retention are served from durable daily rollups). |
| to (query) |
string: Inclusive range end (ISO date, YYYY-MM-DD). Requires `from`. |
| format (query) |
json | csv: Response format: 'json' (default) or 'csv' (flat statement, `Content-Disposition: attachment`). |
Responses
| 200 | Meter rows (JSON by default; CSV attachment when format=csv) · object |
| 400 | Invalid window (USAGE_WINDOW_CONFLICT / USAGE_RANGE_INVALID / USAGE_FORMAT_INVALID) · Error |
Example
curl https://api.credda.io/api/v1/usage/meters
get
/api/v1/scoring/model
public
The scoring model, as data
Weights, the unproven-user anchor, the confidence thresholds, the recency decay rate and the provisional-activity band (the score range unverified activity can reach; verification is the only path beyond it), all read directly from the formula constants rather than restated, so it cannot drift from the engine it describes. Public and unauthenticated: a caller deciding whether to trust a score should be able to read how it is computed, and check a score against the model themselves, without holding a key.
Responses
| 200 | Scoring model · object |
Example
curl https://api.credda.io/api/v1/scoring/model
Analytics
Tenant-scoped time-series aggregates over the caller's OWN data: event volume by day and type with the verified share, and the caller's subjects' score band mix, central tendency and movement over a window. Aggregate-only, read-only (scores:read), test/live isolated; nothing here computes or writes a score, and the numbers are distribution facts, never a verdict on any subject.
get
/api/v1/analytics/events
public
Event volume time-series (by day + by type + verified & confirmed share)
Tenant-scoped aggregates over the CALLER's OWN event ledger: `daily` (one row per UTC day, gaps filled with zeros: total, verified, confirmed, verifiedShare, confirmedShare), `byType` (per event type, busiest first), and window `totals` with the overall shares. **`verified` vs `confirmed`:** `verified` counts the raw `isVerified` flag, which on a directly-reported event is your OWN assertion; `confirmed` counts only events written because a DISTINCT counterparty acted on a one-time confirmation token (and were not downgraded as self-attested). `confirmed` is always a subset of `verified`; it is the counterparty-confirmed evidence density of your integration. Window: a trailing `days` window (default 30, max `ANALYTICS_MAX_DAYS`=365) OR an explicit inclusive `from`/`to` range, mutually exclusive (400 `ANALYTICS_WINDOW_CONFLICT`). Aggregate-only: it returns no subject identifiers, only counts. Requires `scores:read`; test/live isolated. Read-only; nothing here computes or writes a score.
Parameters
| days (query) |
integer: Trailing window in days (default 30, max 365). Mutually exclusive with `from`/`to`. |
| from (query) |
string: Inclusive range start (ISO date, YYYY-MM-DD). Requires `to`. |
| to (query) |
string: Inclusive range end (ISO date, YYYY-MM-DD). Requires `from`. |
Responses
| 200 | Event analytics · object |
| 400 | Invalid window (ANALYTICS_WINDOW_CONFLICT / ANALYTICS_RANGE_INVALID) · Error |
Example
curl https://api.credda.io/api/v1/analytics/events
get
/api/v1/analytics/scores
public
Score distribution + movement across your subjects
Tenant-scoped aggregates over the CALLER's subjects (those you have reported at least one event for): `scoredSubjects` count, `central` (median + mean of current scores), `bandDistribution` (current-score band histogram, highest first, with share), and `movement`: how many scores rose / fell / stayed level between consecutive recomputations WITHIN the window, plus the number of subjects that moved or were recomputed. Same window controls as `/analytics/events`. Aggregate-only: no subject identifiers, only counts and distribution facts, never a verdict. Requires `scores:read`; test/live isolated. Read-only; `calculateCreddaScore` stays the sole snapshot writer.
Parameters
| days (query) |
integer: Trailing window in days (default 30, max 365). Mutually exclusive with `from`/`to`. |
| from (query) |
string: Inclusive range start (ISO date, YYYY-MM-DD). Requires `to`. |
| to (query) |
string: Inclusive range end (ISO date, YYYY-MM-DD). Requires `from`. |
Responses
| 200 | Score analytics · object |
| 400 | Invalid window (ANALYTICS_WINDOW_CONFLICT / ANALYTICS_RANGE_INVALID) · Error |
Example
curl https://api.credda.io/api/v1/analytics/scores
Webhooks
Subscribe to signed trust events (advisory, never affects a score).
get
/api/v1/events/stream
public
Real-time event stream (Server-Sent Events)
Holds one long-lived HTTP connection and streams the SAME event envelopes Credda POSTs to your webhooks (score.updated, score.band_changed, dispute.resolved, monitor.triggered, policy.threshold_crossed, usage.quota_warning, import.completed, screening.completed), in real time, so you can drop a webhook receiver or stop busy-polling. The response is `text/event-stream` (SSE): each event is an `id:` line (the event id; the browser echoes it as `Last-Event-ID` on reconnect), an `event:` line (the type), and a single-line JSON `data:` field carrying the delivery envelope `{id, type, livemode, createdAt, data}`, terminated by a blank line. A `:` comment heartbeat is sent periodically to keep the connection alive. Optional `?types=score.updated,monitor.triggered` restricts the stream to those event types (an unknown type is a 400, same as GET /webhooks/deliveries). Mode isolation: a test key (crd_test_) streams only `livemode:false` envelopes, a live key only live ones. Resume is best-effort: send `Last-Event-ID` (header, or `?lastEventId=`) to replay buffered events after that id (at-most-once past the short buffer window). Gated under the EXISTING `webhooks` scope: a stream is webhook-notification config, not a new scope resource. NOTIFICATION ONLY: this endpoint reads and writes NOTHING score-side.
Parameters
| types (query) |
string: Comma-separated event types to include (default: all). An unknown type is a 400. |
| lastEventId (query) |
string: Resume cursor: replay buffered events after this id (best-effort). Prefer the `Last-Event-ID` header, which browsers set automatically on reconnect. |
| Last-Event-ID (header) |
string: Resume cursor set automatically by browser EventSource on reconnect. Same effect as `?lastEventId=`. |
Responses
| 200 | An open Server-Sent Events stream. Content-Type is text/event-stream; the body is an unbounded sequence of SSE records, one per event, plus periodic `:` heartbeat comments. Not a JSON body. |
| 400 | Unknown eventType in ?types · Error |
| 401 | Missing/invalid API key · Error |
| 403 | API key lacks the webhooks scope · Error |
Example
curl https://api.credda.io/api/v1/events/stream
get
/api/v1/webhooks
public
List your webhooks
Responses
Example
curl https://api.credda.io/api/v1/webhooks
post
/api/v1/webhooks
public
Subscribe an HTTPS endpoint (secret returned once)
Request body
Responses
| 201 | Created · object |
| 400 | Invalid url/events · Error |
Example
curl -X POST https://api.credda.io/api/v1/webhooks \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/webhooks/{id}
public
Fetch one webhook
Parameters
| id (path) required |
string |
Responses
| 200 | Webhook · object |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/webhooks/{id}
patch
/api/v1/webhooks/{id}
public
Update url/events/description/isActive
Parameters
| id (path) required |
string |
Request body
Responses
Example
curl -X PATCH https://api.credda.io/api/v1/webhooks/{id} \
-H "Content-Type: application/json" \
-d '{ ... }'
delete
/api/v1/webhooks/{id}
public
Delete a webhook
Parameters
| id (path) required |
string |
Responses
Example
curl -X DELETE https://api.credda.io/api/v1/webhooks/{id}
post
/api/v1/webhooks/{id}/test
public
Send a synthetic signed delivery
Parameters
| id (path) required |
string |
Responses
| 200 | Delivery result · object |
Example
curl -X POST https://api.credda.io/api/v1/webhooks/{id}/test
get
/api/v1/webhooks/deliveries
public
Recent outbound events across ALL your endpoints (sample data)
The "perform-list" source automation platforms (Zapier, Make, n8n) need to render sample data BEFORE a trigger has ever fired. Returns your most recent outbound events across every webhook endpoint you own, newest first, cursor-paginated; each item is the delivery envelope exactly as sent (`id`, `type`, `livemode`, `createdAt`, `data`), so a field mapping built against a sample keeps working verbatim on real deliveries. One item per EVENT, not per attempt (retry history lives at `/webhooks/{id}/deliveries`). If you have no retained deliveries yet, the representative payloads from `GET /api/v1/webhooks/events` are returned instead so sample data is never empty; those items carry `isExample: true` and a null `delivery`, and must never be shown as something that actually happened (`source` is `examples` rather than `deliveries`). Test-mode isolated: a `crd_test_` key sees only `livemode:false` events, a live key only live ones. Advisory: reading this never affects a score.
Parameters
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
| limit (query) |
integer |
| eventType (query) |
score.updated | score.band_changed | dispute.resolved | monitor.triggered | policy.threshold_crossed | usage.quota_warning | import.completed | screening.completed | confirmation.awaiting_response | confirmation.expiring_soon: Filter to one or more event types (repeat the param or comma-separate). An unknown type is a 400, not an empty result. |
Responses
| 200 | Recent events · object |
| 400 | Unknown eventType (BAD_REQUEST) · Error |
Example
curl https://api.credda.io/api/v1/webhooks/deliveries
get
/api/v1/webhooks/{id}/deliveries
public
Recent delivery attempts
Parameters
| id (path) required |
string |
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
Responses
Example
curl https://api.credda.io/api/v1/webhooks/{id}/deliveries
post
/api/v1/webhooks/{id}/deliveries/{deliveryId}/replay
public
Replay a past delivery
Re-sends the stored event body, signed with the current secret and a fresh transport timestamp. The event keeps its ORIGINAL id: deliveries are at-least-once and consumers dedup on it, so a replay behaves like a duplicate delivery, not a new event. Single attempt; recorded in the delivery log. 409 PAYLOAD_NOT_RETAINED for deliveries recorded before payload retention.
Parameters
| id (path) required |
string |
| deliveryId (path) required |
string |
Responses
| 200 | Replay outcome · object |
| 404 | Delivery not found · Error |
| 409 | Payload not retained · Error |
Example
curl -X POST https://api.credda.io/api/v1/webhooks/{id}/deliveries/{deliveryId}/replay
get
/api/v1/webhooks/events
public
Outbound webhook event catalog
Every event type the API can send (score.updated, score.band_changed, dispute.resolved, monitor.triggered, usage.quota_warning), with the common delivery envelope, an example payload for each, and how to verify a delivery signature. The envelope carries `livemode` (Stripe convention): `false` when the event was produced by test-mode (crd_test_) activity, `true` otherwise. Self-documenting and drift-proof against the delivery pipeline. Public and unauthenticated. INVARIANT: webhooks are advisory: no event, and no consumer, can change anyone's score.
Responses
| 200 | Webhook event catalog · object |
Example
curl https://api.credda.io/api/v1/webhooks/events
Policies
Threshold policies: declarative, partner-grade rules that fire a policy.threshold_crossed webhook when a subject (one user, or all your subjects) crosses a score/component/verified-event line or a band. Credda supplies evidence + a decision_input block; the partner owns the decision and the loss. Notification config only; never affects a score.
get
/api/v1/policies
public
List your threshold policies
Parameters
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
| limit (query) |
integer |
Responses
Example
curl https://api.credda.io/api/v1/policies
post
/api/v1/policies
public
Create a threshold policy
Registers a declarative rule that fires a `policy.threshold_crossed` webhook when a subject crosses a line, edge-triggered. Scope is exactly one of `userId` (one subject) or `appliesToAll: true` (every one of your subjects; auto-covers new subjects, no per-subject registration). The condition is one `metric`: `score`/`component`/`verified_events` (a `direction` up|down + a `threshold`; `component` also names a component) or `band` (`direction` enter|leave + a `band`, or neither to watch any band change). Credda supplies EVIDENCE of the crossing + a machine-readable `decision_input` block for your OWN policy engine; the decision and the loss belong to you. Active policies per platform are capped by plan tier (Starter 5 / Growth 250 / Enterprise 2000 by default, budgeted separately from monitors, overridable via `POLICY_LIMIT_<TIER>` / `POLICY_MAX_PER_PLATFORM`). Policies are notification config only and never affect a score.
Request body
Responses
| 201 | Created · object |
| 400 | Invalid scope or condition · Error |
| 404 | Unknown userId (USER_NOT_FOUND) · Error |
| 409 | Active policy limit reached for the plan tier (POLICY_LIMIT_REACHED; `details` carries `{limit, tier, mode}`) · Error |
Example
curl -X POST https://api.credda.io/api/v1/policies \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/policies/{id}
public
Fetch one policy
Parameters
| id (path) required |
string |
Responses
| 200 | Policy · object |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/policies/{id}
patch
/api/v1/policies/{id}
public
Retune the line / rename / activate / deactivate
Update `name`, `isActive`, or the condition fields (`direction`/`threshold`/`component`/`band`). The `metric` is immutable; to change it, delete and recreate. The merged condition is re-validated with the same rules as create.
Parameters
| id (path) required |
string |
Request body
Responses
| 200 | Updated · object |
| 400 | Update would leave an invalid condition · Error |
| 404 | Not found · Error |
| 409 | Active policy limit reached (POLICY_LIMIT_REACHED) · Error |
Example
curl -X PATCH https://api.credda.io/api/v1/policies/{id} \
-H "Content-Type: application/json" \
-d '{ ... }'
delete
/api/v1/policies/{id}
public
Delete a policy (hard delete; it is config, not ledger data)
Parameters
| id (path) required |
string |
Responses
| 204 | Deleted |
| 404 | Not found · Error |
Example
curl -X DELETE https://api.credda.io/api/v1/policies/{id}
Confirmations
Counterparty confirmation requests: propose an outcome and have a DISTINCT party confirm it through a one-time token, so isVerified is EARNED by a third-party witness rather than asserted by the reporting platform. A pending request writes no event and moves no score; only a genuine confirmation writes to the ledger (through the same append-only path as POST /events). The confirm/preview endpoints are token-gated and unauthenticated: a counterparty holds a token, not an API key.
get
/api/v1/confirmations
public
List your confirmation requests
Parameters
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
| limit (query) |
integer |
| status (query) |
PENDING | CONFIRMED | DECLINED | EXPIRED | CANCELLED: Filter to one lifecycle state. |
Responses
| 200 | Confirmation requests · object |
Example
curl https://api.credda.io/api/v1/confirmations
post
/api/v1/confirmations
public
Propose an outcome for a counterparty to confirm
Creates a PENDING confirmation request and returns a ONE-TIME token; it writes NO event and moves NO score. Deliver the link to the named counterparty over your OWN channel (Credda sends nothing to anyone): either the hosted page `confirmUrl` (zero frontend to build) or your own UI over `previewUrl`/`respondUrl`. Only when they confirm is the proposed event written to the ledger with isVerified: true. That verified flag is EARNED (a distinct party confirmed), never asserted, which is the whole difference from POST /events. `counterpartyRef` must differ from `userId` (a subject cannot be its own witness → 400 CONFIRMATION_SELF); it is recorded into the resulting event metadata so cross-account velocity/graph signals can see who confirmed. The event type is one of the ingestable outcomes (positive OR negative; a counterparty can confirm a breach as validly as a delivery). Send an `Idempotency-Key` header to make a retried create exactly-once. FREE AT EVERY TIER: any valid platform key may create requests, no scope required (the ask is never paywalled; only a genuine counterparty confirm records an event). INVARIANT: pending/declined/expired requests write nothing; only CONFIRMED reaches the ledger, through the same path as every other event.
Parameters
| Idempotency-Key (header) |
string: Optional; makes a retried create return the original request + token instead of a duplicate. |
Request body
Responses
| 201 | Created (token shown once) · object |
| 400 | Validation error, or counterpartyRef equals the subject (CONFIRMATION_SELF) · Error |
Example
curl -X POST https://api.credda.io/api/v1/confirmations \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/confirmations/batch
public
Bulk-create confirmation requests (the activation engine)
Turn a BOOK of historical relationships, such as past jobs, placements, engagements, projects, for a professional in ANY field (a nurse's shift, a lawyer's matter, a contractor's job, a designer's project), into up to 100 PENDING confirmation requests in one call, warming a cold ledger with real counterparty asks. Each item is exactly the single POST /confirmations body and flows through the SAME create service, so isVerified is still earned in exactly one place (on confirm), never here. INVARIANT: a batch item writes NOTHING to the ledger; importing a book creates pending asks, it does NOT manufacture verified events. Each event is written only when ITS named counterparty confirms. Partial success like POST /events/batch: a bad item (e.g. counterpartyRef equals the subject → CONFIRMATION_SELF) fails INDIVIDUALLY with its index + reason; the rest still land. Each ok item returns its one-time token + hosted confirmUrl to deliver over your OWN channel (Credda sends nothing). An over-cap batch is a 400 (rejected whole, never truncated). Send an `Idempotency-Key` header to make a retried batch exactly-once; there is no per-item key. FREE AT EVERY TIER: any valid platform key, no scope required (same as the single create).
Parameters
| Idempotency-Key (header) |
string: Optional; makes a retried batch replay the original response instead of creating duplicates. |
Request body
Responses
| 200 | Per-item results (partial success) · object |
| 400 | Validation error, or over the 100-item cap · Error |
Example
curl -X POST https://api.credda.io/api/v1/confirmations/batch \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/confirmations/{id}
public
Fetch one of your confirmation requests
Parameters
| id (path) required |
string |
Responses
| 200 | Confirmation request · object |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/confirmations/{id}
post
/api/v1/confirmations/{id}/cancel
public
Cancel a still-pending request
Parameters
| id (path) required |
string |
Responses
| 200 | Cancelled · object |
| 404 | Not found · Error |
| 409 | Already decided (CONFIRMATION_NOT_PENDING) · Error |
Example
curl -X POST https://api.credda.io/api/v1/confirmations/{id}/cancel
get
/api/v1/confirmations/{id}/preview
public
What the counterparty is being asked to confirm (public, token-gated)
Token-gated and unauthenticated: a counterparty holds the one-time token, not an API key. Returns a PII-free subset (the platform name, the human description, the outcome type/stake/value, the deadline), never the raw subject externalId. Read-only.
Parameters
| id (path) required |
string |
| token (query) required |
string: The one-time confirmation token. |
Responses
| 200 | Preview · object |
| 401 | Invalid/missing token (CONFIRMATION_TOKEN_INVALID) · Error |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/confirmations/{id}/preview
post
/api/v1/confirmations/{id}/respond
public
Counterparty confirms or declines (public, token-gated)
The counterparty presents the one-time token and their decision. On `confirm` the proposed event is written to the ledger with isVerified: true (the confirmation is the third-party witness) and the request becomes CONFIRMED. On `decline` nothing is written and it becomes DECLINED. Single-use: a request already decided or expired is 409 CONFIRMATION_NOT_PENDING. Unauthenticated on purpose: the token is the capability.
Parameters
| id (path) required |
string |
Request body
Responses
| 200 | Decision applied · object |
| 401 | Invalid token (CONFIRMATION_TOKEN_INVALID) · Error |
| 409 | Already decided or expired (CONFIRMATION_NOT_PENDING) · Error |
Example
curl -X POST https://api.credda.io/api/v1/confirmations/{id}/respond \
-H "Content-Type: application/json" \
-d '{ ... }'
References
Reference / employment-verification requests: the qualifications-half sibling of confirmations. Propose a résumé claim (employment/education/certification/skill) and have the named third party who was there (a past employer, manager, client, colleague, or issuing body) confirm it through a one-time token, turning a self-attested qualification VERIFIED. A pending/declined/expired/cancelled request records nothing; only a genuine confirmation records the qualification (verified, decided by the one witness valve, never hardcoded) and a qualification NEVER moves the reliability score. The confirm/preview endpoints are token-gated and unauthenticated. NO VERDICT: a reference attests a fact (this person held this role / earned this credential), never a rating or recommendation.
get
/api/v1/references
public
List your reference requests
Parameters
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
| limit (query) |
integer |
| status (query) |
PENDING | CONFIRMED | DECLINED | EXPIRED | CANCELLED: Filter to one lifecycle state. |
Responses
| 200 | Reference requests · object |
Example
curl https://api.credda.io/api/v1/references
post
/api/v1/references
public
Propose a résumé claim for a reference to confirm
Creates a PENDING reference request and returns a ONE-TIME token; it records NO qualification and moves NO score. Deliver the link to the named counterparty over your OWN channel (Credda sends nothing to anyone): the hosted page `referenceUrl` (zero frontend to build) or your own UI over `previewUrl`/`respondUrl`. Only when they confirm is the qualification written, with isVerified: true, EARNED because a distinct party who was there confirmed the claim, never asserted (the difference from a self-attested POST /users/{id}/qualifications). `counterpartyRef` must differ from `userId` (a person cannot be their own reference → 400 REFERENCE_SELF). `category` is one of the qualification categories; label/issuer/jurisdiction/reference are display-only and are never scored or ranked (no prestige weighting). Send an `Idempotency-Key` header to make a retried create exactly-once. FREE AT EVERY TIER: any valid platform key may create requests, no scope required (the ask is never paywalled; only a genuine counterparty confirm records an event). INVARIANT: pending/declined/expired requests record nothing; only CONFIRMED records the qualification, and a qualification never moves the reliability score.
Parameters
| Idempotency-Key (header) |
string: Optional; makes a retried create return the original request + token instead of a duplicate. |
Request body
Responses
| 201 | Created (token shown once) · object |
| 400 | Validation error, or counterpartyRef equals the subject (REFERENCE_SELF) · Error |
Example
curl -X POST https://api.credda.io/api/v1/references \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/references/{id}
public
Fetch one of your reference requests
Parameters
| id (path) required |
string |
Responses
| 200 | Reference request · object |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/references/{id}
post
/api/v1/references/{id}/cancel
public
Cancel a still-pending request
Parameters
| id (path) required |
string |
Responses
| 200 | Cancelled · object |
| 404 | Not found · Error |
| 409 | Already decided (REFERENCE_NOT_PENDING) · Error |
Example
curl -X POST https://api.credda.io/api/v1/references/{id}/cancel
get
/api/v1/references/{id}/preview
public
What the counterparty is being asked to confirm (public, token-gated)
Token-gated and unauthenticated: a counterparty holds the one-time token, not an API key. Returns a PII-free subset (the platform name, the human description, the claim category/label/issuer/jurisdiction/reference, the deadline), never the raw subject externalId. Read-only.
Parameters
| id (path) required |
string |
| token (query) required |
string: The one-time reference token. |
Responses
| 200 | Preview · object |
| 401 | Invalid/missing token (REFERENCE_TOKEN_INVALID) · Error |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/references/{id}/preview
post
/api/v1/references/{id}/respond
public
Counterparty confirms or declines (public, token-gated)
The counterparty presents the one-time token and their decision. On `confirm` the qualification is recorded with isVerified: true (the reference is the third-party witness, decided by the one witness valve, never hardcoded) and the request becomes CONFIRMED. A qualification never moves the reliability score. On `decline` nothing is recorded and it becomes DECLINED (absence of a reference is not evidence against the claim). Single-use: a request already decided or expired is 409 REFERENCE_NOT_PENDING. Unauthenticated on purpose: the token is the capability.
Parameters
| id (path) required |
string |
Request body
Responses
| 200 | Decision applied · object |
| 401 | Invalid token (REFERENCE_TOKEN_INVALID) · Error |
| 409 | Already decided or expired (REFERENCE_NOT_PENDING) · Error |
Example
curl -X POST https://api.credda.io/api/v1/references/{id}/respond \
-H "Content-Type: application/json" \
-d '{ ... }'
Ingest
Data ingress without client-side transformation: POST your OWN payload shape alongside a declarative field mapping, or backfill history from a CSV. Both write through the SAME append-only path as POST /events (idempotency, velocity guard, audit trail, score recomputation); neither contains any scoring logic.
post
/api/v1/ingest
public
Ingest YOUR payload shape via a declarative field mapping
Send records exactly as your system already shapes them, plus a mapping that says how to reach Credda's fields, no client-side transformation code. Up to 100 records per call (page beyond that, or use `POST /imports` for a historical backfill).
Partial success, like `POST /events/batch`: an unmappable or rejected record fails INDIVIDUALLY with its index and reason; the rest still land. Give the mapping an `idempotencyKey` field so re-sending a page is a no-op.
The mapping is **declarative data, never code**: a rule may read a dot-path, supply a constant, look a value up in your own table, or apply one of a fixed transform whitelist (`cents_to_units`, `iso_date`, `lowercase`, `trim`, `boolean`). There is no expression language.
Mapped records are validated by the SAME schema `POST /events` uses and written through the SAME append-only path (idempotency, velocity guard, audit trail, asynchronous score recomputation).
**Verification discipline:** `isVerified` defaults to `false`. A mapping cannot simply assert trust; a record is only ingested verified when the mapping also resolves `verifiedBy` (the third party who witnessed the outcome) to a non-empty value for that record. Otherwise the record is still ingested, downgraded, with an explicit warning.
Request body
Responses
| 200 | Processed (partial success) · object |
| 400 | Validation error, or an invalid mapping (INGEST_MAPPING_INVALID) · Error |
| 401 | Missing/invalid API key · Error |
| 403 | API key lacks events:write · Error |
| 404 | Unknown mappingId · Error |
Example
curl -X POST https://api.credda.io/api/v1/ingest \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/ingest/mappings
public
List your stored mappings
Parameters
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
| limit (query) |
integer |
Responses
Example
curl https://api.credda.io/api/v1/ingest/mappings
post
/api/v1/ingest/mappings
public
Save a reusable named mapping
Declare your translation once, then post `{ mappingId, records }` forever. A mapping is COPIED onto an import job at submit time, so editing one never rewrites what a past import did. Validated on save.
Request body
Responses
| 201 | Created · object |
| 400 | Invalid mapping (INGEST_MAPPING_INVALID) · Error |
| 409 | A mapping with that name already exists · Error |
Example
curl -X POST https://api.credda.io/api/v1/ingest/mappings \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/ingest/mappings/{id}
public
Fetch one stored mapping
Parameters
| id (path) required |
string |
Responses
| 200 | Mapping · object |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/ingest/mappings/{id}
delete
/api/v1/ingest/mappings/{id}
public
Delete a stored mapping (config, not ledger data; events already ingested are untouched)
Parameters
| id (path) required |
string |
Responses
| 204 | Deleted |
| 404 | Not found · Error |
Example
curl -X DELETE https://api.credda.io/api/v1/ingest/mappings/{id}
get
/api/v1/imports
public
List your CSV imports (status + counts)
Parameters
| cursor (query) |
string: Opaque cursor: the `nextCursor` from a previous page. Omit for the first page. |
| limit (query) |
integer |
Responses
Example
curl https://api.credda.io/api/v1/imports
post
/api/v1/imports
public
Backfill historical outcomes from a CSV
Upload a CSV of past outcomes with a mapping whose paths are COLUMN NAMES. Rows run through the identical mapping engine as `POST /ingest` and are written through the same append-only path.
Two body forms: `text/csv` with `?mappingId=` naming a stored mapping, or `application/json` with `{ csv, mapping | mappingId }`.
Async: files of at most 100 rows are processed inline (the 202 usually already reads COMPLETED); larger files are queued; poll `GET /imports/{id}`. If the queue is unavailable a large file is REFUSED with 503 rather than accepted and never run.
Row cap 50000 (`IMPORT_MAX_ROWS`). An over-cap file is refused with both numbers; an import is never silently truncated.
**Backfill honesty:** imported events keep their REAL `completedAt`/`dueDate`, so the deterministic engine recomputes over true history (recency decay included). An import is not a shortcut to a fresher-looking record.
**Verification discipline:** `isVerified` defaults to `false` and is only honoured for a row whose mapping resolves `verifiedBy` (counterparty evidence) on that row. Rows without it still import, downgraded, with a warning on `GET /imports/{id}/errors`.
Parameters
| mappingId (query) |
string: Required for a `text/csv` body; ignored when the JSON body carries `mapping`/`mappingId`. |
Request body
Responses
| 202 | Accepted (inline imports return already COMPLETED) · object |
| 400 | IMPORT_CSV_INVALID / IMPORT_TOO_MANY_ROWS / INGEST_MAPPING_INVALID · Error |
| 401 | Missing/invalid API key · Error |
| 403 | API key lacks events:write · Error |
| 404 | Unknown mappingId · Error |
| 503 | IMPORT_QUEUE_UNAVAILABLE: large imports cannot be accepted right now · Error |
Example
curl -X POST https://api.credda.io/api/v1/imports \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/imports/{id}
public
Import status + counts
Parameters
| id (path) required |
string |
Responses
| 200 | Import · object |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/imports/{id}
get
/api/v1/imports/{id}/errors
public
Per-row failures (and warnings) so you can fix and re-upload
Every rejected row with its 1-based data-row number and the exact reason, plus non-fatal warnings such as an `isVerified` downgrade. Re-uploading a corrected file is safe when the mapping supplies an `idempotencyKey`; rows already written come back as `skipped`. The stored list is capped (`IMPORT_MAX_STORED_ERRORS`); `failedCount` stays authoritative and `truncated` says when the list is short.
Parameters
| id (path) required |
string |
| limit (query) |
integer |
| offset (query) |
integer |
Responses
| 200 | Row errors · object |
| 404 | Not found · Error |
Example
curl https://api.credda.io/api/v1/imports/{id}/errors
Agents
Non-human subjects and their counterparty-confirmed delivery receipts. Same ledger, same deterministic formula, one extra integrity rule: an agent's own operator can never confirm its work. A delivery record, not a safety, alignment or capability rating, and never a recommendation.
get
/api/v1/verify/{token}/delivery-receipts
public
Delivery receipts + a signed delivery credential
The counterparty-confirmed delivery record behind a share token: deliveries recorded, how many a DISTINCT counterparty confirmed, how many were the subject's own operator vouching for it (never confirmed), failures, disputes and the on-time rate over confirmed deliveries, plus a signed W3C credential of that record (`CreddaDeliveryReceiptCredential`, and `CreddaAgentDeliveryCredential` when the subject is an agent), on the same EdDSA key, did:web issuer and StatusList2021 revocation entry as every other Credda credential. Public: the token is the capability, so an agent can present one string mid-negotiation and the counterparty verifies it offline. This is a DELIVERY RECORD, not a safety, alignment or capability rating, and never a recommendation; the payload says so itself.
Parameters
| token (path) required |
string |
| ttl (query) |
integer |
Responses
| 200 | Delivery receipts + credential · object |
| 403 | Test-mode subject · Error |
| 404 | Unknown/rotated token · Error |
Example
curl https://api.credda.io/api/v1/verify/{token}/delivery-receipts
post
/api/v1/agents
public
Register (or update) an agent subject
Label a subject as a NON-HUMAN agent and declare who operates it. Writes no events and touches no score; an agent's record runs the IDENTICAL deterministic formula as a person's. Declaring yourself the operator (the default) means every event YOU report for this agent is recorded but never counted as verified evidence: only a distinct counterparty can confirm a delivery. Declaring a third-party operator requires naming it (`operator.name` / `.homepage` / `.did`), and a declared identity that resolves to your own platform is treated as self-dealing too. Refuses to relabel a person subject that already has a reported record (409).
Request body
Responses
| 201 | Agent subject · object |
| 400 | Third-party operator declared but not named (OPERATOR_NOT_DECLARED) · Error |
| 409 | Subject already has a person record · Error |
Example
curl -X POST https://api.credda.io/api/v1/agents \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/agents/{id}
public
Inspect an agent subject
The agent's declared facts (claims, never evidence), its current deterministic score, and its delivery record split by whether a distinct counterparty confirmed each delivery. Read-only.
Parameters
| id (path) required |
string |
Responses
| 200 | Agent subject · object |
| 404 | Not an agent / not found · Error |
Example
curl https://api.credda.io/api/v1/agents/{id}
Businesses
Businesses as first-class scored subjects. Same ledger, same deterministic formula, one extra integrity rule: a business can never confirm its own outcomes, not by reporting them itself, and not by naming itself as its own counterparty. A record of outcomes OTHER businesses confirmed. Business-to-business only: Credda holds no customer/patient/consumer register, and a business's record is never derived from the people who work there.
post
/api/v1/businesses
public
Register (or update) a business subject
Label a subject as a BUSINESS (an ORGANIZATION subject) and declare who controls the record. Writes no events and touches no score; a business's record runs the IDENTICAL deterministic formula as a person's. There is no business score, no business weighting and no business-specific event type.
Declaring yourself the controller (the default) means every outcome YOU report for this business is recorded but never counted as confirmed evidence: only a DISTINCT counterparty business can confirm an outcome. An outcome whose recorded counterparty resolves to the business itself (any alias: trading name, domain, did:web) is likewise recorded but never confirmed.
At least one identity (`business.legalName` / `.tradingName` / `.homepage` / `.did`) is required, because a business with no identifiable name cannot be checked for self-dealing against its own counterparties. Refuses to relabel a subject that already has a reported record as a person or agent (409).
SCOPE: business-to-business only. Credda records no consumer/customer/patient register of any kind, and derives nothing about a business from the people who work there. See docs/BUSINESSES.md.
Request body
Responses
| 201 | Business subject · object |
| 400 | No business identity declared (BUSINESS_IDENTITY_NOT_DECLARED) · Error |
| 409 | Subject already has a person/agent record · Error |
Example
curl -X POST https://api.credda.io/api/v1/businesses \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/businesses/{id}
public
Inspect a business subject
The business's declared facts (claims, never evidence), its current deterministic score, and its business-to-business record split by whether a distinct counterparty business confirmed each outcome. Read-only. Nothing in the response is derived from the business's staff or its customers.
Parameters
| id (path) required |
string |
Responses
| 200 | Business subject · object |
| 404 | Not a business / not found · Error |
Example
curl https://api.credda.io/api/v1/businesses/{id}
get
/api/v1/businesses/{id}/record
public
The counterparty-confirmed B2B record on its own
Outcomes recorded, how many a DISTINCT counterparty business confirmed (`confirmedOutcomes`: the only number that is evidence), how many the business attested about itself (never evidence), failures, disputes, the on-time rate over confirmed fulfilments, the record's own `verificationDepth` (confirmed ÷ total: depth, not platform breadth, matching scoring v5.3), and `distinctConfirmingCounterparties` (aliases collapsed, so one counterparty confirming forty jobs never reads as forty).
Parameters
| id (path) required |
string |
Responses
| 200 | Business record · object |
| 404 | Not a business / not found · Error |
Example
curl https://api.credda.io/api/v1/businesses/{id}/record
get
/api/v1/verify/{token}/business-record
public
Business record + a signed business credential
The counterparty-confirmed business-to-business record behind a share token, plus a signed W3C credential of that record (`CreddaBusinessRecordCredential`) on the same EdDSA key, did:web issuer and StatusList2021 revocation entry as every other Credda credential. Public: the token is the capability, so a business can hand a prospective counterparty one string and have the record verified offline.
This is a RECORD OF CONFIRMED OUTCOMES, never a rating of the business, never a credit or hiring decision, never anything about the business's customers, and never derived from its staff; the payload says so itself. 404s when the subject behind the token is not a business.
Parameters
| token (path) required |
string |
| ttl (query) |
integer |
Responses
| 200 | Business record + credential · object |
| 403 | Test-mode subject · Error |
| 404 | Unknown/rotated token, or not a business · Error |
Example
curl https://api.credda.io/api/v1/verify/{token}/business-record
Earnings
Verified Earnings: a portable attestation of income already recorded on the ledger, plus a signed, offline-verifiable earnings credential. Pure derivation: no scoring logic, and nothing here can move a score. Not an income verification for a credit decision and not a consumer report.
get
/api/v1/users/{id}/earnings
public
Verified earnings attestation (monthly breakdown + stability metrics)
Attests income ALREADY RECORDED on the ledger. Monthly buckets over the requested window, each with attested gross, event count and per-platform breakdown, plus stability metrics lenders care about (months with earnings, median/mean monthly, coefficient of variation, longest consecutive run, trailing-12m total).
**Only counterparty/platform-VERIFIED outcomes are attested.** Unverified activity is reported separately as `unverifiedReported` and is never blended into any attested figure. Disputed outcomes are excluded (and counted in `excluded`). Auto-imported activity still counts but is surfaced via `coverage.selfReportedShare`. `currency` is always **null**: the ledger records no currency, so amounts are platform-reported units.
**This is an attestation of recorded outcomes. It is NOT an income verification for a credit decision, NOT a consumer report, and Credda makes no representation of completeness; the subject may earn income that was never reported here. Nothing is extrapolated or projected.** No verdict language appears anywhere in the response.
Pure derivation over the same ledger the score reads: this surface reads no score, writes nothing, and cannot influence a score.
Parameters
| id (path) required |
string |
| months (query) |
integer: Trailing window length in months (1–120). Ignored when `from` is supplied. Default 12. |
| from (query) |
string: Window start (ISO-8601). Overrides `months`. |
| to (query) |
string: Window end (ISO-8601). Defaults to now. |
Responses
| 200 | Verified earnings · VerifiedEarnings |
| 400 | Invalid window · Error |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/earnings
get
/api/v1/users/{id}/earnings/summary
public
Compact earnings summary (trailing 12m total, median monthly, volatility, coverage)
The same attestation reduced to the handful of figures a lender or landlord actually reads. Adds no fact the full breakdown does not already hold. Carries the same disclosures and the same `currency: null`.
Parameters
| id (path) required |
string |
| months (query) |
integer: Trailing window length in months (1–120). Ignored when `from` is supplied. Default 12. |
| from (query) |
string: Window start (ISO-8601). Overrides `months`. |
| to (query) |
string: Window end (ISO-8601). Defaults to now. |
Responses
| 200 | Earnings summary · EarningsSummary |
| 400 | Invalid window · Error |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/earnings/summary
post
/api/v1/users/{id}/earnings/credential
public
Mint a signed Verified Earnings Credential (W3C VC-JWT)
Issues an EdDSA-signed W3C Verifiable Credential of type `CreddaEarningsCredential` carrying the summary claims, the window, and the `earningsVersion` provenance, so the subject can PROVE the recorded income offline, without the verifier calling Credda. Same issuer key and `did:web` identity as the trust credential, and it carries a StatusList2021 `credentialStatus` so it can be revoked.
Uses the subject's existing share token when there is one (issuing a credential never rotates a token and never invalidates a published badge); mints one if they have none. **Refuses test-mode users** (`TEST_MODE_NOT_ALLOWED`): sandbox data never becomes portable trust.
Writes nothing score-side. The credential attests recorded outcomes; it is not an income verification for a credit decision and not a consumer report.
Parameters
| id (path) required |
string |
Request body
Responses
| 201 | Signed earnings credential · object |
| 400 | Invalid window or ttl · Error |
| 403 | Test-mode keys cannot issue credentials · Error |
| 404 | User not found · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/earnings/credential \
-H "Content-Type: application/json" \
-d '{ ... }'
Professional Record
A worker-OWNED, résumé-shaped summary of a VERIFIED work record: reliability band, verified-outcome counts, verification depth and tenure, plus a signed, offline-verifiable credential and a LinkedIn "Add to profile" deep link. Pure derivation: no scoring logic, nothing here can move a score. It describes the record the subject chose to present; it is NOT a hiring recommendation, a background check, or a consumer report.
get
/api/v1/users/{id}/professional-record
public
Worker-owned summary of a verified work record (résumé-shaped)
A portable summary of the subject's VERIFIED work record: reliability score/band + verified-evidence confidence, verified-outcome counts, `verificationDepth` (share of the record independently verified), verified-platform breadth, and `tenure` (the observed span of the record). For everyone with a work history, not only contractors.
**Only THIRD-PARTY-VERIFIED outcomes count as verified experience.** Self-attested activity is recorded in `totalOutcomes` but is never counted as verified. Nothing is invented, extrapolated or projected; a missing figure is `null`, never a default.
**It describes the record the subject chose to present. It is NOT a hiring, promotion or employment recommendation, NOT a background check, and NOT a consumer report under the FCRA. No verdict language appears anywhere in the response.**
Pure derivation over the same ledger the score reads: this surface reads no score, writes nothing, and cannot influence a score.
Parameters
| id (path) required |
string |
Responses
| 200 | Professional record · ProfessionalRecord |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/professional-record
post
/api/v1/users/{id}/professional-record/credential
public
Mint a signed Professional Record Credential + an "Add to LinkedIn" link
Issues an EdDSA-signed W3C Verifiable Credential of type `CreddaProfessionalRecordCredential` carrying the summary claims and the `professionalRecordVersion` provenance, so the subject can PROVE their verified record offline, on a résumé, a profile, or in a wallet. Same issuer key and `did:web` identity as every other Credda credential, with a StatusList2021 `credentialStatus` so it can be revoked.
Also returns a `linkedin` block with an **"Add to profile" certification deep link**. LinkedIn does not ingest verifiable credentials; the link opens its "Add licenses & certifications" form pre-filled with a certification whose "Show credential" URL (`certUrl`) resolves to a public, independently verifiable Credda proof (`GET /api/v1/verify/{token}`). The signed credential carries the claims; LinkedIn stores only the name, organization, dates, credential id and the verification URL.
Uses the subject's existing share token when there is one (never rotates it, so a published badge keeps working); mints one if they have none. **Refuses test-mode users** (`TEST_MODE_NOT_ALLOWED`): sandbox data never becomes portable trust.
Writes nothing score-side. It attests a record; it is not a hiring recommendation and not a consumer report.
Parameters
| id (path) required |
string |
Request body
Responses
| 201 | Signed professional record credential · object |
| 400 | Invalid ttl · Error |
| 403 | Test-mode keys cannot issue credentials · Error |
| 404 | User not found · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/professional-record/credential \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/api/v1/users/{id}/career-export
public
Portable professional record in the open JSON Resume schema
The subject's whole verified professional record: reliability + verified experience + tenure + itemised qualifications (work history, education, certifications, skills), serialised in an OPEN schema so it drops into an ATS/HRIS, a résumé builder, or an LER-style reader **without a bespoke Credda integration**. Two open serialisations are available: **JSON Resume** (`format=jsonresume`, jsonresume.org; the default) and a **schema.org `Person` JSON-LD graph** (`format=jsonld`, schema.org: `EducationalOccupationalCredential`, `alumniOf`, `hasOccupation`, `knowsAbout`). Both carry the same facts; pick whichever your reader already parses.
**What makes it Credda's and not just a résumé:** every item is flagged `verified` vs `self-reported` (mirroring the ledger's `isVerified` exactly: a per-item `credda` extension in JSON Resume, `credda:verified` on each node in JSON-LD), and each verified item anchors to the subject's public **proof URL** (`GET /api/v1/verify/{token}`) so a reader can confirm it independently. The document therefore transports third-party verification in a shape existing tools already accept. A `meta.credda` / `credda:meta` block carries the reliability summary, verification depth, tenure, provenance and the always-present disclosures.
The subject's EXISTING share token is used to build proof URLs when they have one (issuing an export never mints or rotates a token); with no token (including every sandbox user), verified items carry no public anchor but the document is still valid.
**It describes a record. It is NOT a hiring, lending, or employment decision or an input to one, NOT a background check, and NOT a consumer report under the FCRA. No verdict language appears anywhere.** Read-only under `scores:read`; reads no score and writes nothing.
Parameters
| id (path) required |
string |
| format (query) |
jsonresume | jsonld: Open serialisation: 'jsonresume' (default) or 'jsonld' (schema.org Person). |
Responses
| 200 | JSON Resume document (or schema.org JSON-LD when format=jsonld), each item flagged verified vs self-reported · object |
| 400 | Unsupported format (CAREER_FORMAT_INVALID) · Error |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/career-export
get
/api/v1/users/{id}/reliability-report
public
One-call worker reliability report (decision-support dossier)
A single consolidated read a staffing agency or employer weighs before placing or hiring a worker. It **aggregates** what the engine already computed; it computes no new score:
- `reliability`: score, band, verified-evidence confidence, and the `formulaVersion` / `reasonCodesVersion` that produced them.
- `metrics`: the reliability rates a buyer reads first: `completionRate`, `onTimeRate`, `consistency`, `recency` (null when there is no dated activity) and `disputeRate`, each ∈ [0,1] and a pure relabel of the snapshot's own components.
- `verifiedExperience`: verified vs total outcomes, `verificationDepth`, verified-platform breadth, and `tenure` (the observed span of the record).
- `topFactors`: the ranked reason-code drivers (WHY the score is what it is), merged across adverse/supporting and ordered by contribution.
- `recentOutcomes`: the last N confirmed outcomes, **each flagged `verified` vs `self_reported`** so self-reported activity is never presented as verified.
- `benchmark` (with `?benchmark=1`): a COARSE quartile-grain comparison only (top_decile / top_quartile / above_median / below_median), k-anonymity respected; null below the floor or with no score.
**FCRA bright line:** it is EVIDENCE for a reader who applies their own criteria, NOT a hire / place / rank / approve / deny verdict or recommendation. The always-present `disclosures` state this is not a consumer report, that Credda decides nothing, and that the reader owns the decision. No verdict language appears anywhere in the payload.
Read-only under `scores:read`; reuses already-computed values and cannot influence a score (`calculateCreddaScore` stays the sole snapshot writer). Test/live isolated and book-scoped like every other `/users/{id}` read.
Parameters
| id (path) required |
string |
| recent (query) |
integer: Recent outcomes to include (1–50, default 10). |
| benchmark (query) |
1: Set to 1 to attach the coarse quartile-grain benchmark. |
Responses
| 200 | Consolidated reliability report · object |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/reliability-report
get
/api/v1/users/{id}/projects
public
List the subject's personal projects (verified vs self-reported)
The subject's self-presented personal projects: a photo record of finished work, a live URL, a case study, a code repository, each flagged verified vs self-reported. **A project is never a scoring input** (a `PROJECT_LOGGED` event is not a reliability-outcome type), and Credda stores the link as text and never fetches it. Read-only under `scores:read`.
Parameters
| id (path) required |
string |
Responses
| 200 | Personal projects · PersonalProjects |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/projects
post
/api/v1/users/{id}/projects
public
Record a personal project (self-reported by default)
Records a personal project on the append-only ledger. The project is ALWAYS recorded; whether it counts as VERIFIED is decided by the witness rule: `isVerified` is true ONLY when a genuine third-party `verifiedBy` witness (distinct from the subject) is supplied, NEVER hardcoded true.
The `url` must be an absolute **http(s)** link to a **public** host: `javascript:`/`data:`/`file:` schemes, embedded credentials, and private/loopback/link-local hosts are rejected. **Credda never fetches or scrapes the link**; the scheme check only governs what may be stored. **Writes nothing score-side**; a personal project cannot move the Reliability Score.
Parameters
| id (path) required |
string |
Request body
Responses
| 201 | Project recorded · object |
| 400 | Invalid project (missing title, or url is not a public http(s) link) · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/projects \
-H "Content-Type: application/json" \
-d '{ ... }'
Verified Profile
The QUALIFICATIONS half of the professional record: education, skills, certifications, employment. A deterministic, BIAS-FREE measure of how much of the claimed record is independently third-party verified. It counts WHETHER a claim is verified, never how prestigious it is: no school, employer, degree or credential is ranked or weighted. Complements the Professional Record tag (which summarises the RELIABILITY record). A SEPARATE measure from the Reliability Score that can never move it, and verification of claims, not an assessment of the person.
get
/api/v1/users/{id}/verified-profile
public
Verified-profile measure (verification depth of the qualifications record)
How much of the subject's claimed QUALIFICATIONS record (education, skills, certifications, employment) is independently third-party verified. Returns per-category claimed vs verified counts, each category's verification depth, and the overall depth (share of the whole claimed record that is verified).
**Deterministic and BIAS-FREE.** It counts WHETHER each claim is verified, never how prestigious it is: a verified credential from a community college and a verified degree from any other institution count identically. No school, employer, degree or credential is ranked or weighted; that would reintroduce exactly the bias the score exists to remove. Self-attested claims are recorded but do not raise verification depth.
Complements `GET /api/v1/users/{id}/professional-record`, which summarises the RELIABILITY record (outcome events and tenure). Different populations, different endpoints.
**This is verification of claims, not an assessment of the person, and it is a SEPARATE measure from the Reliability Score; it can never move a score.** Read-only under `scores:read`; nothing here computes or writes a score.
Parameters
| id (path) required |
string |
Responses
| 200 | Verified profile · VerifiedProfile |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/verified-profile
post
/api/v1/users/{id}/qualifications
public
Record a qualification claim (education / skill / certification / employment)
Records a qualifications claim for the subject on the append-only ledger. The claim is ALWAYS recorded; whether it counts as VERIFIED is decided by the witness rule: `isVerified` is true ONLY when a genuine third-party `verifiedBy` witness (distinct from the subject) is supplied; it is NEVER hardcoded true, and a claim without a witness (or witnessed by the subject) is recorded as self-attested.
There is NO prestige/rank field: a claim carries free-text `label`, `issuer`, `jurisdiction` and `reference` for display only, and none of them is ever a scoring input or ranked. **Writes nothing score-side and never enqueues a score recompute**; a qualification claim cannot move the Reliability Score.
`jurisdiction` and `reference` make a LICENCE expressible: a trade or professional licence is meaningless without the place that granted it and the number printed on it. Both are properties of the CREDENTIAL; they are stored in that claim's ledger metadata, are never written to the subject, are never indexed and are never queryable, so a jurisdiction can describe a credential without ever becoming a way to filter people by where they are from. No jurisdiction ranks above another, and Credda does not resolve a `reference` against any register or assert that it is current.
Parameters
| id (path) required |
string |
Request body
Responses
| 201 | Qualification recorded · object |
| 400 | Invalid category · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/qualifications \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/users/{id}/qualifications/import
public
Bulk-import a claimed professional record (education / employment / skills / certifications)
The onboarding accelerator: seed a whole claimed professional record in ONE call instead of one POST per line. Takes a STRUCTURED array of claims; there is deliberately NO AI and NO free-text résumé parsing here (this is the deterministic scoring API; that advisory step belongs elsewhere).
Up to 100 claims per call (more is a 400). Each item is the same shape as `POST /users/{id}/qualifications`, and every item flows through the SAME single-claim writer, so `isVerified` is decided by the witness rule in exactly one place and is NEVER hardcoded true. A self-import with no witnesses records every claim self-attested (`isVerified:false`); because verification depth is verified/claimed, a big self-attested import LOWERS depth until each claim is independently confirmed; it seeds claims, it does not manufacture trust.
Partial-success: one bad row does not roll back the rest; each item carries its own `ok`/`error`. **Writes nothing score-side and never enqueues a score recompute**; a qualification claim cannot move the Reliability Score. Prototype-pollution keys are refused.
Parameters
| id (path) required |
string |
Request body
Responses
| 200 | Import summary (partial success) · QualificationImportResult |
| 400 | Malformed body, over the per-import cap, or an invalid claim · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/qualifications/import \
-H "Content-Type: application/json" \
-d '{ ... }'
Verify
Public, PII-free verification + Verifiable Credentials.
post
/api/v1/users/{id}/share-token
public
Mint/rotate a public trust-badge token
Parameters
| id (path) required |
string |
Responses
| 200 | Token + embed snippet · object |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/share-token
delete
/api/v1/users/{id}/share-token
public
Revoke the share token
Parameters
| id (path) required |
string |
Responses
Example
curl -X DELETE https://api.credda.io/api/v1/users/{id}/share-token
get
/api/v1/verify/{token}
public
Resolve a share token to a PII-free payload + credential
Public trust payload for the share token, reduced to the requested disclosure scope, plus a signed credential over exactly those disclosed facts.
`?earnings=1` additionally attaches a compact Verified Earnings summary, **only at `scope=full`** (a band/minimal embed must never leak income) and **never inside the signed credential**; the signed income attestation is the dedicated `POST /users/{id}/earnings/credential`. The block is `null` if earnings are unavailable.
`?professional=1` attaches the worker-owned Professional Record summary, **only at `scope=full`**, never inside the signed credential (the signed attestation is `POST /users/{id}/professional-record/credential`). The block is `null` if unavailable.
`?profile=1` attaches the CONSOLIDATED professional profile (the "portable resume" / AI-recruiter read): reliability + verified qualifications (work history / education / certifications / skills, itemized) + linked personal projects, each flagged verified vs self-reported, with the FCRA/no-verdict disclosures in-band. The share token IS the consent. **Only at `scope=full`**, never inside the signed credential, `null` if unavailable. It is evidence a reader verifies and applies their own criteria to, never a rating, ranking, or hiring verdict on the person.
Parameters
| token (path) required |
string |
| scope (query) |
full | band | minimal |
| earnings (query) |
1: Set to 1 to attach a Verified Earnings summary (scope=full only). |
| professional (query) |
1: Set to 1 to attach the Professional Record summary (scope=full only). |
| profile (query) |
1: Set to 1 to attach the consolidated professional profile: reliability + verified qualifications + linked projects (scope=full only). |
| benchmark (query) |
1: Set to 1 to attach a COARSE population comparison (scope=full only): a quartile-grain label (`top_decile` / `top_quartile` / `above_median` / `below_median`) against the whole scored population, never a precise percentile and never the population size. Absent (`null`) unless the population clears the k-anonymity floor. A distribution fact, not a verdict. |
Responses
| 200 | Verification payload · object |
| 404 | Unknown/rotated token · Error |
Example
curl https://api.credda.io/api/v1/verify/{token}
get
/api/v1/verify/{token}/credential
public
Signed credential (?format=w3c → W3C VC-JWT)
Parameters
| token (path) required |
string |
| format (query) |
credda | w3c |
Responses
Example
curl https://api.credda.io/api/v1/verify/{token}/credential
get
/api/v1/verify/{token}/export
public
Portable, self-verifying trust export
A single bundle the subject owns: current public score + a signed W3C credential (offline-verifiable) + a revocation pointer. Public: the token is the capability.
Parameters
| token (path) required |
string |
Responses
| 200 | Trust export bundle · object |
| 404 | Unknown/rotated token · Error |
Example
curl https://api.credda.io/api/v1/verify/{token}/export
get
/api/v1/verify/{token}/career-export
public
Portable professional record in an open résumé schema
The subject's whole verified professional record: reliability + verified experience + tenure + itemised qualifications (work history / education / certifications / skills), serialised in an OPEN schema so it drops into an ATS/HRIS or résumé tool without a bespoke Credda integration. Two open serialisations: **JSON Resume** (`format=jsonresume`, jsonresume.org; default) and a **schema.org `Person` JSON-LD graph** (`format=jsonld`, schema.org). Every item is flagged `verified` vs `self-reported`, and verified items anchor to THIS public proof URL so a reader can confirm them independently. A `meta.credda` / `credda:meta` block carries the reliability summary, verification depth, tenure and the always-present disclosures. The share token IS the consent. **It describes a record, never a hiring/lending decision, a background check, or an FCRA consumer report; no verdict language appears.** Public: the token is the capability; read-only.
Parameters
| token (path) required |
string |
| format (query) |
jsonresume | jsonld |
Responses
| 200 | JSON Resume document (or schema.org JSON-LD when format=jsonld) · object |
| 400 | Unsupported format (CAREER_FORMAT_INVALID) · Error |
| 404 | Unknown/rotated token or no record · Error |
Example
curl https://api.credda.io/api/v1/verify/{token}/career-export
get
/api/v1/verify/{token}/reliability-report
public
Worker-consent reliability report
The WORKER-CONSENT variant of `GET /api/v1/users/{id}/reliability-report`: a worker hands a prospective employer their share token and the employer reads the same consolidated decision-support dossier: reliability + metrics + verified experience + tenure + ranked drivers + recent outcomes (each flagged verified vs self-reported) + an optional coarse benchmark, with NO API key. The token IS the consent to share it.
Fail-safe: an unknown/rotated token 404s; a race that removes the record returns `reliabilityReport: null` rather than a broken document. `?recent=N` (1–50, default 10) bounds the outcomes list; `?benchmark=1` attaches the coarse quartile-grain comparison. **It is evidence a reader weighs against their own criteria, never a hiring verdict, a background check, or an FCRA consumer report; no verdict language appears.** Public: the token is the capability; read-only.
Parameters
| token (path) required |
string |
| recent (query) |
integer |
| benchmark (query) |
1 |
Responses
| 200 | Reliability report (or reliabilityReport: null) · object |
| 404 | Unknown/rotated token · Error |
Example
curl https://api.credda.io/api/v1/verify/{token}/reliability-report
get
/api/v1/status/revocation
public
StatusList2021 revocation list (signed VC)
Published bitstring of revoked credential subjects. A bit is set when the subject revoked their share token or the score is frozen. Verifiers check the `credentialStatus` index against this list offline.
Responses
| 200 | StatusList2021 Verifiable Credential · object |
Example
curl https://api.credda.io/api/v1/status/revocation
Wallets
Wallet-compatible issuance: OpenID for Verifiable Credential Issuance (openid-4-verifiable-credential-issuance-1_0 (final, 2025-09-16)) with SD-JWT VC (draft-ietf-oauth-sd-jwt-vc-17), so a holder can keep their Credda credential in any OID4VCI wallet instead of coming back to Credda for it. Selective disclosure moves the "how much do I reveal" choice from issuance to presentation. Issuance is implemented; the OID4VP request side is not (see docs/WALLETS.md).
post
/api/v1/users/{id}/credential-offer
public
Mint an OID4VCI Credential Offer for this subject
Returns a Credential Offer (openid-4-verifiable-credential-issuance-1_0 (final, 2025-09-16), Pre-Authorized Code flow) plus its `openid-credential-offer://` deep link, so the subject can collect their Credda credential into any OID4VCI-capable wallet: show it as a QR code or hand it over as a link.
The offer carries a **single-use, short-lived** pre-authorized code; everything after that happens between the wallet and `/oid4vci/*`, so this is the only endpoint a platform integrates with.
`scope` (minimal | band | full, default `full`) bounds what the credential CONTAINS. For an SD-JWT VC the holder additionally chooses, at presentation time, which of those claims to reveal: issuance scope bounds what exists, presentation choice bounds what is shown.
Uses the subject's existing share token when there is one (never rotates it, so a published badge keeps working). **Refuses test-mode keys** (`TEST_MODE_NOT_ALLOWED`): sandbox data never becomes portable trust.
INVARIANT: no score is computed or adjusted here.
Parameters
| id (path) required |
string |
Request body
Responses
| 201 | Credential offer · object |
| 400 | Unknown credential configuration or scope · Error |
| 403 | Test-mode keys cannot issue credentials · Error |
| 404 | User not found · Error |
Example
curl -X POST https://api.credda.io/api/v1/users/{id}/credential-offer \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/verify/presentation
public
Verify an SD-JWT VC presentation a holder handed you
The relying-party half of a wallet handshake. A holder presents an SD-JWT VC (collected from Credda by any OID4VCI wallet) with only the disclosures they chose to reveal; this checks the issuer signature, enforces expiry, and recomputes every supplied disclosure against `_sd`; a fabricated or tampered disclosure is an error, never a silently-accepted claim.
Returns exactly the disclosed claims, plus the `credentialStatus` pointer so you can check the published StatusList2021 for revocation. Key Binding JWTs are not required or checked today.
This is deliberately NOT full OID4VP: there is no presentation request, no DCQL query and no wallet invocation; the holder brings the token to you. See docs/WALLETS.md.
Public and unauthenticated (the presentation is itself the capability) and strictly read-only.
Request body
Responses
| 200 | Verified presentation · object |
| 400 | Not a valid Credda SD-JWT VC presentation (bad signature, expired, or a disclosure that does not match `_sd`) · Error |
Example
curl -X POST https://api.credda.io/api/v1/verify/presentation \
-H "Content-Type: application/json" \
-d '{ ... }'
get
/.well-known/openid-credential-issuer
public
OID4VCI Credential Issuer Metadata
Credential Issuer Metadata per openid-4-verifiable-credential-issuance-1_0 (final, 2025-09-16). Describes the credential endpoint, the nonce endpoint, and every supported credential configuration, one per (credential type × format), DERIVED from the issuer's credential-type catalog so a new type cannot ship undiscoverable.
Formats: `dc+sd-jwt` (SD-JWT VC, draft-ietf-oauth-sd-jwt-vc-17, selectively disclosable) and `jwt_vc_json` (W3C VC-JWT, all-or-nothing). `authorization_servers` is absent because this service is its own authorization server; its RFC 8414 metadata is at `/.well-known/oauth-authorization-server`.
Public and unauthenticated: a wallet must be able to read it before it holds anything.
Responses
| 200 | Credential Issuer Metadata · object |
Example
curl https://api.credda.io/.well-known/openid-credential-issuer
get
/.well-known/oauth-authorization-server
public
OAuth 2.0 Authorization Server Metadata (RFC 8414)
Token endpoint and supported grants for the wallet issuance flow. Only the Pre-Authorized Code grant is supported, and anonymous token requests are permitted (`pre-authorized_grant_anonymous_access_supported`) because a wallet holds no client credentials with Credda. `response_types_supported` is omitted, which OID4VCI §12.3 explicitly allows for a pre-authorized-code-only authorization server.
Responses
| 200 | Authorization Server Metadata · object |
Example
curl https://api.credda.io/.well-known/oauth-authorization-server
post
/oid4vci/token
public
Exchange a pre-authorized code for an access token
OID4VCI §6. Form-encoded (or JSON) Token Request with `grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code` and the `pre-authorized_code` from the Credential Offer. The code is **single use and short lived**; a replay returns `invalid_grant`. Error bodies are OAuth-shaped (`{error, error_description}`), not Credda-shaped, because that is what a wallet parses.
Responses
| 200 | Access token · object |
| 400 | OAuth error (invalid_request / invalid_grant / unsupported_grant_type) · OAuthError |
| 503 | Issuance store unavailable, retry · OAuthError |
Example
curl -X POST https://api.credda.io/oid4vci/token \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/oid4vci/nonce
public
Get a fresh c_nonce for a key proof
OID4VCI §7. Returns a single-use `c_nonce` the wallet must put in its key-proof JWT, which is what makes the proof non-replayable. Unprotected by design: the nonce is a freshness challenge, not a capability.
Responses
| 200 | Nonce · object |
| 503 | Issuance store unavailable, retry · OAuthError |
Example
curl -X POST https://api.credda.io/oid4vci/nonce
post
/oid4vci/credential
public
Issue the credential into the wallet
OID4VCI §8. Bearer-authenticated with the access token from `/oid4vci/token`. Requires a `proofs.jwt` key proof (`typ: openid4vci-proof+jwt`, `aud` = the Credential Issuer Identifier, a fresh `nonce`, and the public key inline as a `jwk` header; `kid`/`x5c` are refused rather than pretended-to-be-checked). The issued credential is bound to that key via `cnf` and carries a StatusList2021 `credentialStatus`.
Batch issuance is not supported (no `batch_credential_issuance` in metadata), so exactly one key proof is accepted.
INVARIANT: issuance re-encodes what the deterministic engine already computed. No score is computed, read-through, or adjusted here.
Request body
Responses
| 200 | Credential · object |
| 400 | OID4VCI credential error (invalid_proof / invalid_nonce / unknown_credential_configuration / credential_request_denied) · OAuthError |
| 401 | invalid_token · OAuthError |
| 503 | Issuance store unavailable, retry · OAuthError |
Example
curl -X POST https://api.credda.io/oid4vci/credential \
-H "Content-Type: application/json" \
-d '{ ... }'
Admin
Operator-only provisioning (X-Admin-Secret).
post
/api/v1/admin/platforms
admin secret
Create a platform
Request body
Responses
| 201 | Created |
| 400 | referredByPlatformId does not reference an existing platform · Error |
Example
curl -X POST https://api.credda.io/api/v1/admin/platforms \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/admin/platforms/{id}/keys
admin secret
Generate an API key (shown once)
Optional `scopes` (defaults to full access), `expiresInDays` (1–3650; omitted = never expires) and `isTest` (default false; `true` issues a sandbox `crd_test_` key that can only ever create/read the platform's TEST data; see the Test mode section). An expired key is rejected at authentication like a revoked one. The response includes the key record `id` (usable with the rotate endpoint) alongside the plaintext `apiKey`. Rotation preserves a key's mode.
Parameters
| id (path) required |
string |
Request body
Responses
Example
curl -X POST https://api.credda.io/api/v1/admin/platforms/{id}/keys \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/admin/platforms/{id}/keys/{keyId}/rotate
admin secret
Rotate an API key with a grace overlap (new key shown once)
Atomically creates a replacement key with the SAME label and scopes (plaintext returned exactly once) and sets the old key's `expiresAt` to now + `graceHours` (default 24, clamped 0–168), so both keys work during the grace window and a deployed integration can swap credentials without a hard cut-over. `graceHours: 0` kills the old key immediately. Rotation never extends the old key's life; a pre-existing earlier expiry wins. Emits an `API_KEY_ROTATED` audit event.
Parameters
| id (path) required |
string |
| keyId (path) required |
string |
Request body
Responses
| 201 | New key (plaintext shown once) + old-key grace deadline · object |
| 400 | Key already revoked or expired · Error |
| 404 | Platform or key not found · Error |
Example
curl -X POST https://api.credda.io/api/v1/admin/platforms/{id}/keys/{keyId}/rotate \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/admin/users/{id}/resolve-velocity-flag
admin secret
Clear a velocity flag and recompute
Parameters
| id (path) required |
string |
Responses
Example
curl -X POST https://api.credda.io/api/v1/admin/users/{id}/resolve-velocity-flag \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET"
get
/api/v1/admin/users/{id}/audit-log
admin secret
Paginated audit trail
Parameters
| id (path) required |
string |
Responses
Example
curl https://api.credda.io/api/v1/admin/users/{id}/audit-log \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET"
delete
/api/v1/admin/users/{id}/share-token
admin secret
Withdraw a subject's public trust surface (admin mirror)
Operator-authenticated mirror of DELETE /api/v1/users/{id}/share-token, scoped by the externalId in the URL instead of the platform's own key, for callers that provision keys on a user's behalf and never see the plaintext. Built for cross-service ACCOUNT ERASURE (GDPR Art. 17): the account record lives in the product backend, but the published share token and the credentials issued against it live here. One call does both halves: every public surface that resolves a subject BY share token (verify page, credential, trust export, delivery receipts, OID4VCI issuance) stops resolving immediately, and the subject's StatusList2021 bit at /api/v1/status/revocation flips to revoked, so credentials already in a third party's hands verify as revoked offline. Idempotent: a subject with no token returns 200 with `alreadyRevoked: true`, so a failed revocation can be retried blindly. The response never contains the token. The append-only event ledger is deliberately untouched (its events are also the counterparty's record), and nothing here reads or writes a score.
Parameters
| id (path) required |
string |
Responses
| 200 | Revocation outcome · object |
| 404 | Unknown externalId (USER_NOT_FOUND) · Error |
Example
curl -X DELETE https://api.credda.io/api/v1/admin/users/{id}/share-token \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET"
post
/api/v1/admin/platforms/{id}/webhooks
admin secret
Subscribe an HTTPS endpoint on behalf of a platform (secret returned once)
Parameters
| id (path) required |
string |
Request body
Responses
| 201 | Created · object |
| 400 | Invalid url/events · Error |
| 404 | Platform not found · Error |
Example
curl -X POST https://api.credda.io/api/v1/admin/platforms/{id}/webhooks \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/admin/platforms/{id}/webhooks/{webhookId}/test
admin secret
Send a synthetic signed delivery
Parameters
| id (path) required |
string |
| webhookId (path) required |
string |
Responses
| 200 | Delivery result · object |
| 404 | Platform or webhook not found · Error |
Example
curl -X POST https://api.credda.io/api/v1/admin/platforms/{id}/webhooks/{webhookId}/test \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET"
post
/api/v1/admin/platforms/{id}/webhooks/{webhookId}/deliveries/{deliveryId}/replay
admin secret
Replay a past delivery (admin mirror)
Parameters
| id (path) required |
string |
| webhookId (path) required |
string |
| deliveryId (path) required |
string |
Responses
| 200 | Replay outcome · object |
| 404 | Not found · Error |
| 409 | Payload not retained · Error |
Example
curl -X POST https://api.credda.io/api/v1/admin/platforms/{id}/webhooks/{webhookId}/deliveries/{deliveryId}/replay \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET"
post
/api/v1/admin/platforms/{id}/monitors
admin secret
Create a score monitor on behalf of a platform
Admin mirror of POST /api/v1/monitors: identical schema, conditions and per-tier active-monitor cap (409 MONITOR_LIMIT_REACHED, `details` carries `{limit, tier}`).
Parameters
| id (path) required |
string |
Request body
Responses
| 201 | Created · object |
| 400 | No condition provided / validation error · Error |
| 404 | Platform not found, or unknown userId (USER_NOT_FOUND) · Error |
| 409 | Active monitor limit reached for the plan tier (MONITOR_LIMIT_REACHED) · Error |
Example
curl -X POST https://api.credda.io/api/v1/admin/platforms/{id}/monitors \
-H "X-Admin-Secret: $CREDDA_ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{ ... }'
Programmatic self-administration: manage your OWN platform (identity, API keys, scopes) entirely via the API, no dashboard. Platform-key-authenticated and gated by the `admin` scope; every route is tenant-isolated and a minted key can never exceed the minting key. Nothing here touches a score, a plan, or a trust tier.
post
/api/v1/platform/keys
public
Mint a new API key for your platform (shown once)
Create a key scoped AT OR BELOW the CALLING key: a key can never mint a child more powerful than itself (privilege ceiling). Omit `scopes` to inherit the calling key's scopes (never widened to `*`). A sandbox (test) key can only mint sandbox keys. `expiresInDays` 1–3650 (omitted = never expires). Emits an `API_KEY_GENERATED` audit event visible on GET /activity. Requires the `admin` scope.
Request body
Responses
| 201 | API key (plaintext shown once) · object |
| 401 | Missing/invalid API key · Error |
| 403 | Scope ceiling exceeded (SCOPE_CEILING_EXCEEDED), a test key minting a live key (TEST_MODE_NOT_ALLOWED), or the key lacks the admin scope · Error |
Example
curl -X POST https://api.credda.io/api/v1/platform/keys \
-H "Content-Type: application/json" \
-d '{ ... }'
post
/api/v1/platform/keys/{keyId}/rotate
public
Rotate one of your keys with a grace overlap (new key shown once)
Atomically mint a replacement with the SAME label + scopes + mode and set the old key's expiry to now + graceHours (default 24, clamped 0–168), so both work during the window. graceHours:0 kills the old key at once. Rotation never extends a key's life. Scoped to your platform; a foreign keyId 404s. Emits API_KEY_ROTATED. Requires the `admin` scope.
Parameters
| keyId (path) required |
string |
Request body
Responses
| 201 | New key (plaintext shown once) + old-key grace deadline · object |
| 400 | Key already revoked or expired · Error |
| 401 | Missing/invalid API key · Error |
| 403 | API key lacks the admin scope · Error |
| 404 | Key not found (or belongs to another platform) · Error |
Example
curl -X POST https://api.credda.io/api/v1/platform/keys/{keyId}/rotate \
-H "Content-Type: application/json" \
-d '{ ... }'
Benchmarks
Aggregate cohort comparison: where a score falls relative to a population. Legitimate, ledger-derived cohorts only (never a protected class or demographic proxy); k-anonymity enforced (no statistic below the floor); deterministic and read-only. A distribution fact, never a rating, ranking, or verdict, and nothing here can move a score.
get
/api/v1/users/{id}/benchmark
public
Where this subject sits within a cohort (percentile)
The subject's percentile rank within a legitimate, ledger-derived cohort, plus the cohort's aggregate distribution for context. `?dimension=` (default `all`) selects which cohort; the subject's own record decides which cohort VALUE it belongs to.
**K-anonymity is enforced**: if the cohort has fewer than the floor of subjects it comes back `available:false` (`reason: insufficient_data`) with NO percentile, never a comparison against a tiny group. `available:false` with `reason: no_score` means the subject has no computed score yet. This is the REAL comparison (an actual cross-user distribution), distinct from the deprecated `Score.percentile` (`100 − score`). A percentile is a distribution fact, never a verdict. Read-only; computes nothing that feeds back into a score.
Parameters
| id (path) required |
string |
| dimension (query) |
all | subjectType | verificationDepthBand | activityVolumeBand | tenureBand: Cohort dimension. Default `all`. |
Responses
| 200 | Subject benchmark (may be `available:false`) · object |
| 400 | Unknown cohort dimension · Error |
| 404 | User not found · Error |
Example
curl https://api.credda.io/api/v1/users/{id}/benchmark
get
/api/v1/benchmarks
public
Benchmark catalog (cohort dimensions + k-anonymity floor)
Self-describes the aggregate cohort-comparison surface: the legitimate, LEDGER-DERIVED cohort dimensions (`all`, `subjectType`, `verificationDepthBand`, `activityVolumeBand`, `tenureBand`), the statistics returned, and the **k-anonymity floor** (`kAnonymity.minimumCohortSize`, env `BENCHMARK_MIN_COHORT`, default 20) below which nothing is disclosed. Public and unauthenticated: the same machine-readable source-of-truth shape as `/scoring/model` and `/enums`.
Every cohort dimension is a fact about the RECORD, never a protected class or demographic proxy; the score exists to remove bias, and benchmarking must not reintroduce it. **A benchmark is a distribution fact (where a score falls relative to a population), never a rating, ranking, or verdict on a subject**; see the `disclosures` array. This is the real, distribution-based comparison; the legacy `Score.percentile` field (`100 − score`) is not a cross-user ranking and is deprecated.
Responses
| 200 | Benchmark catalog · object |
Example
curl https://api.credda.io/api/v1/benchmarks
get
/api/v1/benchmarks/distribution
public
Aggregate score distribution for a cohort
Population statistics for a legitimate, ledger-derived cohort: `median`, `mean`, `p25`/`p75`/`p90`, and the per-band histogram. `?dimension=` (default `all`) selects the cohort dimension; `?cohort=` optionally narrows to one cohort value (omit it to get every cohort value on the dimension, each as its own distribution).
**K-anonymity is enforced**: any cohort with fewer than the floor of subjects comes back `available:false` with NO numbers, and the sub-floor count is not disclosed. Raw minimum/maximum scores are never returned; only order statistics over the whole cohort and band-bucket counts. Aggregate-only: no other individual's score is ever returned. Deterministic and read-only; nothing here computes or writes a score. Keyed (`scores:read`) because it scans the population; a test key benchmarks only the test population.
Parameters
| dimension (query) |
all | subjectType | verificationDepthBand | activityVolumeBand | tenureBand: Cohort dimension. Default `all`. |
| cohort (query) |
string: A single cohort value on the dimension. Omit for all values. |
Responses
| 200 | Cohort distribution(s); some may be `available:false` under the k-anonymity floor · object |
| 400 | Unknown cohort dimension · Error |
Example
curl https://api.credda.io/api/v1/benchmarks/distribution
Reference
Machine-readable reference data: the error catalog and the wire enums, both derived from the constants the code enforces so they cannot drift.
get
/api/v1/errors
public
Machine-readable error catalog
Every stable error `code` the API can return, each with an HTTP status, a title, what happened, what to do about it, and a `retryable` flag. Also describes the error envelope (`{error, code, requestId, retryable, details?}`) and the retry contract. Public and unauthenticated.
This is the SAME catalog the errors themselves are built from and that the `x-error-codes` extension is derived from, so a code can never be returned undocumented; the server will not compile if a route throws one that is missing here. Retry only codes with `retryable: true`, and honour `Retry-After` when present.
Responses
| 200 | Error catalog · object |
Example
curl https://api.credda.io/api/v1/errors
get
/api/v1/enums
public
Self-describing enums
Every closed value set on the wire: `eventType`, `stakeLevel`, `scoreBand`, `disputeStatus`, `platformTier`, with a human description per value and the facts that matter (stake weights, band score floors, platform trust multipliers, and whether an event type may be reported directly). Public and unauthenticated.
Derived from the Prisma enums, the zod ingestion schema and the formula constants the code actually enforces, so it cannot drift from what is validated. Use it to build pickers and validators without hard-coding the lists.
Responses
Example
curl https://api.credda.io/api/v1/enums
get
/api/v1/outcome-templates
public
Industry outcome templates: map real-world work to events
How a real-world business maps its work to Credda events. Covers professionals across many fields: trades (including licensed trades), home & personal care, clinical & healthcare, hospitality, retail, logistics, transportation & freight, warehouse & production, cleaning, landscaping, staffing & shift work, automotive repair, childcare, security, events & production, veterinary, professional/legal/finance/consulting services, education, creative & design, engineering, real estate, beauty & wellness, and software, and for each names the concrete outcomes that matter, the exact ingest `eventType` to report, a suggested stake, and, the load-bearing part, WHO the third-party witness is and how they confirm. The live catalog is the source of truth; query it for the current industry list.
Each template also carries `witnessType` (which of four kinds of third party confirms it; descriptive only, never a weighting) and `timingBasis` (what `dueDate` and `completedAt` mean for that outcome, in the terms the business already uses). The catalog-level `fieldGuide` states the same mapping once per event field. This matters because on-time rate is a large share of the score and is derived from those two timestamps alone: an outcome reported without them can count as completed, never as completed **on time**.
`notTheWorkersOutcome` names the real-world failure metrics that describe an action taken by somebody OTHER than the worker: a patient who did not attend, a delivery where nobody was home, a shift the business itself cancelled, weather, and must not be recorded as a negative event against a worker. Guidance, not enforcement: Credda cannot see why a booking was missed, which is why the boundary is stated rather than assumed.
Guidance only, same shape as `/plans` and `/enums`: it writes nothing, reads nothing, and cannot touch a score. A template never sets verification; an outcome counts as verified only when the named witness confirms it. Every template maps to a real `INGEST_EVENT_TYPES` value (compile-time enforced). Public and unauthenticated. `?industry=` filters to one set.
Parameters
| industry (query) |
string: Filter to one industry slug (e.g. `dental_clinical`, `trades_home_services`). |
Responses
| 200 | Outcome template catalog · object |
Example
curl https://api.credda.io/api/v1/outcome-templates
get
/api/v1/changelog
public
API version contract, changelog and deprecation policy
The version contract for `v1` as data, plus every dated change to this surface. Public and unauthenticated.
`versioning` states exactly what **additive-only** guarantees: what can appear without notice (new endpoints, new response fields, new optional inputs, new enum values, new error codes, new webhook event types) and what would require a new major version (removals, renames, type or meaning changes, a newly-required field, a changed auth scheme). It also carries `componentVersions`: the scoring formula, reason codes, earnings, professional record, verified profile and benchmark versions, each imported from the module that owns it.
`deprecations` lists anything scheduled for removal, with its replacement and sunset date; it is **empty**, because nothing in v1 has ever been deprecated. When something is, it is announced here at least 180 days ahead and the affected endpoint additionally answers with `Deprecation` (RFC 9745) and `Sunset` (RFC 8594) headers.
`entries` are newest-first, each with a date (the release date; this service deploys on merge), a category (`added` / `changed` / `deprecated` / `fixed` / `security`), a one-line summary and the endpoints it touches. A test validates every referenced endpoint against this OpenAPI document, so an entry cannot describe a route that does not exist.
Responses
| 200 | Version contract + changelog · object |
Example
curl https://api.credda.io/api/v1/changelog
get
/api/v1/reason-codes
public
Adverse-action reason-code catalog
The stable, versioned meaning of every reason code the scoring explanation can attribute to a record, with a plain-language consumer-facing description per code, a `factor` and a `direction` (`adverse` / `supporting`). Public and unauthenticated.
Built for **B2B2C partners** (e.g. a cash-advance app or payroll-API vendor) that must issue an ECOA / Regulation B (12 CFR 1002.9(b)(2)) statement of *specific* principal reasons when they take an adverse action. A subject's ranked reason codes are returned on `GET /api/v1/users/{id}/score/explain` (the additive `reasonCodes` object); the partner draws its statement of reasons from the `adverseActionReasons`, ranked by importance-weighted contribution, taking the top `keyFactorLimit` (the FCRA / Reg B four-reason convention; not hard-capped, so a real reason is never hidden to hit a number).
Every code is DETERMINISTIC factor attribution over facts the pure formula already produces, reproducible from the append-only ledger, versioned by `reasonCodesVersion` so a code never changes meaning under a partner. **Credda is not a creditor and is not a consumer reporting agency; it renders no decision and issues no notice; the adverse action and its consumer notice belong solely to the partner.** See the `disclosures` array.
Responses
| 200 | Reason-code catalog · object |
Example
curl https://api.credda.io/api/v1/reason-codes