code
POST /api/kyc/verifications/:id/review

Approves, declines or sends back a finished verification, or changes a decision made earlier. This is the API twin of the review controls in your dashboard. The two run the same core, so a decision made here does exactly what one made there does:

  • the person's record moves to approved, rejected or under review
  • declining pauses their ongoing screening, and approving resumes it
  • the change is added to the verification's timeline in your dashboard, and to statusHistory on the result endpoint
  • your webhook endpoints receive verification.status_updated with source: "api"
  • the applicant is emailed about it when you ask, or when the workflow does (see Emailing the applicant)

The verification itself is never rewritten. checkStatus keeps saying what the checks found, so an approval granted despite a failed check stays visible.

Authentication: Authorization: Bearer sk_… (secret key required). A publishable key ships in a browser, and a browser must never be able to approve its own applicant, so it receives 403 secret_key_required. Content type: application/json. Production note: sk_live_ keys require an approved business.

Request body

FieldTypeRequiredDescription
decisionstringyesAPPROVED, DECLINED or RESUBMISSION.
notestringnoWhy, up to 2,000 characters. Kept in your audit log and on the verification's timeline in your dashboard. It is never sent to your webhook endpoints or returned by the API.
stepsstring[]noRESUBMISSION only. The steps to redo, named as in Verify again. Absent or empty means the whole flow.
messagestringnoRESUBMISSION only. Shown to the applicant when they open the link, up to 500 characters. Write it for them, not for your logs.
policystringnoRESUBMISSION only. original runs the workflow version the applicant walked; latest runs today's. Absent follows the workflow's own setting.
notifyApplicantbooleannoAPPROVED or DECLINED only. true emails the applicant about the decision and false does not. Absent follows the workflow: an approval is emailed when its Email the applicant when approved setting is on, and a decline never is.
emailstringnoOn a RESUBMISSION, send the link to the applicant at this address as well as returning it. On an approval or a decline sent with notifyApplicant: true, the address to email instead of the one the verification holds.

A field sent with a decision it does not apply to is refused with 400 invalid_input rather than ignored, because a caller who sent it has misunderstood something: steps, message or policy with an approval or a decline, email with one that does not also send notifyApplicant: true, or notifyApplicant with a RESUBMISSION.

Request

shell
curl "https://trust.myaza.app/api/kyc/verifications/ver_abc123/review" \
  -H "Authorization: Bearer $MYAZA_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "DECLINED", "note": "Duplicate of an existing customer" }'

Response 200 OK

json
{
  "review": {
    "verificationId": "ver_abc123",
    "decision": "DECLINED",
    "status": "declined",
    "previousStatus": "approved",
    "checkStatus": "verified",
    "reviewedAt": "2026-09-13T09:00:00.000Z",
    "entityId": "ent_01j9def789",
    "attempt": 1,
    "delivery": null,
    "applicantEmail": null,
    "resubmission": null
  }
}
FieldDescription
statusThe verification's top-line status now, the same value Get verification status returns.
previousStatusThe top-line status immediately before this decision.
checkStatusWhat the checks found. No decision changes it.
entityIdThe person's record the decision was applied to. Null when the checks never created one, for example on a failed check.
attemptThe attempt the verification holds now. A verification you send back keeps its id, and the applicant's resubmission becomes the next attempt.
deliveryOn a RESUBMISSION with email: { emailed, reason }. emailed: false with reason: "send_failed" means the email could not be sent, and the link still works. Null otherwise.
applicantEmailOn an approval or a decline where emailing the applicant was asked for, by notifyApplicant or by the workflow: { emailed, reason, recipient }. recipient is { masked, source }: the address masked (a•••@example.com) and where it came from, email_verification, sdk, entity or provided. When emailed is false, reason says why: no_recipient (there was no address to send to) or send_failed. The decision stands either way. Null when nobody asked.
resubmissionOn a RESUBMISSION, the link to send the applicant: sessionId, url, shortCode, expiresAt, steps and full. url is a credential: whoever opens it verifies as this applicant. Null otherwise.

Emailing the applicant

We can tell the applicant about an approval or a decline, in your organisation's name:

json
{ "decision": "APPROVED", "notifyApplicant": true }

The email says what was decided and who to contact. It never gives a reason: that is yours to give, in your own words.

We only write to an address the verification already holds, using the first of these that has one:

  1. the email the applicant proved they own with a code, when the workflow runs email verification;
  2. an email your integration passed, as userData.email on the SDK or on a session you created (the address you emailed the session link to counts too);
  3. the email on the person's entity record.

To use another address, send it as email beside notifyApplicant: true. When there is no address, nothing is sent and applicantEmail.reason is no_recipient.

Leave notifyApplicant out and the workflow decides: when it has Email the applicant when approved switched on, an approval is emailed however it was made. A decline is only ever emailed when you ask. Every email is added to the verification's timeline in your dashboard.

Sending it back

json
{
  "decision": "RESUBMISSION",
  "steps": ["document-capture"],
  "message": "Your document photo was too blurry to read. Please retake it in good light."
}

The applicant gets a link on the same workflow and walks only the steps you name. Their resubmission becomes the next attempt of this verification, under the same id, so you never have a second verification to reconcile. This is the same operation as Verify again.

Add "email": "ada@example.com" and we send the link for you. Otherwise delivering it is yours to do. Either way it is in this response and in the webhook, and any link sent earlier for the same verification stops working.

Changing a decision

Call it again with a different decision. The latest decision is the verification's status, each earlier one stays on record, and every change sends its own verification.status_updated. Read the whole trail back with Get verification result, whose statusHistory lists every change; in your dashboard it is on the verification's timeline.

A decision can be made on any finished verification: one your workflow sent for review, one it approved or declined automatically, or one a person has already decided.

Errors

StatusCodeMeaning
400invalid_inputThe body is malformed, decision is not one of the three values, or a field came with a decision it does not apply to.
400invalid_stepsA step name is not a valid step. The message lists the valid names.
403secret_key_requiredA publishable key was used.
404verification_not_foundNo verification with that id in your organisation and environment.
409verification_in_progressThe checks have not finished. Wait for the result before deciding it.
422no_workflowA RESUBMISSION on a verification that did not run a workflow, so there is no flow to send the applicant back into. Approve or decline it instead.
429rate_limitedToo many sessions started this hour. A send-back starts one. Try again shortly.