Navigate KYB flow
Starts or resumes an actor's questionnaire and returns the step to render. Answers 201 Created on every success, including a plain resume that creates nothing. Two bodies share that status: a running flow returns KYBFlowResponse (flow + question), while a flow whose status is already COMPLETE (upper case) returns KYBFlowCompletedResponse (flow + message, and deliberately NO question — an empty Question value would serialise as a real question and the client would render an empty form). Branch on whether question is present. THERE IS A THIRD RESPONSE SHAPE, and "branch on whether question is present" does not find it. When the resolved step name is SECTION_LIST the handler returns a KYBFlowResponse whose Question is left at its ZERO VALUE — and Question is a value field with no omitempty, so the body carries "question": {"name": "", "q": "", "schema": null, ...}. A client that renders whenever question exists draws an empty form. Branch on flow.CurrentStep == "SECTION_LIST" instead. That response is also rebuilt field by field and copies neither flow_id nor SectionStatus, so a pinned actor's flow_id is absent from this one body — which, by the rule stated on KYBFlow.flow_id, reads as "not pinned" when the actor is in fact pinned.
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/kyb/navigate" \ -H "Content-Type: application/json" \ -d '{ "flow_name": "business_verification", "actor_id": "550e8400-e29b-41d4-a716-446655440000" }'{
"flow": {
"ActorID": "f5d45287-6d16-476f-89ca-9fde3795da76",
"FlowName": "string",
"flow_id": "0746f03b-16cc-49fb-9833-df3713d407d2",
"CurrentStep": "string",
"back": true,
"invalidate": true,
"PreviousStep": "string",
"Status": "SECTION_LIST",
"Sections": null,
"SectionStatus": {},
"progress_catalog": {
"sections": [
{
"name": "string",
"steps": [
{
"name": "string",
"title": "string",
"order": 0
}
],
"presentation": "string"
}
],
"rail_parent": {
"property1": "string",
"property2": "string"
},
"visit_only": [
"string"
]
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"created_at": "2019-08-24T14:15:22Z",
"created_by": "ee824cad-d7a6-4f48-87dc-e8461a9201c4",
"modified_at": "2019-08-24T14:15:22Z",
"modified_by": "e8d4374d-93a1-4e98-a6c6-fdcf00c5059f",
"active": true,
"metadata": {}
},
"question": {
"name": "string",
"section": {
"name": "string",
"title": "string",
"icon": "string",
"status": "NOT_STARTED",
"show": true
},
"q": "string",
"description": "string",
"message": {
"text": "string",
"severity": "info"
},
"options": {
"save_form_data": true,
"back": true,
"show_in_progress_rail": true,
"presentation": "string",
"wizard_step": "string"
},
"schema": {},
"uiSchema": {},
"formData": {},
"custom_api": {},
"custom_function": {}
}
}{
"status": 400,
"message": "Invalid actor ID: {actorID}",
"code": "kyb_m.invalid_actor_id",
"class": "validation"
}{
"status": 401,
"message": "Unauthorized",
"code": "common.unauthorized",
"class": "business"
}{
"status": 403,
"message": "Access denied",
"code": "common.forbidden",
"class": "business"
}{
"status": 404,
"message": "Flow not found",
"code": "kyb_m.flow_not_found",
"class": "business"
}{
"status": 429,
"message": "Rate limit for 203.0.113.7 to POST:/v1/kyb/navigate exceeded.",
"code": "rate_limits_m.exceeded",
"class": "temporary",
"retryable": true
}{
"status": 500,
"message": "Failed to fetch KYB flow for actor {actorID} and flow name {flowName}",
"code": "kyb_m.failed_to_fetch_kyb_flow",
"class": "business"
}{
"overall_status": "unhealthy",
"message": "Service is shutting down",
"timestamp": "2026-08-27T15:04:05Z"
}Updates one stored answer. Requires the record-level update permission. Answers 200 through apireply.Ok200, whose body is the shared StdResponse envelope ({status, message}) — NOT the {message: "Answer updated successfully"} object this spec used to document, which the code never produced. The update is a gorm Updates over the decoded struct, which writes every non-zero field it finds. Because the Go type embeds BaseModel, that includes audit columns — see KYBAnswerUpdate.
Administratively repins an actor's kyb.flows row to a different snapshot, a different step, or both. Used to recover actors stuck on an inactive snapshot. Admin only: the handler calls requireKYBConfigAdmin, which resolves the caller and then checks Storage.IsAdminAccount. A non-admin gets 403 common.forbidden. At least one of flow_id or current_step must be present; sending neither is 400. Body validation in the handler is shape-level only; that the snapshot and step actually exist is checked inside the storage transaction, AND BOTH OF THOSE ARE 400, NOT 404. A flow_id that names no flows_config row, or one whose flow_name differs from the body's, is kyb_m.invalid_flow_id; a current_step that is not an active item of the effective snapshot is kyb_m.invalid_step. The only 404 on this route is a missing kyb.flows row for the actor. On success the flow_name's config cache is invalidated so the next navigate reads the new layout.