Today, API keys, the workflow IDs you may submit cases to, and webhook configuration (URL, signing secret) are all provisioned for you by the Frayme team. In the future, both API keys and webhook configuration will be self-served from the Frayme portal.
This guide is the narrative walkthrough. For the field-level reference of each
endpoint, see the API reference; for the events
Frayme pushes to you, see Webhooks.
Concepts
You submit cases (KYC, KYB, or Transaction) to Frayme. Each case is routed through a tenant-defined workflow that produces a decision (approved, declined, or in_review). You can:
- Submit cases via the Case API.
- Poll case status via the Case API.
- Receive webhooks when a decision is reached (or when an analyst later overrides it).
Authentication
All Case API calls require an API key scoped to your tenant. The key is sent on every request using theX-API-Key header:
Submitting a case
POST /cases — requires scope cases:write.
Request body
Do not send
tenantId — your tenant is derived from the API key, and any tenantId in the body is ignored.string
required
Provisioned for you by Frayme.
integer
Pin the version your client expects. If omitted, the workflow’s current published version is used.
string
required
One of
KYC, KYB, or Transaction.object
required
Validated against the workflow’s input schema. Fields and types are dictated by the workflow you target; submitting a payload that does not match the schema returns
400.object
Free-form audit fields; surfaced in the portal and event log.
object
required
Typed business attributes used for entity resolution and review.
displayName is required on every subject and is the label shown in review queues. Then set exactly one sub-struct, and it must match type: transaction for Transaction, person for KYC, business for KYB. Setting none, more than one, or the wrong one returns 400. See Subject sub-structs for the per-type required fields.string
If provided and a case with the same key already exists, the existing case is returned with
200 OK instead of 201 Created.string
ISO-8601 timestamp of the originating business event.
Subject sub-structs
displayName is required on every subject. Then set the one sub-struct matching type. Each sub-struct carries one or more identifiers that drive entity resolution.
Identifiers
An identifier is{ "type": <string>, "value": <string>, "country": <string, optional> }. type must be one of the closed enum below (exact lowercase match):
Identifiers are classed strong (resolve identity:
cpf, cnpj, passport, national_id, company_registration, external_customer_id, and wallet_address on a wallet party) or weak (attributes only: email, phone, pix_key). A submission that doesn’t meet the per-type identifier requirements below returns 400.
Transaction → transaction
Each party is
{ "role": "sender" | "receiver", "displayName": <string, optional>, "identifiers": [...] }. The customer-side party (the sender when outbound, the first receiver when inbound) must have a non-empty displayName and at least one strong identifier.
KYC → person
KYB → business
Response
Polling case status
GET /cases/{caseId} — requires scope cases:read.
Response
Status values
The casestatus reflects only lifecycle, not the outcome — read result.decision.value for the outcome.
Notes:
resultis absent until the workflow completes.result.decision.valueis one ofapproved,declined, orin_review.result.decisionis always the current decision;result.decisionHistory[0]is the workflow’s initial decision, and later entries are analyst overrides.result.decision.sourceis one ofworkflow,risk_evaluation, oranalyst.risk_evaluationmeans the case was decided by the pre-DAG risk gate before the workflow ran;analystmeans a human overrode the decision.- Each decision entry (current and historical) may also carry optional fields:
declineReason,queueName,riskScore, andnotes. They are present only when set. result.workflow_resultis write-once: analyst overrides never mutate it.result.riskEvaluationis the audit record of the pre-DAG risk gate (when it ran):status(ok/failed_closed),action(deny/review/workflow),highestSeverity(low/medium/high/critical), and a list oftriggeredRules(each withid,name,severity,conditions,ruleVersion). Treat it as read-only audit evidence.result.dataSources(when present) lists the enrichment providers the workflow called, each withnodeId,providerId, andstatus.
Webhooks
Frayme delivers signed HTTPS POSTs to a URL configured for you. The dedicated Webhooks page is the canonical reference for every event, payload shape, and signature-verification detail.Events you may receive
Payload shapes
- Decision payload
- External callback dispatch
- Node notification
Sent for
case.decided, case.pending_review, and case.decision_overridden.Deduplication via webhookId
Every outbound webhook (decision events and dispatch events alike) carries a unique webhookId (UUID). Frayme’s delivery is at-least-once — a transient failure or network blip can produce duplicate deliveries with the same webhookId. Persist the webhookId of every event you successfully process and reject (or short-circuit) any event whose webhookId you’ve seen before.
Headers
Verifying the signature
- Compute HMAC over the exact bytes of the request body — do not re-serialize the JSON.
- Use a constant-time comparison (for example,
hmac.compare_digest). - If
X-Frayme-Secret-IDis present, pick the matching secret from your rotation set. - Respond
2xxwithin ~10 seconds (the per-delivery timeout). Non-2xx responses and timeouts are retried with exponential backoff — 1s, 2s, 4s, 8s, 16s — up to the configured retry limit (5 attempts by default); final failures are recorded but not retried indefinitely. - Treat deliveries as at-least-once; deduplicate by
webhookIdas described above.
Delivering an external callback
Used only if your workflow has a node that pauses awaiting an external system. After receiving anexternal_callback.dispatch webhook, the external system posts the result to the one-time URL from the dispatch payload:
POST /cases/{caseId}/callbacks/{callbackRef} — requires scope cases:callback.
Content-Type: application/json; the body is a JSON object the workflow node consumes.
Tagging a case
Tags are short, non-PII operator labels used to organise and triage cases (for examplehigh-risk, review-later) — the same labels analysts apply in the Frayme console. Your tenant is taken from the API key, so tag paths are not tenant-scoped in the URL.
Tags applied via the API must come from your tenant’s managed tag vocabulary, configured under Settings → Tags in the portal. An arbitrary label is rejected, so the console’s chips, colours, and filters stay consistent.
Add a tag — POST /cases/{caseId}/tags, requires scope tags:write.
{ "tag", "createdBy", "createdAt" }. The applier is recorded as your API key (createdBy: "apikey:<keyId>"); any createdBy in the request body is ignored.
List a case’s tags —
GET /cases/{caseId}/tags, requires scope tags:read. Returns { "tags": [ { "tag", "createdBy", "createdAt" } ] }, oldest-first (an empty array for an untagged case).
Remove a tag — DELETE /cases/{caseId}/tags?tag=<label>, requires scope tags:write. Idempotent: removing an absent tag still returns 204 No Content.
Pending actions on a paused node
Some workflow nodes pause to await an external review — a Sumsub identity check, for example — and while paused they expose named actions you can invoke. Unlike an external callback (which resolves the node and resumes the workflow), a pending action is a side effect on the paused node: it returns data and leaves the node paused. Typical uses are re-minting an expiring access token or verification link, or pushing applicant data to the provider before the end user starts.Handles
Each paused node is addressed by an opaquepending_handle:
- It is per-paused-node and stable across retries — the same paused node always resolves to the same handle.
- It is tenant-scoped: a handle from another tenant resolves to
404(handle existence is never revealed across tenants). - It expires with the node’s await window (
expiresAt); after that, invoking it returns404.
- From a
node.notificationwebhook — theoutput.pending_handlefield (see Webhooks). - By listing the case’s paused nodes (below).
List paused nodes
GET /cases/{caseId}/pending — requires scope cases:read.
availableActions is provider-specific — it lists exactly the ids you may invoke on that node. resolvesAwait is false for every action available today (all are side effects).
Invoke an action
POST /pending/{handle}/actions/{actionId} — requires scope cases:callback.
{ "params": { ... } }; an empty body is valid for actions that take no parameters. The response wraps the action’s output:
The available actions and their
params depend on the node’s provider. For Sumsub, see Data sources → Sumsub → Real-time actions on a paused node.
Durable case actions
A pending action only exists while a node is paused — once the case is decided, the handle is gone. Some provider operations, though, stay meaningful long after the case has finished. The clearest example is re-issuing a Sumsub share token so a partner client can access the applicant months later. For those, Frayme exposes durable case actions, addressed bycaseId (no handle) and scoped to the broker that owns them — so they work from the moment the node ran, through the decision, and indefinitely afterward.
Durable case actions are addressed by
(brokerId, action) so action ids never collide across brokers. The applicant is re-resolved from the case’s external_customer_id — so the case must have been submitted with one (it is required on every case type).List durable actions
GET /cases/{caseId}/actions — requires scope cases:read.
Lists the durable actions invocable on the case, derived from the brokers it actually ran:
Invoke a durable action
POST /cases/{caseId}/actions/{brokerId}/{action} — requires scope cases:callback.
For Sumsub, see Data sources → Sumsub → Regenerate a share token.
Quick reference
All requests authenticate via the
X-API-Key header. The base URL of the Case API and your webhook configuration (URL, signing secret, secret ID, retry policy) are provided by Frayme — these will move to self-service in the portal.