For partners

Integrate in an afternoon

The whole partner arc on one page: connect a subject, report the outcomes you already see, subscribe to one webhook, read deterministic evidence, and make your decision on your side. Built for escrow release, deposit waivers and verified-earnings underwriting.

Credda supplies evidence. You own the decision, and the loss.

Credda records counterparty-confirmed outcomes and computes a deterministic reliability score. It never makes your release, waiver or underwriting call, and there is no decision, approve/deny or AI-verdict endpoint to call. Its output is contractually barred from being the credit decision. It is verification and nuance only. That boundary is not a limitation to work around; it is what keeps Credda outside FCRA consumer-reporting scope and keeps your risk model entirely yours.

The arc

Five steps, roughly 80 minutes of real work end to end. Every call below is live today; each step links a runnable script in examples/.

Connect subject
your external id
Report outcomes
append-only ledger
Subscribe webhook
band_changed
Read evidence
deterministic
Your decision
on your side
10 min Step 1

Connect a subject

A subject is simply one of YOUR external ids. Credda never asks you to migrate your users into its database. Reference them by the id you already use. The subject consents to sharing their reliability record with you (consumer-permissioned and consumer-initiated is what keeps this arrangement non-CRA); they can share a token you resolve, or you can reference them directly once they have connected under your platform.

# A subject id is whatever YOU already call them. No migration, no new PII.
# Create a sandbox key first (Console -> keys -> "test mode"). It is prefixed
# crd_test_ and touches ZERO live data.
export CREDDA_API_KEY=crd_test_...

# If the subject handed you a share token instead, resolve it. Public, no key:
curl https://api.credda.io/api/v1/verify/THEIR_SHARE_TOKEN
20 min Step 2

Report the outcomes you already see

You already observe the outcomes that matter: a job delivered, an invoice settled, a deadline missed. Report them to the append-only ledger. Writes are idempotent (an Idempotency-Key derived from your own order/PO makes a retry loop exactly-once), and the third-party-witness rule is enforced in code: isVerified is only honoured as verified evidence when a party OTHER than the subject confirmed the outcome. Self-attested activity is recorded, but it never builds trust, which is exactly why the number is worth reading.

# One outcome (idempotent, safe inside any retry loop)
curl -X POST https://api.credda.io/api/v1/events \
  -H "Authorization: Bearer $CREDDA_API_KEY" \
  -H "Idempotency-Key: po-2210-delivered" \
  -H "Content-Type: application/json" \
  -d '{"userId":"subject_17","eventType":"CONTRACT_FULFILLED",
       "stakeLevel":"HIGH","isVerified":true,"transactionValue":12000}'

# Backfilling history? Up to 100 per call, partial success, per-item idempotency:
curl -X POST https://api.credda.io/api/v1/events/batch \
  -H "Authorization: Bearer $CREDDA_API_KEY" -H "Content-Type: application/json" \
  -d '{"events":[ /* ...up to 100 outcome events... */ ]}'

# The two calls above ASSERT the outcome on your word. To EARN it, ask the
# counterparty. The event is written verified only when they act on their
# one-time token. Free on every plan, no events:write scope needed:
curl -X POST https://api.credda.io/api/v1/confirmations \
  -H "Authorization: Bearer $CREDDA_API_KEY" -H "Content-Type: application/json" \
  -d '{"userId":"subject_17","eventType":"CONTRACT_FULFILLED",
       "counterpartyRef":"ops@buyer.example","description":"PO-2210 delivered."}'
# → confirmationToken (once) + confirmUrl. Full loop: /quickstart#confirm
15 min Step 3

Subscribe to one webhook

Do not poll. Subscribe your endpoint once and Credda pushes a signed event the moment a subject crosses a band. score.band_changed carries the factor-level reason for the move, so your policy engine can react to WHY, not just the number. Verify every delivery with the SDK before trusting it (HMAC-SHA256 over the raw body, constant-time, replay-tolerant). Deliveries are ADDITIONALLY signed with Web Bot Auth (RFC 9421, Ed25519) so a WAF that drops unsigned automated traffic still lets Credda through.

# Subscribe (the signing secret is returned ONCE, so store it)
curl -X POST https://api.credda.io/api/v1/webhooks \
  -H "Authorization: Bearer $CREDDA_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://you.example.com/hooks/credda",
       "events":["score.band_changed","dispute.resolved"]}'

# In your handler, verify with the RAW body, never re-serialized JSON:
import { constructWebhookEvent } from '@credda/js';
const event = await constructWebhookEvent({
  rawBody,
  signatureHeader: req.headers['x-credda-signature'],
  timestampHeader: req.headers['x-credda-timestamp'],
  secret: process.env.CREDDA_WEBHOOK_SECRET,
});
// WAF note: outbound deliveries also carry a Web Bot Auth "Signature-Agent"
// header; the verifying keys are published at
// /.well-known/http-message-signatures-directory
20 min Step 4

