ONP API Reference
This document describes how to integrate with the Guardline onboarding API. It covers authentication, creating onboarding sessions, declaring linked subjects (legal representative for kyc_minor, officers for kyb), resolving session links, tracking execution lifecycle, receiving webhook notifications, and polling for missed events.
The integration model follows a clear separation of responsibilities: Guardline verifies and decides, the integrator orchestrates. The integrator declares the onboarding via a single API call, distributes the URLs returned in the response (or asks Guardline to deliver them), and receives progress and final-decision notifications via webhook.
Overview
Seção intitulada “Overview”The integration flow involves four participants:
| Participant | Responsibility |
|---|---|
| Integrator backend | Creates sessions, receives webhooks, queries executions |
| Guardline API | Manages sessions, tokens, executions, linked subjects, and notifications |
| Integrator frontend | Renders the link directly or mounts an iframe pointing at it |
| Guardline onboarding | Conducts the verification journey with the end user |
A single POST /api/v1/onboarding/sessions call creates an onboarding for any flow type. The request carries the primary subject’s data plus an optional array of linked subjects. The response carries one URL per subject created. Notification of linked subjects (SMS or WhatsApp) is opt-in per call.
For kyc_minor, the integrator declares both the minor (in primary) and the legal representative (in linked_subjects[]) in the same request. The journey for each is independent: the minor opens their URL, the representative opens theirs, and Guardline orchestrates the consolidation. The integrator decides whether Guardline delivers the representative’s URL via SMS/WhatsApp (notify.linked: true) or whether the integrator handles delivery (notify.linked: false or omitted).
All API calls follow the REST pattern over HTTPS, with JSON payloads and responses in Guardline’s standard envelope format.
Environments and Authentication
Seção intitulada “Environments and Authentication”Environments
Seção intitulada “Environments”| Environment | Base URL | API Key prefix |
|---|---|---|
| Sandbox | https://{instance}.onp.dev.guardline.com.br | gl_test_ |
| Production | https://{instance}.onp.prod.guardline.com.br | gl_live_ |
The {instance} value is provisioned during integrator setup and identifies the dedicated infrastructure. Each instance has its own isolated database, application, and storage.
Authentication
Seção intitulada “Authentication”All integrator-facing API calls require the X-API-Key header with the key corresponding to the environment.
X-API-Key: gl_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2Keys are provisioned through the Guardline admin panel. The plaintext token is displayed only at creation time. Internally, Guardline stores the SHA-256 hash of the key.
HTTP requests are rejected. HTTPS with TLS 1.2 or higher is required in all environments.
The POST /api/v1/onboarding/executions/{execution_id}/resend-representative-link endpoint is the only exception: it requires an admin or supervisor JWT obtained via the Guardline backoffice. See Resending the Representative Link.
Idempotency
Seção intitulada “Idempotency”Mutating endpoints accept an optional Idempotency-Key request header (any unique string, typically a UUID). When present, the server caches the response for 15 minutes; subsequent calls with the same key return the cached response with the Idempotency-Replayed: true response header. Without the header, each call is processed independently.
Use this when retrying after a network timeout to avoid duplicate executions. Generate a fresh key for each new logical operation.
Response Format
Seção intitulada “Response Format”All responses follow the standard envelope:
Success:
{ "error": false, "data": {}}Error:
{ "error": true, "message": "descrição do erro", "code": "ERROR_CODE", "request_id": "550e8400-e29b-41d4-a716-446655440000"}The message field is localized in pt-BR. Integrators must branch on the code field (stable, untranslated), not on the message text.
The request_id field is returned in all responses and can be used for tracking in support tickets.
Available Journeys
Seção intitulada “Available Journeys”GET /api/v1/onboarding/flowsReturns the list of configured and active journeys for the integrator, with the metadata needed to create sessions correctly. Internal-use flow types (such as kyc_representative) are filtered server-side and do not appear in the response.
Response
Seção intitulada “Response”{ "error": false, "data": [ { "flow_id": "8a4c2d1e-9f37-4b6a-851d-c3e80f9b2a47", "flow_type": "kyc", "name": "KYC Pessoa Física", "description": "Verificação de identidade para maiores de 18 anos.", "status": "active", "steps_count": 8, "steps": [ { "id": "cpf", "name": "CPF", "order": 1 }, { "id": "personal_data", "name": "Dados pessoais", "order": 2 }, { "id": "contact", "name": "Informações de contato", "order": 3 }, { "id": "professional_data", "name": "Dados profissionais", "order": 4 }, { "id": "address", "name": "Endereço", "order": 5 }, { "id": "terms", "name": "Termos de uso", "order": 6 }, { "id": "document", "name": "Documentoscopia", "order": 7 }, { "id": "biometric", "name": "Biometria facial", "order": 8 } ], "primary_schema": [ { "field": "full_name", "type": "string", "required": false, "description": "Nome completo" }, { "field": "tax_id", "type": "string", "required": false, "description": "CPF (somente dígitos ou com máscara)" }, { "field": "email", "type": "string", "required": false, "description": "Endereço de e-mail" }, { "field": "phone", "type": "string", "required": false, "description": "Telefone com código do país" }, { "field": "birth_date", "type": "string", "required": false, "description": "Data de nascimento (AAAA-MM-DD)" } ] }, { "flow_id": "b7d3e5c1-2f48-4a90-86b4-d125e7a93f6c", "flow_type": "kyc_minor", "name": "KYC Menor de Idade", "description": "Verificação de identidade para menores entre 14 e 17 anos, com etapa adicional do representante legal.", "status": "active", "steps_count": 9, "steps": [ { "id": "cpf_birth_date", "name": "CPF e data de nascimento", "order": 1 }, { "id": "welcome", "name": "Boas-vindas", "order": 2 }, { "id": "personal_data", "name": "Dados pessoais do menor", "order": 3 }, { "id": "contact", "name": "Informações de contato", "order": 4 }, { "id": "professional_data", "name": "Dados profissionais e PEP", "order": 5 }, { "id": "address", "name": "Endereço", "order": 6 }, { "id": "terms", "name": "Termos de uso", "order": 7 }, { "id": "document", "name": "Documentoscopia do menor", "order": 8 }, { "id": "biometric", "name": "Biometria facial do menor", "order": 9 } ], "primary_schema": [ { "field": "full_name", "type": "string", "required": true, "description": "Nome completo do menor" }, { "field": "tax_id", "type": "string", "required": true, "description": "CPF do menor" }, { "field": "birth_date", "type": "string", "required": true, "description": "Data de nascimento do menor (AAAA-MM-DD), idade entre 14 e 17 anos" }, { "field": "phone", "type": "string", "required": false, "description": "Telefone do menor com código do país" }, { "field": "email", "type": "string", "required": false, "description": "Email do menor" } ] } ]}The primary_schema array describes the fields accepted under the primary block of POST /api/v1/onboarding/sessions. For kyc_minor, the integrator additionally declares the legal representative under linked_subjects[] (see Linked Subjects). The representative’s journey runs independently from the minor’s; steps above lists only the minor’s steps.
Creating a Session
Seção intitulada “Creating a Session”POST /api/v1/onboarding/sessionsCreates a new onboarding session and returns a URL per subject. For flows with linked subjects (kyc_minor today, kyb officers in the future), the response includes a separate URL for each linked subject, alongside the primary’s URL.
Request Schema
Seção intitulada “Request Schema”| Field | Type | Required | Description |
|---|---|---|---|
flow_type | string | One of flow_type or flow_id | Journey type. Possible values: kyc, kyb, kyc_minor. Resolves to the active flow of that type for the integrator. |
flow_id | UUID | One of flow_type or flow_id | Specific flow version. Use this to pin a particular flow configuration when multiple flows of the same type exist. Resolution to a flow whose type is internal-use (such as kyc_representative) is rejected with INVALID_FLOW_TYPE. |
channel | enum | No | Origin of the session: web, sdk, api. Default: web. The legacy alias channel_code is also accepted. |
layout_mode | enum | No | UI layout for the journey: stepper (default) or chat. |
biometric | boolean | No | Toggles the facial proof-of-life step for THIS session only, overriding the journey configuration: false suppresses the step (the user advances directly to the next one), true enables it on a journey that ships it disabled, omitted keeps the journey configuration. Requires the resolved flow to carry the biometric step (400 otherwise, in both directions). Applies to the primary execution only; linked-subject journeys keep their flow configuration. The terminal webhook reflects the choice in data.liveness.status (see [data.liveness](#dataliveness)). |
reference_id | string (≤ 255) | No | Free-form correlator for the integrator’s system. Returned in webhooks and queryable via API. Recommended: a unique stable identifier such as the account ID or onboarding request ID. |
parent_execution_id | UUID | No | Links this execution to a parent execution. Reserved for advanced use cases such as KYB sub-flows; not required for standard kyc/kyc_minor/kyb journeys. |
metadata | object | No | Free-form passthrough. Persisted on the execution and exposed back in queries. |
surface_hint | enum | No | Set to embed when the integrator will render the journey inside an iframe. Drives PII sanitization on GET /api/v1/link/{token}. See Surface Hint (Embed Mode). |
primary | object | Required for kyc_minor; optional for kyc and kyb | Primary subject’s data. Fields are described by the primary_schema returned in GET /flows. May be omitted (cold-start) for kyc and kyb, in which case the customer record is materialized when the user submits the CPF/CNPJ step in the journey. |
linked_subjects | array | Required for kyc_minor (exactly one entry, role: "representative"); empty for other flow types | Subjects linked to the primary. See Linked Subjects. |
notify | object | No | Notification opt-in flags. See Notification Opt-in. |
Linked Subjects
Seção intitulada “Linked Subjects”The linked_subjects array declares any subject linked to the primary by the flow contract. Today, this covers the legal representative for kyc_minor. KYB officers and other roles are reserved for future flow types.
Each entry has:
| Field | Type | Required | Description |
|---|---|---|---|
role | enum | Yes | Role discriminator. Today only representative is accepted. Other values (such as officer) are reserved and rejected. |
subject | object | Yes | Subject data block. May be empty ({}) for shell creation. |
The subject block accepts the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
full_name | string | Paired with tax_id (see Field Requiredness below) | Full name. |
tax_id | string | Paired with full_name | CPF (11 digits) or CNPJ (14 digits). |
phone | string (E.164) | Required if notify.linked is true; optional otherwise | Phone with country code. |
birth_date | string (YYYY-MM-DD) | No | Date of birth. Pass-through; no creation-time validation. |
email | string | No | Email. |
relationship | enum | Yes for kyc_minor; optional otherwise | Representative’s relationship with the minor. Required at session creation for kyc_minor regardless of whether the rest of subject is a shell. Values: mother, father, grandparent, legal_guardian, tutor, other. See [kyc_minor Cardinality](#kyc_minor-cardinality). |
notification_channel | enum | No | Preferred channel for backend-managed delivery: sms or whatsapp. |
Field Requiredness for linked_subjects[i].subject
Seção intitulada “Field Requiredness for linked_subjects[i].subject”The service accepts three shapes:
| Shape | Content | Effect |
|---|---|---|
| Shell | subject: {} (or subject without both tax_id and full_name) | Creates the linked execution without materializing a customer record. Integrator delivers the URL manually. |
| Mixed | subject: {tax_id: "..."} or subject: {full_name: "..."} (one of the two only) | Accepted; treated as shell. The single field does not trigger customer materialization. Not an error. |
| Materialized | subject: {tax_id: "...", full_name: "...", ...} (both present) | Creates or reuses a customer record by tax_id and back-links it to the linked execution. Other subject fields (email, phone, birth_date) populate the new customer record on creation; ignored when the customer already exists (see CPF Reuse Semantics). |
Independent of the shape, notify.linked: true requires subject.phone to actually dispatch. Without phone, notify_sent is recorded as false and no provider call is made.
For flow_type: kyc_minor, subject.relationship is required at session creation regardless of which shape above applies. A shell (subject: {}) without relationship is rejected; see [kyc_minor Cardinality](#kyc_minor-cardinality).
kyc_minor Cardinality
Seção intitulada “kyc_minor Cardinality”For flow_type: kyc_minor, the service requires exactly one entry in linked_subjects with role: "representative". Any of the following returns 400 LINKED_SUBJECTS_REQUIRED:
linked_subjectsfield absentlinked_subjects: [](empty array)- More than one entry
- An entry with
roleother thanrepresentative
The entry must also carry subject.relationship. Missing subject.relationship returns 422 LINKED_SUBJECT_RELATIONSHIP_REQUIRED. The relationship is the canonical source of the guardian-type value consumed downstream by the bond-document gate and the auto-skip of the in-flow relationship step, so it has to be declared up front even when the rest of the subject is a shell.
Shell creation is explicit and still possible; just include relationship: send linked_subjects: [{role: "representative", subject: {relationship: "mother"}}].
Notification Opt-in
Seção intitulada “Notification Opt-in”The notify block controls whether Guardline delivers the URL via SMS or WhatsApp.
| Field | Type | Default | Description |
|---|---|---|---|
notify.primary | bool | false | Reserved. Currently has no effect; future versions will dispatch the primary’s URL. |
notify.linked | bool | false | When true, Guardline dispatches an SMS or WhatsApp message containing the URL to each linked_subjects[i] whose subject.phone is present. |
Channel Selection
Seção intitulada “Channel Selection”When notify.linked: true and subject.phone is present, the dispatch channel is selected by:
linked_subjects[i].subject.notification_channelif present (smsorwhatsapp).- Otherwise, both channels are attempted in parallel as a fallback.
notify_sent Semantics
Seção intitulada “notify_sent Semantics”The response field linked_subjects[i].notify_sent reflects synchronous provider acceptance only:
true: at least one attempted channel returned a provider message ID (Twilio MessageSID).false:notify.linkedwas off, the subject’sphonewas absent, or the provider rejected synchronously.
notify_sent: true does not mean the message was delivered to the recipient. Asynchronous delivery confirmation (provider status callbacks) is not surfaced today; it remains in communication-log records accessible via the admin panel. This is a deliberate trade-off to keep the API surface honest and small.
Surface Hint (Embed Mode)
Seção intitulada “Surface Hint (Embed Mode)”Integrators rendering the journey inside an iframe set surface_hint: "embed" on the request. The backend persists the flag on the execution. When the iframe loads /api/v1/link/{token} to render the journey, the response is sanitized (CPF, email, phone, birth_date stripped from the customer block; representative URL fields stripped from the parent payload). See PII Sanitization Behavior.
The surface_hint value is server-internal and is not echoed in any response (neither /sessions nor /link/{token}). Integrators observe its effect only via the sanitized shape of the customer block in /link/{token}.
The legacy GET /api/v1/embed/{token} endpoint, which served as a separate sanitized resolver, has been removed. Embed integrators now use /api/v1/link/{token} after creating the session with surface_hint: "embed".
CPF Reuse Semantics
Seção intitulada “CPF Reuse Semantics”When primary.tax_id matches an existing active record in customers_person, the service reuses the existing customer rather than creating a new one. The fields primary.full_name, primary.email, primary.phone, and primary.birth_date from the request are ignored in this case; the customer keeps the data from its first creation.
The same applies to linked_subjects[i].subject.tax_id when the linked subject’s CPF matches an existing record.
Reason: idempotency. Retrying with the same CPF does not duplicate customer records. Trade-off: customer data is sticky to the first creation; corrections via subsequent POST /sessions calls do not propagate.
To update an existing customer’s name, email, or phone, use the backoffice or the customer-management endpoints; POST /sessions is not the path for customer updates. For local development, use a fresh CPF per test or delete the existing record before reusing the CPF.
Examples
Seção intitulada “Examples”Standard KYC, cold-start (no primary data)
Seção intitulada “Standard KYC, cold-start (no primary data)”{ "flow_type": "kyc", "channel": "web", "reference_id": "SOL-2026-00099"}The customer record is materialized when the user submits the CPF step in the journey. customer_id in the response is the zero UUID until then.
Standard KYC, full primary data
Seção intitulada “Standard KYC, full primary data”{ "flow_type": "kyc", "channel": "web", "reference_id": "SOL-2026-00099", "primary": { "full_name": "Carlos Oliveira", "tax_id": "11122233344", "email": "carlos@exemplo.com", "phone": "+5511999990000", "birth_date": "1990-05-15" }}KYC Minor with full data and Guardline-delivered notification
Seção intitulada “KYC Minor with full data and Guardline-delivered notification”{ "flow_type": "kyc_minor", "channel": "web", "reference_id": "SOL-2026-00042", "primary": { "full_name": "João Santos", "tax_id": "12345678900", "birth_date": "2010-05-15" }, "linked_subjects": [ { "role": "representative", "subject": { "full_name": "Maria Santos", "tax_id": "98765432100", "phone": "+5511999998888", "email": "maria@exemplo.com", "birth_date": "1985-03-20", "relationship": "mother", "notification_channel": "sms" } } ], "notify": { "primary": false, "linked": true }}Guardline creates both executions, delivers an SMS to +5511999998888 containing the representative’s URL, and reports the synchronous dispatch outcome in the response.
KYC Minor with shell representative (integrator delivers link)
Seção intitulada “KYC Minor with shell representative (integrator delivers link)”{ "flow_type": "kyc_minor", "reference_id": "SOL-2026-00043", "primary": { "full_name": "Ana Pereira", "tax_id": "12399988877", "birth_date": "2009-11-30" }, "linked_subjects": [ { "role": "representative", "subject": { "relationship": "mother" } } ]}The representative’s URL is returned in the response (linked_subjects[0].url). The integrator delivers it through their own channel. No notification is dispatched (notify.linked defaulted to false). The relationship field is mandatory for kyc_minor even in shell mode; see [kyc_minor Cardinality](#kyc_minor-cardinality).
Embed mode (iframe rendering)
Seção intitulada “Embed mode (iframe rendering)”{ "flow_type": "kyc", "surface_hint": "embed", "primary": { "full_name": "Lucas Almeida", "tax_id": "55566677788" }}The session’s surface_hint flag drives PII sanitization on GET /api/v1/link/{token} when the iframe resolves the token.
Success Response (201)
Seção intitulada “Success Response (201)”{ "error": false, "data": { "execution_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c", "customer_id": "155384e8-c86b-478e-be99-3937d2fee1ab", "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/abc123def456", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/abc123def456", "token": "<token>", "expires_at": "2026-05-13T12:49:34Z", "status": "created", "reference_id": "SOL-2026-00042", "linked_subjects": [ { "role": "representative", "execution_id": "c0aef12b-7c2a-4b3e-9a8d-f1e2d3c4b5a6", "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/def456ghi789", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/def456ghi789", "subject_status": "created", "notify_sent": true } ] }}| Field | Description |
|---|---|
execution_id | Unique execution identifier for the primary subject. |
customer_id | Customer record identifier. In cold-start (no primary data sent), returns the zero UUID 00000000-0000-0000-0000-000000000000; populated with the real UUID when the CPF/CNPJ step is submitted in the journey. Test against the zero UUID, not against null or empty. |
url | Public URL for the primary subject’s journey. |
embed_url | URL pointing to the iframe-friendly route for the primary subject. Use this as the iframe src when rendering inside the integrator’s app. |
token | Slug of the link, also accepted by GET /api/v1/link/{token}. |
expires_at | Link expiration in ISO 8601. Default: 7 days after creation, configurable per flow. |
status | Initial execution status (created). |
reference_id | Echo of the reference_id sent in the request, when provided. |
linked_subjects[] | One entry per linked subject created. Always present (empty array for non-kyc_minor flows). |
linked_subjects[i].role | Role of the linked subject (representative for kyc_minor). |
linked_subjects[i].execution_id | Linked subject’s execution ID. |
linked_subjects[i].url | Linked subject’s journey URL. |
linked_subjects[i].embed_url | Iframe-friendly URL for the linked subject. |
linked_subjects[i].subject_status | Linked subject’s initial status (created). |
linked_subjects[i].notify_sent | true if at least one channel was accepted by the provider; false when notify.linked: false, subject.phone absent, or the provider rejected synchronously. |
The url field is intended for direct user access via SMS, WhatsApp, email, or any other channel the integrator chooses. The embed_url field is intended for iframe and webview usage. Both point to the same execution and resolve to the same content.
Note: error
messagefields are localized in pt-BR. Match oncodefor programmatic branching, not on message text.
| HTTP | Code | Cause |
|---|---|---|
| 400 | INVALID_REQUEST | Malformed JSON, unknown field (the message names the field), or wrong field type. |
| 400 | VALIDATION_FAILED | Field-level validation failed: invalid tax_id format, flow_type not in the enum, relationship or notification_channel outside the enum, kyc_minor birth_date outside the 14-17 range, or invalid linked_subjects[i].role. |
| 400 | LINKED_SUBJECTS_REQUIRED | kyc_minor request with missing, empty, or oversized linked_subjects array, or with a role other than representative. |
| 400 | INVALID_FLOW_TYPE | The flow_type provided does not exist or is not active for the integrator, or flow_id resolves to an internal-use flow type (such as kyc_representative). |
| 400 | MISSING_REQUIRED_FIELDS | kyc_minor request without primary.tax_id or without primary.full_name. |
| 422 | LINKED_SUBJECT_RELATIONSHIP_REQUIRED | kyc_minor request whose linked_subjects[0].subject.relationship is absent or empty. Applies to all kyc_minor shapes (shell, mixed, materialized). |
| 401 | MISSING_API_KEY | API Key header not provided. |
| 401 | INVALID_API_KEY | API Key invalid, revoked, or expired. |
| 404 | FLOW_NOT_FOUND | The flow_id does not exist. |
| 409 | ACTIVE_SESSION_EXISTS | An active session already exists for the same primary subject and flow. |
| 429 | RATE_LIMITED | Request limit exceeded. Respect the Retry-After header. |
| 502 | ONBOARDING_ERROR | Internal error during session creation. Retry after a short delay; persistent failures should be reported to support with the request_id. |
Resolving the Link
Seção intitulada “Resolving the Link”GET /api/v1/link/{token}Public endpoint (no authentication). The integrator’s frontend, the iframe, or any browser opening the url from the session response calls this endpoint to load the journey. Returns the flow definition, steps, and the customer block.
When the session was created with surface_hint: "embed", the response is sanitized (see PII Sanitization Behavior).
Rate limit: 10 requests per hour per source IP, shared across the entire /api/v1/link/* namespace (not per token). Integrators resolving multiple links from the same office IP in sequence consume the same bucket.
Response (Default)
Seção intitulada “Response (Default)”{ "error": false, "data": { "execution_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c", "flow": { "id": "61000000-0000-0000-0000-000000000010", "type": "kyc_minor", "name": "KYC Menor", "steps": [ { "id": "...", "step_type_code": "welcome", "step_order": 1, "title": "Boas-vindas", "config": {} } ] }, "customer": { "id": "155384e8-c86b-478e-be99-3937d2fee1ab", "name": "João Santos", "cpf": "12345678900", "cpf_masked": "***456789**", "email": "joao@exemplo.com", "phone": "+5511999887766", "birth_date": "2010-05-15" }, "status": "in_progress", "expires_at": "2026-05-13T12:49:34Z", "allowed_origins": ["https://cliente.com"], "representative_link": "https://acme.onp.prod.guardline.com.br/onboarding/link/def456ghi789", "representative_token": "<linked-token>", "representative_status": "pending", "representative_notify_sent": true }}| Field | Description |
|---|---|
execution_id | Execution identifier of the subject this token belongs to. |
flow | Flow definition: id, type, name, ordered steps. |
customer | Customer record. Fields below. |
customer.id | UUID of the customer record. Absent when the execution is in cold-start. |
customer.name | Full name. |
customer.cpf | Unmasked CPF. Removed when surface_hint=embed. |
customer.cpf_masked | Masked CPF (always present when cpf is present). |
customer.email | Email. Removed when surface_hint=embed. |
customer.phone | Phone in E.164. Removed when surface_hint=embed. |
customer.birth_date | Date of birth in YYYY-MM-DD. Removed when surface_hint=embed. |
status | Current execution status. See Execution Lifecycle. |
expires_at | Link expiration. |
allowed_origins | Origins allowed to embed the iframe (CSP frame-ancestors). |
representative_link | Present only when this token belongs to the primary subject of a kyc_minor pair. URL of the linked representative. Removed when surface_hint=embed. |
representative_token | Linked representative’s token (FE uses for redirection). Removed when surface_hint=embed. |
representative_status | Current status of the representative’s execution (pending, in_progress, etc.). Preserved under surface_hint=embed so the FE knows whether to poll. |
representative_notify_sent | true when Guardline-managed dispatch returned a provider message ID at session creation; false when notify.linked was off or phone was absent; absent when the flow has no representative. Removed when surface_hint=embed. |
PII Sanitization Behavior
Seção intitulada “PII Sanitization Behavior”When the session was created with surface_hint: "embed", the resolver applies in-process sanitization before returning the payload:
- Removed:
customer.cpf,customer.email,customer.phone,customer.birth_date,representative_link,representative_token,representative_notify_sent, allflow.steps[].display_dataandflow.steps[].completed_fields. - Preserved:
customer.id,customer.name,customer.cpf_masked, the entireflowblock (id, type, name, layout_mode, settings),status,expires_at,allowed_origins,representative_status,execution_id.
Sessions created without surface_hint (default) receive the full payload.
Note: error
messagefields are localized in pt-BR.
| HTTP | Code | Cause |
|---|---|---|
| 404 | LINK_NOT_FOUND | Token does not exist. |
| 410 | LINK_EXPIRED | Token expired. |
| 429 | RATE_LIMITED | 10 requests per hour per source IP exceeded, shared across all tokens. |
Resending the Representative Link
Seção intitulada “Resending the Representative Link”POST /api/v1/onboarding/executions/{execution_id}/resend-representative-linkRe-fires the SMS or WhatsApp dispatch for the representative of an existing kyc_minor execution. Does not create a new representative execution; it re-uses the existing linked child and re-attempts notification.
Authentication
Seção intitulada “Authentication”This endpoint requires an admin or supervisor JWT obtained via the Guardline backoffice. The X-API-Key header alone is not sufficient. Calls without a valid JWT return 401 MISSING_TOKEN.
The endpoint is intended for administrative recovery flows (an integrator’s support team detects the representative did not receive the link, the operator clicks “resend” in the backoffice). It is not designed to be called from integrator-facing automation. For integrator-driven dispatch, set notify.linked: true on the original POST /sessions and (if the link expires) create a new session.
Idempotency
Seção intitulada “Idempotency”The endpoint honors the Idempotency-Key header; see Idempotency.
A maximum of 3 resends per execution. The 4th attempt returns 429 RATE_LIMITED.
| HTTP | Code | Cause |
|---|---|---|
| 401 | MISSING_TOKEN | JWT not provided. |
| 403 | INSUFFICIENT_PERMISSIONS | JWT does not have admin or supervisor role. |
| 404 | EXECUTION_NOT_FOUND | The primary execution does not exist. |
| 404 | NO_RESENDABLE_CHILD | No representative child in a non-terminal state to resend. |
| 409 | EXECUTION_TERMINATED | Primary execution is in a terminal state. |
| 429 | RATE_LIMITED | 3-resend cap reached. |
Execution Lifecycle
Seção intitulada “Execution Lifecycle”State Diagram
Seção intitulada “State Diagram”State Descriptions
Seção intitulada “State Descriptions”| State | Terminal | Description |
|---|---|---|
created | No | Session created, user has not yet accessed the link. |
in_progress | No | User is navigating through journey steps. |
pending_representative | No | Minor’s journey finished, waiting for the legal representative. Applies only to kyc_minor. |
processing | No | All steps submitted; decision engine processing. |
pending_review | No | Referred for manual review. Resolution SLA: 72 hours. |
approved | Yes | Approved by the engine or by an analyst. |
rejected | Yes | Rejected by the engine or by an analyst. |
blocked | Yes | Journey interrupted by a security rule (CPF restriction, biometric attempt limit). |
cancelled | Yes | Cancelled by the integrator or by an administrator. |
expired | Yes | Session expired before completion. |
Linkage Sub-state for kyc_minor
Seção intitulada “Linkage Sub-state for kyc_minor”For kyc_minor, the parent execution holds the lifecycle state above, while the representative’s progress is tracked separately on the linked child execution. GET /api/v1/link/{token} on the parent’s token surfaces the representative’s status via the representative_status field.
The parent transitions to pending_representative when the minor completes their journey. While the representative is doing their part, the parent stays in pending_representative; the child execution moves through created → in_progress → processing → terminal independently. When the child reaches a terminal positive state, the parent’s consolidation runs, and the parent moves through processing to its own terminal state.
Querying an Execution
Seção intitulada “Querying an Execution”GET /api/v1/onboarding/executions/{execution_id}Returns the complete state of an execution, including data collected during the journey. This endpoint is the primary data source for the integrator. Webhooks notify state transitions; details are obtained through this query.
Standard KYC Response (200)
Seção intitulada “Standard KYC Response (200)”{ "error": false, "data": { "execution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "flow_type": "kyc", "reference_id": "SOL-2026-00099", "status": "approved", "decision": { "result": "approved", "risk_level": "low", "decided_at": "2026-03-26T15:30:00Z", "decided_by": "engine", "reasons": [] }, "customer": { "full_name": "Carlos Oliveira", "tax_id": "11132345344", "tax_id_masked": "111.***.**3-44", "birth_date": "1990-05-15", "nationality": "Brasileira", "email": "carlos@exemplo.com", "phone": "+5511999990000", "income": "8500.00", "is_pep": false, "has_foreign_tax_obligation": false, "address": { "zip_code": "01310-100", "street": "Avenida Paulista", "number": "1000", "complement": "Sala 302", "neighborhood": "Bela Vista", "city": "São Paulo", "state": "SP" }, "document": { "type": "cnh", "status": "verified" }, "biometric": { "status": "verified" } }, "links": { "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/abc123def456", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/abc123def456" }, "steps_completed": 8, "steps_total": 8, "created_at": "2026-03-26T14:00:00Z", "started_at": "2026-03-26T14:05:00Z", "completed_at": "2026-03-26T14:12:00Z", "expires_at": "2026-04-02T14:00:00Z" }}KYC Minor Response (200)
Seção intitulada “KYC Minor Response (200)”For kyc_minor, the parent execution’s customer block describes the minor. The representative’s data is fetched by querying the linked child execution separately. Embedded representative data inside the parent response is reserved for backwards-compatible cases and may be deprecated in a future version; new integrations should query the linked child execution explicitly using the execution_id returned in linked_subjects[].execution_id from POST /sessions.
The relationship field on the representative’s customer record accepts: mother, father, grandparent, legal_guardian, tutor, other. When the relationship is anything other than mother or father, the representative’s journey requires uploading a guardianship document; the upload status is reflected in guardianship_document (null or "uploaded").
The decision block is only populated when the execution reaches a terminal state (approved, rejected) or is under review (pending_review). In all other states, the field is null.
Decision Reasons
Seção intitulada “Decision Reasons”The decision.reasons array carries the canonical reason(s) behind the decision. It is always present as an array: empty ([]) for automatic engine decisions and for approvals, and populated only when a manual decision (analyst review) recorded a catalog reason. Each entry has a machine-readable code and a human-readable description:
"decision": { "result": "rejected", "risk_level": "high", "decided_at": "2026-03-26T15:30:00Z", "decided_by": "analyst", "reasons": [ { "code": "pep_restriction", "description": "Restrição PEP" } ]}Possible code values:
| code | description |
|---|---|
invalid_document | Documento inválido |
fraud_suspected | Suspeita de fraude |
pep_restriction | Restrição PEP |
sanctions_match | Match em sanções |
biometric_failed | Biometria reprovada |
high_risk | Risco elevado |
underage | Menor de idade |
incomplete_docs | Documentação incompleta |
expired_document | Documento vencido |
data_inconsistency | Dados inconsistentes |
other | Outro |
Treat code as the stable identifier for programmatic handling. The description mirrors the catalog label and may be tailored per tenant; the catalog can also be extended over time, so consumers should tolerate unknown codes gracefully.
| HTTP | Code | Cause |
|---|---|---|
| 401 | MISSING_API_KEY | API Key header not provided. |
| 401 | INVALID_API_KEY | API Key invalid, revoked, or expired. |
| 404 | EXECUTION_NOT_FOUND | The execution_id does not exist or does not belong to the integrator. |
Listing Executions
Seção intitulada “Listing Executions”GET /api/v1/onboarding/executionsReturns a paginated list of executions. By default, executions that are terminal (approved, rejected, blocked, expired, cancelled) or still in the initial created state (created but never advanced past the first step submission) are excluded from the response; pass an explicit status to include them (for example, status=created returns the excluded shell rows).
Results are ordered by created_at descending, with execution_id descending as a deterministic tie-breaker between rows created at the same instant.
Query Parameters
Seção intitulada “Query Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Requested page. |
per_page | integer | 20 | Items per page (maximum: 100). Values above 100 are clamped. |
reference_id | string (≤ 255) | (unset) | Filter by exact, case-sensitive match on the execution’s reference_id. Useful for retrieving every execution tagged with a given correlator, such as the integrator’s own customer ID, account ID, or order number. |
flow_id | UUID | (unset) | Filter by the flow that originated the executions. A malformed UUID returns 400 INVALID_REQUEST. |
status | string | (unset) | Filter by execution status code (e.g., created, in_progress, pending_review, approved, rejected, blocked, expired, cancelled). When provided, the default exclusion set (terminal states plus created) is bypassed, so status=approved returns approved executions and status=created returns shell rows that would otherwise be hidden. Unknown codes return an empty page. |
name | string | (unset) | Filter by case-insensitive substring on the customer’s name. Matches both customers_person.name (kyc, kyc_minor, kyc_representative) and customers_company.company_name (kyb). |
tax_id | string | (unset) | Filter by exact match on the customer’s tax identifier. Accepts CPF (11 digits) or CNPJ (14 digits), with or without punctuation: 383.417.298-73, 38.341.729/0001-87, 38341729873, and 38341729000187 are all normalized to digits before comparison. Anything else returns 400 INVALID_REQUEST. |
Filters are combinable: ?reference_id=SOL-2026-00042&status=approved returns approved executions whose reference_id matches exactly; ?name=silva&status=in_progress returns in-progress executions whose customer name contains silva.
Response (200)
Seção intitulada “Response (200)”{ "error": false, "data": { "items": [ { "execution_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "flow_type": "kyc_minor", "reference_id": "SOL-2026-00042", "status": "in_progress", "steps_completed": 9, "steps_total": 19, "customer": { "full_name": "Marcus Vinicius Sales", "tax_id": "23117555854", "tax_id_masked": "***175.558**", "email": "msales@exemplo.com.br", "is_pep": false, "has_foreign_tax_obligation": false }, "links": { "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/xyz987def456", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/xyz987def456" }, "data_divergences": [], "created_at": "2026-03-31T09:00:00Z", "started_at": "2026-03-31T09:03:00Z", "expires_at": "2026-04-07T09:00:00Z" } ], "pagination": { "page": 1, "per_page": 20, "total": 1, "total_pages": 1 } }}The customer block carries the same shape on the list and on the per-id detail endpoint. On the list, only summary identification fields are populated (full_name, tax_id, tax_id_masked, email, is_pep, has_foreign_tax_obligation). Address, document, and biometric sub-blocks plus the decision envelope are only returned by GET /api/v1/onboarding/executions/{execution_id}. The customer field is omitted (rather than emitted as null) on rows where no customer record is linked yet (e.g., probes or sessions abandoned before the first step). For kyb executions, tax_id is the CNPJ (14 digits) and email is omitted (companies do not carry an email on the customer record).
The tax_id field is the raw identifier (CPF or CNPJ, digits only). The legacy tax_id_masked is still emitted for backwards compatibility and will be removed in a future version; new integrations should consume tax_id.
The links block, when present, carries the tenant portal URLs the customer or your embed can use to resume or continue the journey: url is the direct link users open in a browser, embed_url is the iframe-friendly variant. Both are constructed from the execution’s link token and the tenant’s onboarding hostname (the same token POST /onboarding/sessions returns via its top-level url field). The block is emitted on both the list and the per-id detail endpoint, and is omitted (not emitted as null) for orphan executions that have no linked token, so integrators can safely condition rendering on the field’s presence.
| HTTP | Code | Cause |
|---|---|---|
| 400 | INVALID_REQUEST | flow_id is not a valid UUID, reference_id exceeds 255 characters, or tax_id is not 11 or 14 digits after normalization. |
| 401 | MISSING_API_KEY / INVALID_API_KEY | Authentication missing or invalid. |
Cancelling an Execution
Seção intitulada “Cancelling an Execution”POST /api/v1/executions/{execution_id}/cancelTransitions the execution to the cancelled state. When the execution has child executions (the representative under a kyc_minor parent), the call cascades and cancels them in the same operation.
Because the partial unique index idx_executions_active_person_flow excludes terminal statuses, cancelling frees the (customer_person_id, flow_id) slot. After a successful cancel, a new POST /onboarding/sessions for the same CPF and flow stops returning 409 ACTIVE_SESSION_EXISTS and a fresh session can be created. Typical use cases: reopening a kyc_minor journey for retesting, recovering from a wrong submission, or rotating a session that the end user could not finish in time.
A onboarding.cancelled webhook event is emitted on success; see Event Families.
Authentication
Seção intitulada “Authentication”Accepts either:
X-API-Key(gl_live_*orgl_test_*), orAuthorization: Bearer <JWT>(backoffice JWT).
Idempotency
Seção intitulada “Idempotency”Honors the Idempotency-Key header; see Idempotency.
Request Body
Seção intitulada “Request Body”The body is optional. The only accepted field is reason, recorded in the audit trail and in the onboarding.cancelled event payload:
{ "reason": "Duplicate registration, recreating with correct data"}| Field | Type | Required | Description |
|---|---|---|---|
reason | string (max 500) | No | Free-text reason recorded in audit and event payload. |
Success Response (200)
Seção intitulada “Success Response (200)”{ "error": false, "data": { "execution_id": "dc8fc63f-f25a-4ce7-a9a7-2005bb3f1689", "cancelled_children": [ "b11d2b44-3a8e-4a0f-9c4d-90d1f8a2e111" ], "status_code": "cancelled", "cancelled_at": "2026-05-27T13:50:00Z" }}| Field | Type | Description |
|---|---|---|
execution_id | UUID | Execution that was cancelled. |
cancelled_children | UUID array | UUIDs of child executions cancelled in cascade (the representative under a kyc_minor). Empty when the execution has no children. |
status_code | string | Always cancelled on success. |
cancelled_at | ISO 8601 | Cancellation timestamp. |
| HTTP | Code | Cause |
|---|---|---|
| 400 | INVALID_REQUEST | The path UUID is malformed. |
| 401 | MISSING_TOKEN / INVALID_API_KEY | Authentication missing, invalid, or expired. |
| 404 | NOT_FOUND | No execution with the given UUID. |
| 422 | UNPROCESSABLE | Execution already in a terminal state (approved, rejected, cancelled, expired, blocked). |
| 429 | RATE_LIMITED | Rate limit exceeded. |
Webhooks
Seção intitulada “Webhooks”Webhooks are HTTP notifications sent by Guardline to the integrator’s backend when an execution changes state. Payloads are intentionally lean: they contain the event identifiers and a small data block. Complete data should be obtained via the query endpoint.
Event Families
Seção intitulada “Event Families”Two families coexist post-rewrite:
- **
onboarding.***: state of the onboarding as a whole. Emitted on every execution. - **
linkage.***: state of a linked subject within a paired flow. Today this covers the legal representative forkyc_minor; future flow types (such askybofficers) will reuse the same family. Emitted on the parent execution, with the linked subject’s outcome reflected in the payload.
onboarding.* Events
Seção intitulada “onboarding. Events”| Event | Description |
|---|---|
onboarding.started | Session created via POST /sessions. |
onboarding.completed | All steps submitted; decision engine processing. |
onboarding.approved | Approved by the engine or by an analyst. |
onboarding.rejected | Rejected by the engine or by an analyst. |
onboarding.review | Referred for manual review. |
onboarding.blocked | Journey interrupted by a security rule. |
onboarding.expired | Session expired before completion. |
onboarding.cancelled | Cancelled by the integrator or by an administrator. |
linkage.* Events (kyc_minor)
Seção intitulada “linkage. Events (kyc_minor)”| Event | Description |
|---|---|
linkage.pending | The minor completed their journey and the parent transitioned to pending_representative. The representative’s URL was created at POST /sessions; this event marks the parent’s transition, not the child’s creation. |
linkage.started | The legal representative accessed their link and submitted the first step. |
linkage.completed | The legal representative completed their journey; the parent’s consolidation has been triggered. |
linkage.rejected | An analyst rejected the representative’s execution in manual review. |
linkage.expired | The representative’s execution expired before completion. |
Legacy representative.* Events
Seção intitulada “Legacy representative. Events”The integrator API rewrite replaced representative.{pending, started, completed} with the linkage.* family above. The legacy event types are not emitted on the new code paths, but historical webhook deliveries in integrator-side audit logs may reference them. Treat both taxonomies as informationally equivalent for historical data; only linkage.* is emitted today.
Emission Order
Seção intitulada “Emission Order”Events follow a strict emission order within each execution. Due to retries, events may arrive out of order at the integrator’s endpoint. The event_sequence field (monotonically increasing per execution) should be used as the canonical ordering criterion, not the timestamp or delivery order.
Standard KYC
Seção intitulada “Standard KYC”Automatic approval:
onboarding.started (1) session createdonboarding.completed (2) all steps submittedonboarding.approved (3) approved by engineRejection:
onboarding.started (1)onboarding.completed (2)onboarding.rejected (3)Manual review, then resolved:
onboarding.started (1)onboarding.completed (2)onboarding.review (3)onboarding.approved (4) analyst decision (or onboarding.rejected)Blocked:
onboarding.started (1)onboarding.blocked (2)Expired:
onboarding.started (1)onboarding.expired (2)KYC Minor
Seção intitulada “KYC Minor”All events on a kyc_minor execution reference the parent (minor’s) execution_id. The representative’s progress surfaces via linkage.* events with the same execution_id.
Automatic approval:
onboarding.started (1) session createdlinkage.pending (2) minor completed; parent in pending_representativelinkage.started (3) representative accessed their linklinkage.completed (4) representative finishedonboarding.completed (5) consolidation; decision engine processingonboarding.approved (6) approved by engineRejection:
onboarding.started (1)linkage.pending (2)linkage.started (3)linkage.completed (4)onboarding.completed (5)onboarding.rejected (6)Manual review, then resolved:
onboarding.started (1)linkage.pending (2)linkage.started (3)linkage.completed (4)onboarding.completed (5)onboarding.review (6)onboarding.approved (7) analyst decision (or onboarding.rejected)Blocked during the minor module:
onboarding.started (1)onboarding.blocked (2)Expired waiting for the representative:
onboarding.started (1)linkage.pending (2)linkage.expired (3) representative's link expiredonboarding.expired (4) parent expired as a consequenceRepresentative rejected by analyst:
onboarding.started (1)linkage.pending (2)linkage.started (3)linkage.completed (4)onboarding.completed (5)onboarding.review (6)linkage.rejected (7) analyst rejected the representativeonboarding.rejected (8) parent decision propagatesPayload
Seção intitulada “Payload”{ "event": "onboarding.approved", "execution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "flow_type": "kyc_minor", "reference_id": "SOL-2026-00042", "event_sequence": 6, "timestamp": "2026-04-01T10:45:00Z", "data": { "decision": { "result": "approved", "risk_level": "low", "decided_by": "engine", "decided_at": "2026-04-01T10:45:00Z" }, "liveness": { "status": "executed", "result": "passed" } }}| Field | Description |
|---|---|
event | Event type. |
execution_id | Parent execution identifier. For linkage.* events on kyc_minor, this is the minor’s execution ID; the linked child’s identifier is included inside the payload data block. |
flow_type | Journey type. |
reference_id | Integrator identifier, as sent during session creation. |
event_sequence | Sequential event number within the execution, for canonical ordering. |
timestamp | Event emission timestamp in ISO 8601. |
data | Optional object carrying the decision and proof-of-life blocks below. Absent when the event has neither. |
data.decision
Seção intitulada “data.decision”Present on events that carry a decision or a linkage state transition (onboarding.approved/rejected/review/blocked and linkage.*).
| Field | Description |
|---|---|
result | approved, rejected, pending_review, blocked, or the linkage state (pending_representative, in_progress, completed, expired). |
risk_level | Derived from the decision score: low (score up to 150), medium (up to 350), high (up to 700), critical (above 700). |
decided_by | Always engine on webhook payloads. The query endpoint may report manual for analyst decisions. |
decided_at | Decision timestamp in ISO 8601. |
data.liveness
Seção intitulada “data.liveness”Present on terminal events (onboarding.completed/approved/rejected/review/blocked/expired). The proof-of-life step (iProov) is modular per flow: a journey may run it, skip it by configuration, or belong to a flow variant without it, and the integrator can toggle it per session via the biometric field on POST /api/v1/onboarding/sessions. This block lets the integrator tell “not executed by configuration” apart from “failed”.
status | Meaning |
|---|---|
executed | The proof of life ran and passed. Step completion requires passed liveness evidence, so executed implies approval; result: "passed" accompanies it. |
not_executed | The flow carries the step but the journey ended without completing it (failure, attempt cap, abandonment). |
skipped_by_config | The step exists in the flow but was switched off by configuration: disabled on the flow, disabled for this session ("biometric": false), or an unmet flow condition. |
not_configured | The flow variant has no proof-of-life step. |
The result field is present only when status is executed and currently only takes passed. Best-effort semantics: if the block cannot be resolved at emission time it is omitted; its absence must not fail the integrator’s parser.
Notes:
- **Absence of the block means “unknown”, never
not_configured.** Envelopes emitted before this contract existed also lack it. Do not infer configuration from the absence of thelivenessobject; “the journey has no proof of life” is signaled exclusively by the explicitnot_configuredvalue. onboarding.cancelleddeliberately does NOT carry the block: it is scoped to outcome-bearing terminal events, and a cancellation is not an outcome.
Headers
Seção intitulada “Headers”| Header | Description |
|---|---|
Content-Type | Always application/json. |
X-Guardline-Event-ID | Stable event UUID, identical across retries. Use for deduplication. |
X-Guardline-Delivery-ID | Unique UUID per delivery attempt. Changes on each retry. |
X-Guardline-Attempt-Number | Sequential attempt number (1 for first delivery, 2 for first retry, etc.). |
X-Guardline-Timestamp | Unix timestamp of the signature moment (when HMAC is enabled). |
X-Guardline-Signature | HMAC-SHA256 signature in hexadecimal (when HMAC is enabled). |
Authorization | Bearer token (when OAuth is enabled). |
Webhook Authentication
Seção intitulada “Webhook Authentication”Guardline supports three authentication modes for webhook delivery. The mode is configured per webhook endpoint in the admin panel. HMAC signature is always present regardless of the selected mode, providing message integrity verification on every delivery.
| Mode | HMAC Signature | OAuth Bearer | Client Certificate |
|---|---|---|---|
| HMAC only (default) | Yes | No | No |
| OAuth 2.0 | Yes | Yes | No |
| mTLS | Yes | No | Yes |
| OAuth 2.0 + mTLS | Yes | Yes | Yes |
HMAC-SHA256 Authentication
Seção intitulada “HMAC-SHA256 Authentication”HMAC authentication uses a shared secret provisioned during integrator setup in the admin panel.
Verification process on the receiver:
- Extract the
X-Guardline-Timestampheader. - Reject if the timestamp is older than 5 minutes relative to the server clock.
- Concatenate timestamp and request body:
{timestamp}.{body}. - Compute
HMAC-SHA256of the concatenation using the shared secret. - Encode the result in hexadecimal.
- Compare with the
X-Guardline-Signatureheader using constant-time comparison. - Reject if there is a mismatch.
Pseudocode:
import hmac, hashlib, timedef verify_webhook(body: bytes, timestamp: str, signature: str, secret: str) -> bool: if abs(time.time() - int(timestamp)) > 300: return False message = f"{timestamp}.".encode() + body expected = hmac.new( secret.encode(), message, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature)mTLS Authentication
Seção intitulada “mTLS Authentication”Mutual TLS authentication ensures Guardline’s identity at the transport layer, without requiring application-level validation.
Setup:
- During provisioning, Guardline provides the root CA certificate (
guardline-ca.pem). - The integrator configures their webhook server to require client certificates.
- The integrator adds
guardline-ca.pemto the server’s trusted CA list. - On each delivery, Guardline presents a client certificate signed by the root CA.
- The integrator’s server validates the certificate chain automatically at the TLS layer.
The CN field of the client certificate contains the integrator’s instance identifier. The integrator can optionally validate this field to ensure the request comes from the correct instance.
When mTLS is enabled, the X-Guardline-Signature and X-Guardline-Timestamp headers are not sent.
OAuth 2.0 Authentication
Seção intitulada “OAuth 2.0 Authentication”OAuth authentication allows Guardline to obtain an access token before each delivery, using the Client Credentials flow (RFC 6749, section 4.4).
Setup:
- The integrator provides in the admin panel:
token_endpoint,client_id,client_secret, and optionallyscope. - Before each delivery, Guardline requests a token from the provided endpoint.
- The token request follows the standard format:
POST {token_endpoint}Content-Type: application/x-www-form-urlencodedgrant_type=client_credentials&client_id={client_id}&client_secret={client_secret}&scope={scope}- Guardline includes the obtained token in the webhook authorization header:
Authorization: Bearer {access_token}- The integrator validates the token at their webhook endpoint according to their OAuth implementation.
Guardline caches the token respecting the expires_in field from the token endpoint response, with a 60-second safety margin. If the webhook receiver returns 401, the cached token is invalidated and a fresh token is acquired for a single retry.
If token acquisition fails due to invalid credentials (401 or 400 from the token endpoint), the delivery is immediately marked as exhausted and is not retried. Transient token endpoint failures (500, timeout) follow the normal retry policy.
Endpoint Configuration
Seção intitulada “Endpoint Configuration”The webhook endpoint is configured in the Guardline admin panel. Requirements:
| Requirement | Detail |
|---|---|
| Protocol | HTTPS required. |
| TLS version | 1.2 or higher. |
| Success response | Any 2xx code (body ignored). |
| Timeout | Respond within 10 seconds. |
| Idempotency | Deduplicate deliveries using X-Guardline-Event-ID. |
Implementation recommendation: receive the webhook, enqueue for asynchronous processing, and respond immediately with 200. This avoids timeouts and ensures reliable delivery.
Credential Rotation
Seção intitulada “Credential Rotation”When OAuth credentials or mTLS certificates are rotated, in-flight retries for previously dispatched deliveries continue using the credentials that were active at the time of dispatch. New deliveries use the updated credentials immediately.
If failed deliveries need to be re-sent with the new credentials, a manual resend from the admin panel creates a new delivery using the latest configured credentials, preserving the original event payload. The original delivery is kept intact for audit purposes.
Retry Policy
Seção intitulada “Retry Policy”When the integrator’s endpoint returns a non-2xx code or does not respond within 10 seconds, Guardline resends the notification with exponential backoff:
| Attempt | Interval after failure |
|---|---|
| 1 | 5 seconds |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 30 minutes |
| 6 | 1 hour |
| 7 | 2 hours |
| 8 | 4 hours |
| 9 | 8 hours |
| 10 | 12 hours |
After 10 failed attempts, the delivery is marked as exhausted and an internal alert is generated. Notifications remain accessible via the polling endpoint (see Notification Polling). Manual resend is also available in the admin panel.
Notification Polling
Seção intitulada “Notification Polling”The polling endpoint is the fallback mechanism to ensure no notification is lost, regardless of webhook failures. It can also be used as the primary integration mechanism by integrators who prefer a pull model.
Querying Pending Notifications
Seção intitulada “Querying Pending Notifications”GET /api/v1/onboarding/notifications| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | pending | Filter by state: pending, failed, retrying. Comma-separated for multiple values. |
page | integer | 1 | Requested page. |
per_page | integer | 50 | Items per page (maximum: 200). |
Response (200):
{ "error": false, "data": { "items": [ { "delivery_id": "d4e5f6a7-b8c9-0123-def0-123456789abc", "execution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "event": "onboarding.approved", "event_sequence": 6, "status": "pending", "attempts": 3, "max_attempts": 10, "last_attempt_at": "2026-04-01T10:48:00Z", "created_at": "2026-04-01T10:45:00Z" } ], "total": 1, "page": 1, "per_page": 50, "total_pages": 1 }}Acknowledging Processing
Seção intitulada “Acknowledging Processing”POST /api/v1/onboarding/notifications/{delivery_id}/ackMarks a notification as processed. After acknowledgment, the notification no longer appears in queries with pending status. The operation is idempotent: repeated calls for the same delivery_id return success with no side effects.
Response (200):
{ "error": false, "data": { "delivery_id": "d4e5f6a7-b8c9-0123-def0-123456789abc", "status": "acknowledged" }}Complete Flow Diagrams
Seção intitulada “Complete Flow Diagrams”Standard KYC
Seção intitulada “Standard KYC”KYC Minor with Guardline-Delivered Notification
Seção intitulada “KYC Minor with Guardline-Delivered Notification”Error Reference
Seção intitulada “Error Reference”HTTP Status Codes
Seção intitulada “HTTP Status Codes”| Code | Meaning | Guidance |
|---|---|---|
| 200 | Success | Process normally. |
| 201 | Resource created | Process normally. |
| 400 | Invalid request | Fix the payload. Do not retry. |
| 401 | Authentication failed | Check the API Key (or JWT for resend-representative-link). Do not retry. |
| 404 | Resource not found | Check the identifiers. Do not retry. |
| 409 | Conflict | Handle according to the specific error code. |
| 410 | Gone | The resource was valid but is no longer accessible (e.g., expired link). |
| 429 | Rate limited | Wait for the interval indicated in the Retry-After header. |
| 500 | Internal error | Retry with exponential backoff (maximum 3 attempts). |
| 502 | Internal error during processing | Retry with exponential backoff. |
Domain Error Codes
Seção intitulada “Domain Error Codes”Note: error
messagefields are localized in pt-BR. Match oncodefor programmatic branching.
| Code | HTTP | Description |
|---|---|---|
MISSING_API_KEY | 401 | API Key header not provided. |
INVALID_API_KEY | 401 | API Key invalid, revoked, or expired. |
MISSING_TOKEN | 401 | JWT not provided (resend-representative-link only). |
INSUFFICIENT_PERMISSIONS | 403 | JWT lacks the required role. |
INVALID_REQUEST | 400 | Malformed JSON, unknown field, or wrong field type. |
VALIDATION_FAILED | 400 | Field-level validation failed. |
LINKED_SUBJECTS_REQUIRED | 400 | kyc_minor request with missing/empty/oversized linked_subjects or wrong role. |
INVALID_FLOW_TYPE | 400 | Flow type not active for the integrator, or flow_id resolves to an internal-use flow type. |
MISSING_REQUIRED_FIELDS | 400 | kyc_minor missing primary.tax_id or primary.full_name. |
ACTIVE_SESSION_EXISTS | 409 | Active session exists for the same primary subject and flow. |
EXECUTION_NOT_FOUND | 404 | Execution not found or does not belong to the integrator. |
EXECUTION_TERMINATED | 409 | The execution has already reached a terminal state. |
NO_RESENDABLE_CHILD | 404 | No representative child to resend (resend-representative-link only). |
LINK_NOT_FOUND | 404 | Link token does not exist. |
LINK_EXPIRED | 410 | Link token expired. |
FLOW_NOT_FOUND | 404 | Flow not found. |
NOTIFICATION_NOT_FOUND | 404 | Notification not found. |
RATE_LIMITED | 429 | Request limit exceeded. |
ONBOARDING_ERROR | 502 | Internal error during session creation. |
Endpoint Summary
Seção intitulada “Endpoint Summary”| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/v1/onboarding/flows | API Key | List available journeys. |
| POST | /api/v1/onboarding/sessions | API Key | Create an onboarding session (with optional linked subjects). |
| GET | /api/v1/link/{token} | Public (rate-limited) | Resolve a link token to flow + customer payload. Sanitized when the session was created with surface_hint=embed. |
| GET | /api/v1/onboarding/executions/{execution_id} | API Key | Query execution with complete data. |
| GET | /api/v1/onboarding/executions | API Key | List active executions. |
| POST | /api/v1/onboarding/executions/{execution_id}/resend-representative-link | Admin/Supervisor JWT | Resend the representative link via SMS/WhatsApp. |
| POST | /api/v1/executions/{execution_id}/cancel | API Key | Cancel an execution (and its representative child for kyc_minor), releasing the active-session slot for reuse. |
| GET | /api/v1/onboarding/notifications | API Key | Poll pending webhook notifications. |
| POST | /api/v1/onboarding/notifications/{delivery_id}/ack | API Key | Acknowledge a webhook notification. |