Skip to main content
A reference for partners integrating with the Frayme fraud-analysis platform.
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 the X-API-Key header:
No other authentication mechanism is supported on the partner endpoints. Each key carries one or more scopes:

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.

Transactiontransaction

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.

KYCperson

KYBbusiness

Response

Polling case status

GET /cases/{caseId} — requires scope cases:read.

Response

Status values

The case status reflects only lifecycle, not the outcome — read result.decision.value for the outcome. Notes:
  • result is absent until the workflow completes.
  • result.decision.value is one of approved, declined, or in_review.
  • result.decision is always the current decision; result.decisionHistory[0] is the workflow’s initial decision, and later entries are analyst overrides.
  • result.decision.source is one of workflow, risk_evaluation, or analyst. risk_evaluation means the case was decided by the pre-DAG risk gate before the workflow ran; analyst means a human overrode the decision.
  • Each decision entry (current and historical) may also carry optional fields: declineReason, queueName, riskScore, and notes. They are present only when set.
  • result.workflow_result is write-once: analyst overrides never mutate it.
  • result.riskEvaluation is 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 of triggeredRules (each with id, name, severity, conditions, ruleVersion). Treat it as read-only audit evidence.
  • result.dataSources (when present) lists the enrichment providers the workflow called, each with nodeId, providerId, and status.
Poll on a backoff (for example, 1s, 2s, 5s, then every 10s) until status is completed, then read result.decision.value. Prefer webhooks over long polling whenever possible.

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

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

  1. Compute HMAC over the exact bytes of the request body — do not re-serialize the JSON.
  2. Use a constant-time comparison (for example, hmac.compare_digest).
  3. If X-Frayme-Secret-ID is present, pick the matching secret from your rotation set.
  4. Respond 2xx within ~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.
  5. Treat deliveries as at-least-once; deduplicate by webhookId as described above.

Delivering an external callback

Used only if your workflow has a node that pauses awaiting an external system. After receiving an external_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 example high-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 tagPOST /cases/{caseId}/tags, requires scope tags:write.
Both success responses return the stored tag as { "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 tagsGET /cases/{caseId}/tags, requires scope tags:read. Returns { "tags": [ { "tag", "createdBy", "createdAt" } ] }, oldest-first (an empty array for an untagged case). Remove a tagDELETE /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 opaque pending_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 returns 404.
You obtain a handle in one of two ways:
  • From a node.notification webhook — the output.pending_handle field (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.
The body is { "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 by caseId (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.
The response wraps the action’s output, exactly like a pending action:
Each invocation is an independent vendor call — there is no idempotency, so a retry mints a new artifact. Use the returned value rather than calling again “to be safe”.
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.