Read the deterministic evidence

This is what you actually consume. The score is a pure function of the ledger: same events in, same score out, one writer, no manual adjustment. /score/explain returns per-factor reasons AND a ranked list of stable, versioned reasonCodes: if your decision is a credit decision, those are the specific reasons you need for an ECOA / Reg B adverse-action notice, and the code catalog is public at /api/v1/reason-codes. For income underwriting, /earnings/summary attests VERIFIED income only. Self-reported and disputed amounts are reported separately, never blended. And the model itself is public, so you can audit any score against it.

# The score, its factor breakdown, and (for underwriting) verified earnings
curl https://api.credda.io/api/v1/users/subject_17/score \
     -H "Authorization: Bearer $CREDDA_API_KEY"
# { "finalScore": 74, "scoreBand": "Good", "confidence": 0.9 }

curl https://api.credda.io/api/v1/users/subject_17/score/explain \
     -H "Authorization: Bearer $CREDDA_API_KEY"
# { "factors": [ { "name": "Completion Rate", "contribution": 32.4,
#                  "description": "Strong: you complete the majority..." }, ... ],
#   "reasonCodes": [ /* ranked, adverse first, for a Reg B notice */ ] }

curl https://api.credda.io/api/v1/users/subject_17/earnings/summary \
     -H "Authorization: Bearer $CREDDA_API_KEY"
# { "trailing12mVerifiedTotal": 48200, "medianMonthly": 3900,
#   "verifiedShare": 0.82, "volatility": 0.19, "currency": null, ... }

# Audit any score against the model, and the reason-code catalog. Public, no key:
curl https://api.credda.io/api/v1/scoring/model
curl https://api.credda.io/api/v1/reason-codes
15 min Step 5

Make YOUR decision, on YOUR side

Credda hands you evidence; the policy, the decision and the loss are yours. Publish a deterministic threshold policy ("release escrow at band Good or above with verifiedShare >= 0.5", "waive the deposit above a trailing-12-month verified income floor") and drive it off the band-change webhook. Credda deliberately has no decision, approve/deny, or AI-verdict endpoint to call: that boundary is what keeps its output verification-and-nuance rather than the decision, and keeps you in control of your own risk model.

// The decision lives entirely in YOUR code. Credda never runs it.
function depositWaiverDecision(score, earnings) {
  const bandOk   = ['Good', 'Excellent'].includes(score.scoreBand);
  const incomeOk = earnings.trailing12mVerifiedTotal >= 36000;
  const solidOk  = (earnings.verifiedShare ?? 0) >= 0.5;
  return {
    waiveDeposit: bandOk && incomeOk && solidOk,
    // Log the exact evidence that drove it. This is YOUR audit trail.
    evidence: { band: score.scoreBand, verifiedIncome: earnings.trailing12mVerifiedTotal },
  };
}
// Runnable end to end: examples/partner-deposit-waiver.mjs

Or embed the confirmation in your own product

A counterparty confirmation is the strongest event you can send us — it is the one that earns isVerified from a witness rather than asserting it. You do not have to build the UI for it, and you do not have to send anyone away from your product: one script tag opens the hosted flow in a sandboxed modal on your own page. The script is served from this host, not a CDN. What comes back is a status — confirmed, declined or dismissed — and nothing else: no token, no subject id, no counterparty reference, no score.

<!-- served from api.credda.io itself -->
<script src="https://api.credda.io/verify-embed.js" async></script>

<credda-verify confirmation-id="cnf_123" token="cnft_…"
               label="Confirm this delivery"></credda-verify>

// …or open it from the confirmUrl POST /api/v1/confirmations gave you
CreddaVerify.open({
  confirmUrl: created.confirmUrl,
  onComplete: ({ status }) => refreshOrder(status),   // 'confirmed' | 'declined' | 'dismissed'
});

The launcher validates every message against https://api.credda.io and against the exact frame it opened before a status is allowed to mean anything — an unvalidated postMessage listener is a real vulnerability, so the rejection path is tested, not assumed. Full contract, sandbox rationale and the React snippet: API reference.

Prove it in the sandbox first

Every step above works against a crd_test_ key. Test mode is fully isolated: a test key sees only test data, never touches a live score, and is excluded from every public trust surface. When you are done, wipe it: DELETE /api/v1/test/data is the one deletion path in the product, and it asserts test-mode in every clause, so it can never reach real data. The append-only guarantee applies to production; the sandbox is yours to reset.

# Reset your sandbox. Test data only, real ledger untouched
curl -X DELETE https://api.credda.io/api/v1/test/data \
     -H "Authorization: Bearer $CREDDA_API_KEY"   # crd_test_ key

Go-live checklist

Start building