{"apiVersion":"v1","note":"Every entry corresponds to a real merge into the API repository; the date is the release date, because this service deploys on merge. Endpoint references are validated against the served OpenAPI document by a test, so an entry cannot describe an endpoint that does not exist.","versioning":{"version":"v1","scheme":"The major version is in the URL path (`/api/v1/...`). There is no version header and no per-account pinned version: every caller is on the same v1 surface, and v1 only ever grows.","guarantee":"v1 is additive-only. A request that works today will keep working, unchanged, for as long as v1 is served. New capability arrives as new endpoints, new optional inputs and new response fields, never by changing or removing something you already depend on.","additive":["New endpoints, and new methods on an existing path.","New fields on a response body. Ignore fields you do not recognise; do not validate responses as a closed schema.","New OPTIONAL request fields, query parameters and headers. Existing requests keep their existing meaning.","New values in a closed enum (event types, score bands, dispute statuses). Read the live set from GET /api/v1/enums and tolerate a value you have not seen.","New error `code` values. Read the live set from GET /api/v1/errors; treat an unknown code by its HTTP status and its `retryable` flag.","New outbound webhook event types. Read the live set from GET /api/v1/webhooks/events and ignore types you have not subscribed to.","New response HEADERS, including the deprecation signalling described below.","Relaxing a validation rule, or raising a limit."],"breaking":["Removing or renaming an endpoint, a request field, or a response field.","Changing the type of an existing field, or the meaning of an existing value.","Making a previously optional request field required, or tightening validation so a previously accepted request is rejected.","Changing the HTTP status or the meaning of an existing error `code`.","Changing the authentication scheme, or what an existing scope grants.","Removing a webhook event type, or changing the shape of an existing payload."],"behaviourVersions":"The API SHAPE is versioned by `v1`; the things the API computes carry their OWN versions, which travel in every payload that depends on them. A formula change does not break a request, but it does change the number you read back, so it is always recorded in this changelog under `changed`, and `GET /api/v1/scoring/model` publishes the model itself so a score can be independently recomputed.","componentVersions":{"api":"v1","scoringFormula":"5.3","reasonCodes":"1.0","earnings":"1.0","professionalRecord":"1.1","verifiedProfile":"1.0","benchmarks":"1.0"},"nextMajorVersion":"If a breaking change ever becomes unavoidable it ships as a NEW path prefix (`/api/v2/...`) served ALONGSIDE v1, never as a change to v1. v1 would then be deprecated as a whole under the notice policy below, with per-endpoint `Deprecation` and `Sunset` headers and a migration note in this changelog. No v2 is planned.","deprecation":{"minimumNoticeDays":180,"announcement":"A deprecation is announced as a `deprecated` entry in this changelog, with the endpoint(s) affected, the replacement and the sunset date, at least 180 days before that date.","headers":"A deprecated endpoint additionally answers with `Deprecation` (RFC 9745: a structured-field date, `@<unix seconds>`, when the deprecation took effect) and `Sunset` (RFC 8594: an HTTP-date after which the endpoint may stop responding), plus `Link` relations `deprecation` and `sunset` pointing at the notice. Nothing is deprecated today, so no response carries these headers.","behaviourUnchanged":"Deprecation changes nothing about how an endpoint behaves. Until its sunset date it answers exactly as before; the headers are a schedule, not a degradation.","activeCount":0}},"deprecations":[],"categories":["added","changed","deprecated","fixed","security"],"latestChange":"2026-07-25","count":87,"entries":[{"id":"2026-07-25-events-verified-default-false","date":"2026-07-25","category":"fixed","summary":"POST /api/v1/events and POST /api/v1/events/batch no longer treat an OMITTED isVerified as verified. The flag was read as \"isVerified ?? true\", so a caller that simply never sent the key had every event recorded as third-party verified: verification minted by omission, with no witness anywhere in the flow. Omitting the field now records the event self-attested (isVerified false), matching what POST /api/v1/ingest and POST /api/v1/imports have always done. Only an explicit true is a request to verify, and it is still reduced to false for an agent or business subject whose own operator/controller is reporting it. This is a behaviour change for any integrator that relied on the old default: those events now count as activity rather than as trust evidence, which lowers verification depth until a counterparty confirms them. To have verification EARNED rather than asserted, use POST /api/v1/confirmations. Nothing already written to the ledger was altered; the ledger is append-only and no score was recomputed by this change alone.","endpoints":["/api/v1/events","/api/v1/events/batch"],"reference":"claude/wf-0-events-default-unverified"},{"id":"2026-07-24-book-null-score","date":"2026-07-24","category":"fixed","summary":"GET /api/v1/users no longer reports a placeholder score for a subject the engine has not scored yet. Those rows previously came back finalScore 50 / scoreBand \"Fair\", a number the v5.3 formula does not hand a new subject (a real new record starts near 20, \"Unproven\"), and it contradicted the endpoint's own filtering, since a NULL score satisfies no SQL comparison and those subjects were already excluded by any scoreMin/scoreMax/band filter while an unfiltered page presented them as scored. finalScore and scoreBand are now null for an unscored subject, and the CSV export leaves both cells empty. Use the new hasScore filter to list them in or out deliberately.","endpoints":["/api/v1/users"],"reference":"claude/data-surfaces"},{"id":"2026-07-24-book-segment-summary","date":"2026-07-24","category":"added","summary":"GET /api/v1/users/summary sizes and shapes a segment of your book without paging it: how many subjects match, how many are scored, their band mix and median/mean. It takes the same closed filter set as GET /api/v1/users and is built from the identical tenant-scoped query, so it can never count a subject the listing would not show you. central.median/mean are null (never 0) when nothing in the segment is scored, and an oversized population returns the exact matched count with null aggregates plus an aggregationSkipped reason rather than a partial aggregate presented as a whole-segment one. Read-only under scores:read.","endpoints":["/api/v1/users/summary"],"reference":"claude/data-surfaces"},{"id":"2026-07-24-book-filters","date":"2026-07-24","category":"added","summary":"Four new filters on the book query, all over columns that already existed and all still part of the CLOSED filter set (there is deliberately no query DSL; the closed set is what lets the tenant scope be guaranteed for every combination). hasScore selects computed vs not-yet-scored subjects; scoreFrozen selects subjects whose score is frozen by a velocity flag; registeredSince/registeredBefore bound when a subject first appeared in your ledger. subjectType also accepts ORGANIZATION; businesses have been first-class scored subjects since /businesses shipped and were returned in book rows while being un-filterable. hasScore=false with a score range or band is refused as contradictory rather than returning a confidently empty page.","endpoints":["/api/v1/users","/api/v1/users/summary"],"reference":"claude/data-surfaces"},{"id":"2026-07-24-analytics-days-validation","date":"2026-07-24","category":"fixed","summary":"GET /api/v1/analytics/events and /analytics/scores now validate the days parameter instead of silently coercing it. Any unparseable value (days=abc, days=0, days=-5) previously fell back to the default 30-day window, so a caller who mistyped their window received data they had not asked for with no signal. Those now return 400 ANALYTICS_RANGE_INVALID; an empty ?days= is still treated as absent, and an over-large value is still clamped to ANALYTICS_MAX_DAYS. The score-analytics snapshot read was also rewritten from an id list to a relation filter, which removes a hard failure for any platform whose book exceeds PostgreSQL's bind-parameter limit.","endpoints":["/api/v1/analytics/events","/api/v1/analytics/scores"],"reference":"claude/data-surfaces"},{"id":"2026-07-24-outcome-templates-vertical-fit","date":"2026-07-24","category":"changed","summary":"Outcome templates v1.2: the catalog now fits work that is not software. Eight new industries (home & personal care, automotive repair, transportation & freight, warehouse & production, landscaping & grounds, veterinary, retail, events & production), and every template gained timingBasis (what dueDate and completedAt mean for that outcome, in the terms the business already uses) plus witnessType, a closed four-value classification of the confirming third party, descriptive only, never a weighting. New catalog-level fieldGuide, and notTheWorkersOutcome: the failure metrics that describe an action taken by somebody other than the worker (a patient who did not attend, a shift the business cancelled, weather) and must not be recorded against a worker. Timing was the load-bearing gap: on-time rate is derived from two timestamps alone, so an outcome reported without them could never count as completed on time. Guidance only: still writes nothing, scores nothing, sets no verification.","endpoints":["/api/v1/outcome-templates"],"reference":"#356"},{"id":"2026-07-24-confirmation-reminders","date":"2026-07-24","category":"added","summary":"Confirmation reminders: two new webhook events, confirmation.awaiting_response and confirmation.expiring_soon, fire through the existing delivery pipeline (webhooks + notification channels + the SSE stream) when a confirmation request you created is still PENDING past a reminder threshold (CONFIRMATION_REMINDER_AFTER_HOURS, default 48h) or is approaching its expiry (CONFIRMATION_EXPIRING_WITHIN_HOURS, default 24h). An hourly sweep selects them; each stage fires at most once per request, deduped in Redis. The reminder goes to the REQUESTING PLATFORM, never to the counterparty; counterpartyRef is opaque, is echoed back verbatim and is resolved to nobody, and Credda still delivers nothing to anyone. The raw token is never included (only its hash is stored), so the payload carries confirmUrl/previewUrl templates with a {confirmationToken} placeholder to substitute. Notification only: the sweep writes nothing to the ledger or to the request, and a pending or lapsed request still records no event.","endpoints":["/api/v1/webhooks/events","/api/v1/confirmations"],"reference":"claude/confirmation-reminders"},{"id":"2026-07-24-reliability-at-dispatch","date":"2026-07-24","category":"added","summary":"Reliability at dispatch: the compact hot-path record read. GET /api/v1/users/{id}/reliability?context=dispatch answers the one question a staffing or marketplace platform asks before assigning a shift, in a sub-1KB payload: 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. READ-ONLY: every value is either already stored on the latest ScoreSnapshot or a plain count over the append-only ledger; nothing computes or writes a score, calculateCreddaScore stays 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). Two queries: the snapshot plus one grouped event aggregate. An unresolved TRANSACTION_DISPUTED is deliberately NOT counted as a breach: an accusation is not an outcome against the subject until the dispute resolves, mirroring how the formula treats resolution rather than filing. EVIDENCE, NEVER A VERDICT: no field carries fitness or decision language (a test scans both the payload and the module source), and the reference states plainly that a partner using this read to SELECT workers may bring FCRA into scope; Credda reports the record, the platform owns the decision. context defaults to dispatch; an unrecognised value is a 400 (new code DISPATCH_CONTEXT_INVALID) rather than a silently different projection. Gated on the existing scores:read scope; no new scope resource and no schema change.","endpoints":["/api/v1/users/{id}/reliability"],"reference":"claude/dispatch-read"},{"id":"2026-07-24-confirmations-free-every-tier","date":"2026-07-24","category":"changed","summary":"Counterparty confirmations are now FREE at every tier. POST /api/v1/confirmations (and the batch, list, get, cancel surfaces) no longer require the events scope: any valid platform key, including a read-only Starter key, can propose an outcome and hand the one-time token to the named counterparty. Rationale: the counterparty-confirmed event is the moat, so the ASK is never paywalled; creating a request writes nothing (no event, no score), and an event is recorded only when the counterparty affirmatively confirms, strictly safer than the events:write path because a third party is the witness by construction. Rate limits and the monthly quota still apply. Bulk fan-out (activation campaigns) remains a Growth+ convenience. Backward-compatible: keys that carried the events scope work exactly as before.","endpoints":["/api/v1/confirmations","/api/v1/confirmations/batch"],"reference":"claude/free-confirmations"},{"id":"2026-07-24-activation-campaigns","date":"2026-07-24","category":"added","summary":"Activation campaigns: the confirmation primitive at scale. POST /api/v1/activation/campaigns lets a platform submit its whole historical book (roster / timesheets, up to 500 rows) in one call; each row becomes an UNCONFIRMED ConfirmationRequest (a proposed outcome plus a one-time token), fanned out to its named counterparty, turning \"manually create 200 confirmations\" into a single request. GET /api/v1/activation/campaigns/{id} reports the funnel (submitted → confirmed / declined / pending) derived live from the linked requests. INVARIANT: a campaign writes NO events: every row flows through the same createConfirmationRequest → respondToConfirmation → event.service.createEvent path a single confirmation uses, so isVerified is earned only on a genuine counterparty confirm, never hardcoded here, and calculateCreddaScore stays the sole snapshot writer. Partial success per row (a self-confirmation → CONFIRMATION_SELF fails alone); idempotent per (campaign, rowKey) plus whole-batch Idempotency-Key; over-cap or all-rows-invalid is a 400 (new code ACTIVATION_NO_VALID_ROWS). Test-mode isolated. Gated on the existing events scope, no new scope resource. TS SDK createActivationCampaign/getActivationCampaign.","endpoints":["/api/v1/activation/campaigns","/api/v1/activation/campaigns/{id}"],"reference":"claude/activation-campaigns"},{"id":"2026-07-24-event-stream-sse","date":"2026-07-24","category":"added","summary":"Real-time event stream (Server-Sent Events): GET /api/v1/events/stream holds one long-lived connection and streams the SAME event envelopes Credda POSTs to webhooks, in real time, so integrators can drop a webhook receiver or stop busy-polling. Optional ?types= filter (unknown type is a 400), periodic heartbeat comments, id: lines + Last-Event-ID resume from a short Redis buffer (best-effort, at-most-once past the window), and correct teardown on client disconnect. Fanned out over Redis pub/sub from the existing dispatchWebhookEvent point, so it inherits every event type for free. Test/live isolation rides the envelope's own livemode. Gated under the existing webhooks scope (no new scope resource); notification only: it reads and writes nothing score-side.","endpoints":["/api/v1/events/stream"],"reference":"claude/sse-and-job-webhooks"},{"id":"2026-07-24-job-completion-events","date":"2026-07-24","category":"added","summary":"Job-completion webhook events: import.completed and screening.completed fire through the existing delivery pipeline (webhooks + notification channels + the SSE stream) when an async historical import or bulk screening job reaches a terminal state (COMPLETED with counts, or FAILED), so integrators can stop busy-polling GET /api/v1/imports/{id} and GET /api/v1/screenings/{id} for status. Advisory only: an import records events through the normal ingestion path and screening only reads scores; neither notification writes a score.","endpoints":["/api/v1/webhooks/events"],"reference":"claude/sse-and-job-webhooks"},{"id":"2026-07-24-notification-channels","date":"2026-07-24","category":"added","summary":"Notification channels (Slack + email): a partner who runs no webhook receiver can now still be notified. POST/GET/DELETE /api/v1/notification-channels registers a Slack incoming-webhook URL or an email address; when a monitor/policy/score/dispute/quota event fires through the existing delivery path it is ALSO fanned out to the platform's active channels: fire-and-forget, never awaited, never throws, carrying the SAME event envelope webhooks send. Slack targets are pinned to the hooks.slack.com host (SSRF-safe, redirect not followed) and masked in responses; email delivery is inert until SMTP is configured (no SMTP dependency ships). Gated under the existing webhooks scope (no new scope resource), test-mode isolated, inert by default. A channel writes nothing score-side; calculateCreddaScore stays the sole snapshot writer.","endpoints":["/api/v1/notification-channels","/api/v1/notification-channels/{id}"],"reference":"claude/notification-channels"},{"id":"2026-07-24-worker-reliability-report","date":"2026-07-24","category":"added","summary":"Worker Reliability Report: a one-call decision-support dossier that aggregates everything already computed about a subject (reliability score/band/confidence, the buyer's reliability metrics (completion, on-time, consistency, recency, dispute), verified experience + tenure, the ranked reason-code drivers, the last N confirmed outcomes (each flagged verified vs self-reported), and an optional coarse benchmark) into a single read. Pure aggregation: it computes no new score and calculateCreddaScore stays the sole snapshot writer. A worker-consent variant lets a worker hand a prospective employer the same dossier via a share token, no API key. It is evidence a reader weighs against their own criteria, never a hire/place/rank verdict; no verdict language, always-present FCRA/no-verdict disclosures.","endpoints":["/api/v1/users/{id}/reliability-report","/api/v1/verify/{token}/reliability-report"],"reference":"#332"},{"id":"2026-07-24-confirmations-batch","date":"2026-07-24","category":"added","summary":"Batch confirmation requests: the activation engine. POST /api/v1/confirmations/batch turns a platform's BOOK of historical relationships (past jobs, placements, engagements, projects, for a professional in ANY field) into up to 100 PENDING counterparty-confirmation requests in one call, warming a cold ledger with real asks. Each item flows through the same single-endpoint create service, so isVerified is still earned in exactly one place (on confirm) and never hardcoded; a batch item writes NOTHING to the ledger; importing a book creates pending asks, it does not manufacture verified events, and each event lands only when its named counterparty confirms. Partial success per item, over-cap is a 400, and an optional Idempotency-Key header makes a retried batch exactly-once. TS SDK: createConfirmationBatch.","endpoints":["/api/v1/confirmations/batch"],"reference":"#333"},{"id":"2026-07-24-industry-outcome-templates","date":"2026-07-24","category":"added","summary":"Industry outcome templates: a public catalog mapping real-world work (dental, trades, hospitality, logistics, cleaning, staffing, professional services, software) to the exact ingest event type to report, a suggested stake, and (the load-bearing part) who the third-party witness is for each outcome. Guidance only, same shape as /plans and /enums: it writes nothing, scores nothing, and ranks no one; a template never sets verification (only the named witness confirming does). Makes the ledger legible to a non-technical business.","endpoints":["/api/v1/outcome-templates"],"reference":"#325"},{"id":"2026-07-24-career-export-json-resume","date":"2026-07-24","category":"added","summary":"Career export: the subject's whole verified professional record: reliability + verified experience + tenure + itemised qualifications, serialised in the open JSON Resume schema (jsonresume.org) so it drops into an ATS/HRIS or résumé tool without a bespoke Credda integration. Every item is flagged verified vs self-reported and verified items anchor to a public proof URL, so the document transports third-party verification in a shape existing tools already accept. Read-only; describes a record, never a hiring verdict; the score is untouched.","endpoints":["/api/v1/users/{id}/career-export","/api/v1/verify/{token}/career-export"],"reference":"#324"},{"id":"2026-07-24-reference-requests","date":"2026-07-24","category":"added","summary":"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 actually there (a past employer, manager, client, colleague, or issuing body) confirm it through a one-time token, turning a self-attested claim VERIFIED. Includes a hosted \"Confirm a reference\" page at /reference/{id} (no integration work, zero JavaScript). On confirm the qualification is recorded through the same witness valve every qualification ingress uses, so isVerified is earned in one place and never hardcoded; a pending/declined/expired/cancelled request records nothing, and a qualification never moves the reliability score.","endpoints":["/api/v1/references","/api/v1/references/{id}","/api/v1/references/{id}/cancel","/api/v1/references/{id}/preview","/api/v1/references/{id}/respond"],"reference":"#326"},{"id":"2026-07-24-qualifications-bulk-import","date":"2026-07-24","category":"added","summary":"Bulk-import a claimed professional record (education, employment, skills, certifications) in one call: the onboarding accelerator. Structured input only: no AI and no free-text résumé parsing on the scoring API. Every claim flows through the same single-claim writer, so isVerified is decided by the witness rule in one place and is never hardcoded. A self-import with no witnesses records every claim self-attested, which LOWERS verification depth until each is independently confirmed. Partial-success per item; writes nothing score-side.","endpoints":["/api/v1/users/{id}/qualifications/import"],"reference":"#323"},{"id":"2026-07-23-business-subjects","date":"2026-07-23","category":"added","summary":"Businesses as first-class scored subjects. A company holds its own portable record, built from outcomes ANOTHER business confirmed, and runs the identical deterministic formula: no business score, no sector weighting. One extra integrity rule: a business can never confirm its own outcomes, including by naming itself as its own counterparty under any alias. Business-to-business only: Credda records no customer/consumer register, and a business's record is never derived from the people who work there.","endpoints":["/api/v1/businesses","/api/v1/businesses/{id}","/api/v1/businesses/{id}/record","/api/v1/verify/{token}/business-record"],"reference":"#310"},{"id":"2026-07-23-subject-type-enum","date":"2026-07-23","category":"changed","summary":"The `subjectType` vocabulary is now published at GET /api/v1/enums and gained ORGANIZATION alongside PERSON and AGENT. It is a label, never a second scoring system; every subject type runs the identical formula. Another example of why a client must tolerate an enum value it has not seen.","endpoints":["/api/v1/enums","/api/v1/businesses"],"reference":"#310"},{"id":"2026-07-23-personal-projects-and-professional-profile","date":"2026-07-23","category":"added","summary":"Personal projects: link a repo, live URL or case study on a professional profile (recorded self-reported, verified only with a third-party witness; the link is stored, never fetched). Plus a consolidated, consent-gated professional-profile read on the share token (`GET /verify/{token}?profile=1`): reliability + verified qualifications + linked projects, each flagged verified vs self-reported, with the FCRA/no-verdict disclosures in-band. The `eventType` enum gained PROJECT_LOGGED, never a scoring input.","endpoints":["/api/v1/users/{id}/projects","/api/v1/verify/{token}"],"reference":"#298"},{"id":"2026-07-23-qualification-jurisdiction","date":"2026-07-23","category":"added","summary":"A qualification claim can carry `jurisdiction` and `reference`: the place a licence applies and the issuing body's own number for it, so a trade or professional licence is expressible instead of being flattened into free text. Both are display-only properties of the credential, are never stored on the subject, and are never scored or ranked; the verified-profile derivation is unchanged, so its version stays 1.0.","endpoints":["/api/v1/users/{id}/qualifications"],"reference":"#310"},{"id":"2026-07-23-verified-profile","date":"2026-07-23","category":"added","summary":"Verified Profile: report education, skill, certification and employment claims and read back how much of the claimed record is independently verified. A separate measure from the Reliability Score that can never move it; verification depth is counted, prestige is never ranked.","endpoints":["/api/v1/users/{id}/qualifications","/api/v1/users/{id}/verified-profile"],"reference":"#295"},{"id":"2026-07-23-qualification-event-types","date":"2026-07-23","category":"changed","summary":"The `eventType` enum gained four qualification values (EDUCATION_VERIFIED, SKILL_VERIFIED, CERTIFICATION_VERIFIED, EMPLOYMENT_VERIFIED). They are reported through the qualifications endpoint, never POST /events, and they are not scoring inputs: an example of why a client must tolerate an enum value it has not seen.","endpoints":["/api/v1/enums","/api/v1/users/{id}/qualifications"],"reference":"#295"},{"id":"2026-07-23-hosted-confirm-page","date":"2026-07-23","category":"added","summary":"Hosted \"Confirm with Credda\" page at /confirm/{id}: a counterparty can answer a confirmation request in a browser with no integration work, and POST /api/v1/confirmations gained an optional validated https `returnUrl` to send them back afterwards.","endpoints":["/api/v1/confirmations","/api/v1/confirmations/{id}/preview","/api/v1/confirmations/{id}/respond"],"reference":"#296"},{"id":"2026-07-23-professional-record","date":"2026-07-23","category":"added","summary":"Professional Record: a worker-owned, résumé-shaped summary of a verified reliability record, plus a signed offline-verifiable credential over it.","endpoints":["/api/v1/users/{id}/professional-record","/api/v1/users/{id}/professional-record/credential"],"reference":"#291"},{"id":"2026-07-23-confirmation-requests","date":"2026-07-23","category":"added","summary":"Confirmation requests: propose an outcome and have a DISTINCT counterparty confirm it through a one-time token, so `isVerified` is earned by a third-party witness instead of asserted by the reporting platform. A pending request writes no event.","endpoints":["/api/v1/confirmations","/api/v1/confirmations/{id}","/api/v1/confirmations/{id}/cancel","/api/v1/confirmations/{id}/preview","/api/v1/confirmations/{id}/respond"],"reference":"#289"},{"id":"2026-07-23-book-of-subjects","date":"2026-07-23","category":"added","summary":"GET /api/v1/users: query and export your own book of subjects (filters, cursor pagination, CSV), so a platform can get its data out without iterating ids it has to remember.","endpoints":["/api/v1/users"],"reference":"#288"},{"id":"2026-07-23-benchmarks","date":"2026-07-23","category":"added","summary":"Benchmarks: aggregate cohort distributions and a subject percentile, over ledger-derived cohorts only, with a k-anonymity floor below which nothing is disclosed. A distribution fact, never a rating.","endpoints":["/api/v1/benchmarks","/api/v1/benchmarks/distribution","/api/v1/users/{id}/benchmark"],"reference":"#287"},{"id":"2026-07-23-partner-attribution","date":"2026-07-23","category":"added","summary":"Partner attribution on platform provisioning plus a consolidated admin platform view, so a reseller can see the platforms it introduced.","endpoints":["/api/v1/admin/platforms"],"reference":"#286"},{"id":"2026-07-23-threshold-policies","date":"2026-07-23","category":"added","summary":"Threshold policies: declarative rules that fire a `policy.threshold_crossed` webhook when a subject crosses a score, component, verified-event or band line. Credda supplies deterministic evidence and a decision_input block; the decision stays with the partner.","endpoints":["/api/v1/policies","/api/v1/policies/{id}","/api/v1/webhooks/events"],"reference":"#284"},{"id":"2026-07-23-webhook-ssrf-fix","date":"2026-07-23","category":"security","summary":"Webhook delivery targets are validated against private and link-local address ranges (SSRF), and flagged dependency advisories were remediated.","endpoints":["/api/v1/webhooks"],"reference":"#282"},{"id":"2026-07-23-reason-codes","date":"2026-07-23","category":"added","summary":"Adverse-action reason codes: GET /api/v1/users/{id}/score/explain now returns ranked `reasonCodes`, and GET /api/v1/reason-codes publishes their stable, separately-versioned meaning, so a partner can meet its own Regulation B obligations. Credda renders no decision and issues no notice.","endpoints":["/api/v1/users/{id}/score/explain","/api/v1/reason-codes"],"reference":"#280"},{"id":"2026-07-23-quota-defaults","date":"2026-07-23","category":"changed","summary":"Monthly volume quotas now have committed per-tier defaults instead of being inert unless a `QUOTA_TIER_*` environment value was set. The free/self-reported tier stays uncapped by default, and env still overrides in both directions.","endpoints":["/api/v1/usage","/api/v1/plans"],"reference":"#278"},{"id":"2026-07-23-open-badges","date":"2026-07-23","category":"added","summary":"Open Badges 3.0 issuance: a publicly readable, closed allowlist of achievements this issuer will sign, plus the operator endpoint that attaches the proof. A badge records an outcome already on the ledger.","endpoints":["/api/v1/open-badges/achievements","/api/v1/open-badges/achievements/{badgeId}","/api/v1/admin/open-badges"],"reference":"#269"},{"id":"2026-07-23-transport-hardening","date":"2026-07-23","category":"security","summary":"HSTS raised to one year with includeSubDomains, access logging added with bearer-capability redaction (share tokens travel in the URL path and are never written down), and audit entries gained full actor attribution.","reference":"#268"},{"id":"2026-07-23-web-bot-auth","date":"2026-07-23","category":"added","summary":"Outbound webhook deliveries are additionally signed with Web Bot Auth (RFC 9421 HTTP Message Signatures over Ed25519), and the signing keys are published at a well-known signature directory, so a receiver's WAF can verify the traffic is a known agent. The existing HMAC signature is unchanged.","endpoints":["/.well-known/http-message-signatures-directory"],"reference":"#267"},{"id":"2026-07-23-recent-deliveries","date":"2026-07-23","category":"added","summary":"GET /api/v1/webhooks/deliveries: a recent-events list for automation platforms (Zapier/Make) that need sample data to build a trigger. Falls back to labelled examples when there are no real deliveries yet; the `source` field says which you are looking at.","endpoints":["/api/v1/webhooks/deliveries"],"reference":"#266"},{"id":"2026-07-23-oid4vci","date":"2026-07-23","category":"added","summary":"Wallet-compatible issuance: OID4VCI 1.0 pre-authorized code flow with SD-JWT VC (`dc+sd-jwt`) selective disclosure, issuer and authorization-server metadata, and a relying-party presentation verifier. A holder can keep a Credda credential in any OID4VCI wallet.","endpoints":["/.well-known/openid-credential-issuer","/.well-known/oauth-authorization-server","/api/v1/users/{id}/credential-offer","/oid4vci/token","/oid4vci/nonce","/oid4vci/credential","/api/v1/verify/presentation"],"reference":"#262"},{"id":"2026-07-23-error-catalog-and-enums","date":"2026-07-23","category":"added","summary":"GET /api/v1/errors (every stable error code, with what to do and whether a retry can help) and GET /api/v1/enums (every closed wire value set), both derived from the constants the code enforces. Error codes became a closed compile-time union, so an undocumented code can no longer ship.","endpoints":["/api/v1/errors","/api/v1/enums"],"reference":"#259"},{"id":"2026-07-23-request-id-on-429","date":"2026-07-23","category":"fixed","summary":"Rate-limited (429) responses bypassed the error pipeline and carried no `requestId`; two other inline responses did the same. Every error response now carries the full envelope, and `Retry-After` is honoured by the TypeScript SDK's auto-retry.","reference":"#259"},{"id":"2026-07-23-projection-breach-and-monitor-caps","date":"2026-07-23","category":"fixed","summary":"The what-if projection could not model CONTRACT_BREACHED (the strongest negative outcome) because it carried its own hand-written event-type list. Monitor caps also became mode-scoped, so a sandbox monitor no longer consumes a paid live slot.","endpoints":["/api/v1/users/{id}/score/project","/api/v1/monitors"],"reference":"#260"},{"id":"2026-07-22-agent-subjects","date":"2026-07-22","category":"added","summary":"Agent subjects and counterparty-confirmed delivery receipts: a non-human subject runs the identical deterministic formula, with one extra rule: an agent's own operator can never be the witness for its work. A delivery record, never a trustworthiness verdict.","endpoints":["/api/v1/agents","/api/v1/agents/{id}","/api/v1/verify/{token}/delivery-receipts"],"reference":"#258"},{"id":"2026-07-22-ingest-and-imports","date":"2026-07-22","category":"added","summary":"Field-mapping ingest (POST your own payload shape alongside a declarative mapping) and async CSV import for historical backfill. Both write through the same append-only path as POST /events; the mapping DSL has no expression or eval escape hatch, by design.","endpoints":["/api/v1/ingest","/api/v1/ingest/mappings","/api/v1/ingest/mappings/{id}","/api/v1/imports","/api/v1/imports/{id}","/api/v1/imports/{id}/errors"],"reference":"#257"},{"id":"2026-07-22-verified-earnings","date":"2026-07-22","category":"added","summary":"Verified Earnings: monthly buckets, stability metrics and platform breakdown derived from income already recorded on the ledger, plus a signed earnings credential. Only verified outcomes attest; unverified activity is reported separately and never blended.","endpoints":["/api/v1/users/{id}/earnings","/api/v1/users/{id}/earnings/summary","/api/v1/users/{id}/earnings/credential"],"reference":"#256"},{"id":"2026-07-22-csv-injection-guard","date":"2026-07-22","category":"security","summary":"CSV exports escape leading `=`, `+`, `-` and `@` so a cell cannot execute as a formula when the file is opened in a spreadsheet.","endpoints":["/api/v1/usage","/api/v1/events/export"],"reference":"#255"},{"id":"2026-07-22-admin-monitors-mirror","date":"2026-07-22","category":"added","summary":"Admin-authenticated mirror of the monitor endpoints, scoped by platform id, so a dashboard can manage monitors on a platform's behalf without holding its bearer key.","endpoints":["/api/v1/admin/platforms/{id}/monitors","/api/v1/admin/platforms/{id}/monitors/{monitorId}"],"reference":"#253"},{"id":"2026-07-22-test-mode","date":"2026-07-22","category":"added","summary":"Sandbox test mode: `crd_test_` keys alongside live keys, free at every tier, with a per-platform isolated test universe, `livemode:false` on webhook deliveries, and DELETE /api/v1/test/data to reset it. Test data can never become portable trust.","endpoints":["/api/v1/test/data"],"reference":"#251"},{"id":"2026-07-22-official-pricing","date":"2026-07-22","category":"changed","summary":"GET /api/v1/plans now carries an official `priceUsdMonthly` per tier and reports `pricing: \"official\"`. Self-serve checkout is not live; no plan can move anyone's score.","endpoints":["/api/v1/plans"],"reference":"#248"},{"id":"2026-07-22-bulk-screening","date":"2026-07-22","category":"added","summary":"Async bulk screening jobs: submit thousands of subject ids and collect scores from a job result. Strictly read-only: a screening writes no events and no snapshots.","endpoints":["/api/v1/screenings","/api/v1/screenings/{id}","/api/v1/screenings/{id}/results"],"reference":"#244"},{"id":"2026-07-22-activity-and-event-export","date":"2026-07-22","category":"added","summary":"GET /api/v1/activity (the audit trail of what your own keys and configuration did) and GET /api/v1/events/export (your own reported events, JSON or CSV).","endpoints":["/api/v1/activity","/api/v1/events/export"],"reference":"#245"},{"id":"2026-07-22-status-history","date":"2026-07-22","category":"changed","summary":"/status.json gained a durable `history` block: uptime percentages and a p95 series that survive a deploy, instead of only in-process percentiles that reset.","endpoints":["/status.json"],"reference":"#242"},{"id":"2026-07-22-key-rotation","date":"2026-07-22","category":"added","summary":"Admin API-key rotation with a bounded grace overlap, so a key can be replaced without a window where neither the old nor the new key works. Rotation preserves the key's mode.","endpoints":["/api/v1/admin/platforms/{id}/keys/{keyId}/rotate"],"reference":"#239"},{"id":"2026-07-22-monitor-limits-from-plans","date":"2026-07-22","category":"changed","summary":"Continuous-monitor caps are sourced per tier from the plan catalog (env still overrides), so the published limit and the enforced limit cannot disagree.","endpoints":["/api/v1/monitors","/api/v1/plans"],"reference":"#241"},{"id":"2026-07-22-durable-usage-history","date":"2026-07-22","category":"changed","summary":"Usage reads are backed by durable daily rollups, so a usage window can reach past the 90-day live retention.","endpoints":["/api/v1/usage"],"reference":"#240"},{"id":"2026-07-22-monitors","date":"2026-07-22","category":"added","summary":"Continuous score monitors: edge-triggered watches that fire a `monitor.triggered` webhook when a subject crosses a threshold or changes band, plus a `usage.quota_warning` event when month-to-date usage crosses a warning share of the quota. Notification config only; a monitor writes nothing score-side.","endpoints":["/api/v1/monitors","/api/v1/monitors/{id}","/api/v1/webhooks/events"],"reference":"#237"},{"id":"2026-07-22-usage-ranges-and-csv","date":"2026-07-22","category":"added","summary":"GET /api/v1/usage accepts an inclusive `from`/`to` date range (mutually exclusive with `days`) and `format=csv` for a downloadable statement.","endpoints":["/api/v1/usage"],"reference":"#236"},{"id":"2026-07-22-scoring-5-3","date":"2026-07-22","category":"changed","summary":"Scoring v5.3: Verification Depth is the share of the record independently verified, not the number of distinct platforms. A fully verified single-platform record now earns full depth; breadth is neither required nor rewarded. Factor weights are unchanged. The human-readable factor LABEL on /score/explain changed with it, from \"Platform Diversity\" to \"Verification Depth\", a display string, not a field name; match on the factor key, never on the label.","endpoints":["/api/v1/scoring/model","/api/v1/users/{id}/score/explain"],"reference":"#229"},{"id":"2026-07-22-formula-version-drift-fix","date":"2026-07-22","category":"fixed","summary":"The OpenAPI document declared its own copy of the formula version and had gone stale at 5.0 while the engine ran 5.3, and the webhook catalog hardcoded 5.2 in its example payload. Both now derive from the real constant, and the documented `scoreBand` enum regained the missing `Unproven` band.","endpoints":["/api/v1/webhooks/events"],"reference":"#234"},{"id":"2026-07-21-webhook-event-catalog","date":"2026-07-21","category":"added","summary":"GET /api/v1/webhooks/events: every outbound event type with a description, an example payload, the delivery envelope and signature-verification guidance. A test asserts it documents exactly the enum the delivery pipeline sends.","endpoints":["/api/v1/webhooks/events"],"reference":"#221"},{"id":"2026-07-21-projection-from-blank-baseline","date":"2026-07-21","category":"changed","summary":"POST /api/v1/users/{id}/score/project no longer 404s for a subject with no ledger: an unknown id projects from a blank, unproven baseline. Simulating a trajectory no longer requires writing speculative events. The projection still writes nothing.","endpoints":["/api/v1/users/{id}/score/project"],"reference":"#217"},{"id":"2026-07-21-plan-catalog","date":"2026-07-21","category":"added","summary":"GET /api/v1/plans: the canonical developer tier catalog (scopes, per-minute rate limits, monitor caps, feature matrix). The rate limiter derives its per-tier default from the same catalog, so the published limit is the enforced one.","endpoints":["/api/v1/plans"],"reference":"#213"},{"id":"2026-07-21-scoring-5-2","date":"2026-07-21","category":"changed","summary":"Scoring v5.2: a strong but unverified record reaches the Fair band rather than sitting at the unproven anchor. Good and Excellent remain verification-only.","endpoints":["/api/v1/scoring/model"],"reference":"#204"},{"id":"2026-07-21-scoring-5-1","date":"2026-07-21","category":"changed","summary":"Scoring v5.1: recency became a first-class signal, plus a bounded provisional band so real-but-unverified activity is not pinned to exactly the unproven anchor.","endpoints":["/api/v1/scoring/model"],"reference":"#202"},{"id":"2026-07-21-scoring-model-endpoint","date":"2026-07-21","category":"added","summary":"GET /api/v1/scoring/model: the scoring model published as data (weights, anchor, confidence thresholds, decay), read from the live formula constants. A score can be independently recomputed from the ledger.","endpoints":["/api/v1/scoring/model"],"reference":"#194"},{"id":"2026-07-19-batch-events","date":"2026-07-19","category":"added","summary":"POST /api/v1/events/batch: report up to 100 events in one call, with partial success and per-item idempotency.","endpoints":["/api/v1/events/batch"],"reference":"e9066e7"},{"id":"2026-07-19-scoring-5-0","date":"2026-07-19","category":"changed","summary":"Scoring v5.0: only VERIFIED outcomes count as trust evidence. An unverified record sits at the unproven anchor rather than accruing toward a high score.","endpoints":["/api/v1/scoring/model"],"reference":"4a79595"},{"id":"2026-07-19-scoring-4-0","date":"2026-07-19","category":"changed","summary":"Scoring v4.0: asymmetric confidence. The baseline blend regresses toward neutral on the upside (a high score must be earned) but does not cushion the downside, so a breach is not masked as neutral.","endpoints":["/api/v1/scoring/model"],"reference":"3f4dcba"},{"id":"2026-07-19-widget-self-hosted","date":"2026-07-19","category":"fixed","summary":"The embeddable trust badge is served from api.credda.io itself. Every published embed snippet had pointed at a CDN hostname that was never provisioned, so every embed 404'd.","reference":"c03366e"},{"id":"2026-07-18-component-scoring","date":"2026-07-18","category":"added","summary":"GET /api/v1/users/{id}/score/components: the score reframed as six independently 0–100-scored components. A relabelling of fields the formula already produces; it cannot change the final score.","endpoints":["/api/v1/users/{id}/score/components"],"reference":"4b2a220"},{"id":"2026-07-18-timeline","date":"2026-07-18","category":"added","summary":"GET /api/v1/users/{id}/timeline: a cursor-paginated feed merging the event ledger and score-snapshot history, newest first.","endpoints":["/api/v1/users/{id}/timeline"],"reference":"cbd53c1"},{"id":"2026-07-18-cursor-pagination","date":"2026-07-18","category":"added","summary":"Growing list endpoints accept an optional `cursor` alongside `limit` and return `nextCursor`. Additive: omit `cursor` and behaviour is unchanged.","endpoints":["/api/v1/users/{id}/score/history","/api/v1/webhooks/{id}/deliveries"],"reference":"1541052"},{"id":"2026-07-18-score-delta","date":"2026-07-18","category":"added","summary":"GET /api/v1/users/{id}/score/delta: a factor-level explanation of the last score change, and the same `reason` is attached to the `score.updated` webhook payload.","endpoints":["/api/v1/users/{id}/score/delta"],"reference":"2dca93a"},{"id":"2026-07-18-batch-score-read","date":"2026-07-18","category":"added","summary":"POST /api/v1/users/scores: read up to 100 subjects' scores in one call, with partial success on unknown ids.","endpoints":["/api/v1/users/scores"],"reference":"4880c11"},{"id":"2026-07-18-score-projection","date":"2026-07-18","category":"added","summary":"POST /api/v1/users/{id}/score/project: a read-only what-if preview built on the pure formula. Writes nothing: no snapshot, no webhook.","endpoints":["/api/v1/users/{id}/score/project"],"reference":"225efb7"},{"id":"2026-07-18-trust-export","date":"2026-07-18","category":"added","summary":"GET /api/v1/verify/{token}/export: a self-verifying portable bundle: the public score, the signed W3C credential and the revocation pointer.","endpoints":["/api/v1/verify/{token}/export"],"reference":"2cf6d7b"},{"id":"2026-07-18-resource-scopes","date":"2026-07-18","category":"added","summary":"Fine-grained per-resource API key scopes (`events:write`, `scores:read`, …) alongside the coarse read/write forms. Backward compatible: an existing `*` key is unaffected.","reference":"3038d36"},{"id":"2026-07-18-request-ids","date":"2026-07-18","category":"added","summary":"Every response carries `X-Request-Id` (send your own and it is echoed) and every error body includes the same `requestId`.","reference":"b7219fa"},{"id":"2026-07-18-quotas-and-rate-limits","date":"2026-07-18","category":"added","summary":"Tier-scaled per-platform rate limiting with standard `RateLimit-*` headers, monthly volume quotas, per-request metering, and GET /api/v1/usage for a platform to read its own consumption.","endpoints":["/api/v1/usage"],"reference":"ae1de04"},{"id":"2026-07-18-revocation-list","date":"2026-07-18","category":"added","summary":"StatusList2021 credential revocation: issued credentials carry a `credentialStatus`, and the signed bitstring is published so an offline verifier can check it.","endpoints":["/api/v1/status/revocation"],"reference":"5a669ac"},{"id":"2026-07-18-openapi","date":"2026-07-18","category":"added","summary":"The machine-readable contract at GET /openapi.json and an interactive reference at /docs. A test asserts every mounted route is documented.","reference":"d464f78"},{"id":"2026-07-18-idempotency","date":"2026-07-18","category":"added","summary":"Optional `Idempotency-Key` on event ingestion, so a retried write cannot duplicate a ledger entry.","endpoints":["/api/v1/events"],"reference":"ddc17a9"},{"id":"2026-07-18-webhooks","date":"2026-07-18","category":"added","summary":"Outbound webhooks: HMAC-signed `score.*` and `dispute.resolved` deliveries with subscription management, a delivery log and a test send. Advisory: a webhook never affects a score.","endpoints":["/api/v1/webhooks","/api/v1/webhooks/{id}","/api/v1/webhooks/{id}/test","/api/v1/webhooks/{id}/deliveries"],"reference":"9ce7dc8"},{"id":"2026-07-18-w3c-credentials","date":"2026-07-18","category":"added","summary":"W3C Verifiable Credentials, a `did:web` issuer document and a trust registry, alongside the existing compact Credda trust credential.","endpoints":["/.well-known/did.json","/.well-known/credda-trust-registry.json","/api/v1/verify/{token}/credential"],"reference":"b164550"},{"id":"2026-07-18-trust-credentials","date":"2026-07-18","category":"added","summary":"EdDSA-signed, offline-verifiable trust credentials with disclosure scopes (full / band / minimal) and published verification keys.","endpoints":["/.well-known/jwks.json"],"reference":"80fc8f4"},{"id":"2026-07-17-public-verify-surface","date":"2026-07-17","category":"added","summary":"The first public surface: share-token verification, the deterministic score read and its explanation, and the embeddable badge.","endpoints":["/api/v1/verify/{token}","/api/v1/users/{id}/score","/api/v1/users/{id}/score/explain","/api/v1/users/{id}/share-token"],"reference":"bd8645e"}]}