Pular para o conteúdo

ONP API Reference

This document describes how to integrate with the Guardline onboarding API. It covers authentication, creating onboarding sessions, declaring linked subjects (the legal representative for kyc_minor at session creation, a company’s legal representatives mid-journey for kyb), resolving session links, reusable QR dispensers, 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.

The integration flow involves four participants:

ParticipantResponsibility
Integrator backendCreates sessions, receives webhooks, queries executions
Guardline APIManages sessions, tokens, executions, linked subjects, and notifications
Integrator frontendRenders the link directly or mounts an iframe pointing at it
Guardline onboardingConducts 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.

EnvironmentBase URLAPI Key prefix
Sandboxhttps://{instance}.onp.dev.guardline.com.brgl_test_
Productionhttps://{instance}.onp.prod.guardline.com.brgl_live_

The {instance} value is provisioned during integrator setup and identifies the dedicated infrastructure. Each instance has its own isolated database, application, and storage.

All integrator-facing API calls require the X-API-Key header with the key corresponding to the environment.

X-API-Key: gl_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Keys 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.

The prefix is a label chosen when the key is created, not a routing mechanism, and it defaults to live when the environment is not specified. A key issued on a sandbox instance without an explicit environment therefore reads gl_live_ while still being a sandbox-only key. The prefix says nothing about which instance will accept it: a key authenticates only against the instance that issued it.

A credential problem always answers 401, never 400. MISSING_TOKEN when no header was sent, INVALID_API_KEY when the key is unknown, revoked or expired. A 400 means the key was accepted and the request body was rejected, so reissuing the key will not resolve it.

HTTP requests are rejected. HTTPS with TLS 1.2 or higher is required in all environments.

Three surfaces depart from that rule:

EndpointAuth
POST /api/v1/onboarding/executions/{execution_id}/resend-representative-linkAdmin or supervisor JWT from the Guardline backoffice. See Resending the Representative Link.
POST and GET /api/v1/onboarding/executions/{execution_id}/representativesAPI key, JWT, or no credential at all: the KYB journey opens them from a magic link and the path execution_id is the capability. See KYB Legal Representatives.
POST /api/v1/onboarding/dispensers/{token}/sessions and GET /api/v1/link/{token}Public. Possession of the token is the capability, and both are rate limited per source IP.

Mutating endpoints accept an optional Idempotency-Key request header (any unique string of at most 255 characters, typically a UUID; a longer one returns 400 INVALID_IDEMPOTENCY_KEY). 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.

The cache is keyed by the pair (key, endpoint), so reusing one key across two different endpoints does not collide. One endpoint deliberately ignores the header: POST /onboarding/dispensers/{token}/sessions, where replaying a cached response would hand two people the same journey.

Use this when retrying after a network timeout to avoid duplicate executions. Generate a fresh key for each new logical operation.

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. 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.

The message field is resolved from the Accept-Language request header. pt-BR is the base locale and is what a caller that sends no header receives.

Accept-LanguageResult
absent, unparseable, or unmatchedpt-BR (base locale)
pt-BRpt-BR
es-ARSpanish (Argentina)
Terminal window
curl https://{instance}.onp.dev.guardline.com.br/api/v1/link/invalido \
-H "Accept-Language: es-AR"
{
"error": true,
"message": "Enlace no encontrado",
"code": "LINK_NOT_FOUND",
"request_id": "77440a78-6e11-4def-8c08-31b1189ccbd4"
}

The catalogue is keyed by error code. A code with no entry in the requested locale degrades to its pt-BR message rather than to a blank or a placeholder, so a response is never empty and the code is always authoritative. This is the reason integrators must never match on message text: the same code yields different strings depending on the header.

GET /api/v1/onboarding/flows

Returns the list of configured and active journeys for the integrator, with the metadata needed to create sessions correctly.

kyc_representative is filtered server-side and does not appear: that child is created only as a linked_subjects[] entry of a kyc_minor session, and pointing flow_id at one is rejected with INVALID_FLOW_TYPE.

kyb_representative is not filtered and can appear in this list on instances that configure it. Do not start a session against it. It is the subflow a KYB parent provisions for each declared representative (see KYB Legal Representatives), and starting it directly produces an orphan execution with no company to attach to. Select journeys by the type field rather than by position, and treat kyc, kyb and kyc_minor as the startable set.

{
"error": false,
"data": [
{
"flow_id": "61000000-0000-0000-0000-000000000001",
"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": "61000000-0000-0000-0000-000000000010",
"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" }
]
}
]
}

Each flow entry also returns flow_id (the specific flow version, usable as flow_id in POST /sessions) and steps_count (the number of entries in steps).

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.

KYB flows additionally return company_schema, the same array shape describing the fields accepted under the company block (see Company Pre-fill (KYB)). It is absent on person flows, which have no company subject. Read the schema rather than hardcoding the field list: it is the server’s own statement of what the flow accepts.

POST /api/v1/onboarding/sessions

Creates a new onboarding session and returns a URL per subject. For kyc_minor, the response includes a separate URL for the linked representative alongside the minor’s URL. KYB representatives are not declared here: their count and identity are only known mid-journey, so they are declared at the representatives-setup step instead (see KYB Legal Representatives).

FieldTypeRequiredDescription
flow_typestringOne of flow_type or flow_idJourney type. Possible values: kyc, kyb, kyc_minor. Resolves to the active flow of that type for the integrator.
flow_idUUIDOne of flow_type or flow_idSpecific 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.
channelenumNoOrigin of the session: web, sdk, api, asistido_agencia. Default: web. The legacy alias channel_code is also accepted; when both are sent, channel wins. asistido_agencia marks a session opened by a branch officer on the applicant’s behalf and is only meaningful on instances configured for it.
layout_modeenumNoUI layout for the journey: stepper (default) or chat.
reference_idstring (≤ 255)NoFree-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_idUUIDNoLinks 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.
metadataobjectNoFree-form passthrough. Persisted on the execution and exposed back in queries.
surface_hintenumNoSet 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).
redirect_urlstring (≤ 2048)NoAbsolute https URL the journey sends the person to after the terminal screen, so the integrator gets its user back into its own application. Rejected with 422 UNPROCESSABLE when the scheme is not https or the host is missing; javascript:, data: and http:// values are refused at creation. Ignored in embed mode, where the embed bridge posts dismissed and the host page owns navigation. With no value the terminal screen offers no exit.
primaryobjectRequired for kyc_minor; optional for kyc and kybPrimary 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 document step in the journey. primary.tax_id is validated against the flow’s country, see Tax ID by Flow Country.
companyobjectNo; KYB onlyCompany pre-fill for a KYB flow. See Company Pre-fill (KYB). Dropped on person flows: a KYB execution’s subject is a company, and the person and company subjects are mutually exclusive on an execution.
linked_subjectsarrayRequired for kyc_minor (exactly one entry, role: "representative"); empty for other flow typesSubjects linked to the primary. See Linked Subjects.
notifyobjectNoNotification opt-in flags. See Notification Opt-in.

primary.tax_id is validated against the country the resolved flow declares, not against the raw flow_type in the request. The country comes from the flow record, so the same API contract serves Brazilian and Argentine instances without a per-tenant switch.

Flow countryAccepted in primary.tax_id
BRA valid CPF (11 digits, check digits verified).
ARA CUIT/CUIL of 11 digits with the check digit verified, or a DNI of 7 to 8 digits. Letters are refused.
otherThe foreign-identifier shape: 4 to 20 characters, letters, digits and hyphens, upper-cased and stripped of spaces, dots and slashes. Never reduced to digits.

Formatted input is accepted and normalized server-side, so 20-12345678-9 and 20123456789 are the same value. A mismatch returns 400 INVALID_TAX_ID.

Two consequences worth planning for:

  • The check runs after the flow is resolved. A request that omits flow_id and relies on flow_type resolves to the active flow of that type, which is not necessarily the one the integrator has in mind on an instance carrying more than one. Pin flow_id when the instance serves flows of more than one country, otherwise an Argentine document can be validated against a Brazilian flow and rejected.
  • A flow with no explicit country falls back to BR, which is the strict side.

On a KYB flow the execution’s subject is a company, so primary has nothing to attach to and is dropped. The company block is the KYB counterpart: it opens the journey with the tax ID the integrator already holds instead of asking the applicant for it again.

FieldTypeRequiredDescription
company.tax_idstringNoCompany CUIT, with or without the mask. Must belong to a juridical person: AFIP prefixes 30, 33 or 34. Anything else returns 422 CUIT_NOT_JURIDICAL_PERSON rather than materializing a company record for a human being.
company.razon_socialstring (≤ 255)NoRegistered company name, subject to the same free-text whitelist as full_name. Optional: the padron lookup and the statute OCR both fill it later, and neither overwrites a name the integrator supplied.
{
"flow_id": "61000000-0000-0000-0000-000000000092",
"company": {
"tax_id": "30712345678",
"razon_social": "Estancias y Colonias Arizona S.A."
},
"reference_id": "KYB-2026-00042"
}

A CUIT supplied here is locked for the rest of the journey: the applicant cannot edit it in the company step, and an attempt returns COMPANY_TAX_ID_LOCKED. If the number is wrong, issue a new session rather than trying to correct it in place.

The linked_subjects array declares any subject linked to the primary by the flow contract. Today, this covers the legal representative for kyc_minor. For KYB, the company’s legal representatives are declared mid-journey instead, see KYB Legal Representatives.

Each entry has:

FieldTypeRequiredDescription
roleenumYesRole discriminator: representative or legal_representative. kyc_minor requires exactly one entry with representative. Other values (such as officer) are reserved and rejected.
subjectobjectYesSubject data block. May be empty ({}) for shell creation.

The subject block accepts the following fields:

FieldTypeRequiredDescription
full_namestringPaired with tax_id (see Field Requiredness below)Full name.
tax_idstringPaired with full_namePersonal tax ID, in the shape the flow’s country issues: CPF on BR flows, CUIT/CUIL or DNI on AR flows. See Tax ID by Flow Country.
phonestring (E.164)Required if notify.linked is true; optional otherwisePhone with country code. Validated against the flow’s phone policy: a mobile-only flow rejects landlines with 422 PHONE_POLICY_VIOLATION.
birth_datestring (YYYY-MM-DD)NoDate of birth. When provided, it must indicate age 18 or over; a younger representative is rejected with 422 REPRESENTATIVE_AGE_BELOW_MINIMUM.
emailstringNoEmail.
relationshipenumRequired for kyc_minor; optional otherwiseRelationship to the primary. Values: mother, father, grandparent, legal_guardian, tutor, other.
notification_channelenumNoPreferred channel for backend-managed delivery: sms, whatsapp, or email_sms.

The service accepts three shapes:

ShapeContentEffect
Shellsubject: {} (or subject without both tax_id and full_name)Creates the linked execution without materializing a customer record. Integrator delivers the URL manually.
Mixedsubject: {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.
Materializedsubject: {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, the service requires exactly one entry in linked_subjects with role: "representative". Any of the following returns 400 LINKED_SUBJECTS_REQUIRED:

  • linked_subjects field absent
  • linked_subjects: [] (empty array)
  • More than one entry
  • An entry with role other than representative

Shell creation is explicit: send linked_subjects: [{role: "representative", subject: {}}].

For kyc_minor, two additional gates apply to the representative entry:

  • linked_subjects[0].subject.relationship is required. Omitting it returns 400 VALIDATION_FAILED.
  • The representative’s tax_id must differ from the minor’s tax_id. Reusing the same CPF returns 422 CPF_MINOR_REPRESENTATIVE_MATCH.

The notify block controls whether Guardline delivers the URL via SMS or WhatsApp.

FieldTypeDefaultDescription
notify.primaryboolfalseReserved. Currently has no effect; future versions will dispatch the primary’s URL.
notify.linkedboolfalseWhen true, Guardline records the opt-in and dispatches an SMS or WhatsApp message containing the representative’s URL when the minor finishes their module (not at session creation). The opt-in applies to the kyc_minor representative whose subject.phone is present.

For kyc_minor, the representative is only needed after the minor completes their journey. Guardline persists the notify.linked opt-in at POST /sessions and fires the SMS or WhatsApp message at that later moment (when the parent transitions to pending_representative), not synchronously during session creation. As a result, the linked_subjects[i].notify_sent field in the POST /sessions response is always false. The actual dispatch outcome surfaces later via representative_notify_sent on GET /api/v1/link/{token} for the parent’s token (read from the child execution row).

When the dispatch fires and subject.phone is present, the channel is selected by:

  1. linked_subjects[i].subject.notification_channel if present (sms, whatsapp, or email_sms).
  2. Otherwise, both channels are attempted in parallel as a fallback.

The linked_subjects[i].notify_sent field in the POST /sessions response is always false, because the dispatch is deferred to the minor’s completion (see Dispatch Timing). It does not indicate whether a message was sent.

To observe the actual dispatch outcome, read representative_notify_sent from GET /api/v1/link/{token}:

  • true: at least one attempted channel returned a provider message ID (Twilio MessageSID).
  • false: notify.linked was off, the subject’s phone was absent, or the provider rejected the send.

A provider message ID does not mean the message was delivered to the recipient. Asynchronous delivery confirmation (provider status callbacks) is not surfaced in this API; 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.

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".

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.

{
"flow_type": "kyc",
"channel": "web",
"reference_id": "SOL-2026-00099"
}

The customer record is materialized when the user submits the document step in the journey. customer_id is absent from the response until then.

{
"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 and records the notify.linked opt-in. The SMS to +5511999998888 containing the representative’s URL is dispatched when the minor finishes their module, not at session creation; the POST /sessions response therefore returns notify_sent: false (see Dispatch Timing).

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": {} }
]
}

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).

{
"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.

{
"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": false
}
]
}
}
FieldDescription
execution_idUnique execution identifier for the primary subject.
customer_idCustomer record identifier. Absent from the response in cold-start (no primary data sent), because no customer record exists yet; present and stable once the record exists. Test for the presence of the key, not against the zero UUID: earlier builds published 00000000-0000-0000-0000-000000000000 here and integrators read it as an identifier that would be filled in later, which is why the field is now elided instead.
urlPublic URL for the primary subject’s journey.
embed_urlURL pointing to the iframe-friendly route for the primary subject. Use this as the iframe src when rendering inside the integrator’s app.
tokenSlug of the link, also accepted by GET /api/v1/link/{token}.
expires_atLink expiration in ISO 8601. Default: 7 days after creation, configurable per flow.
statusInitial execution status (created).
reference_idEcho 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].roleRole of the linked subject (representative for kyc_minor).
linked_subjects[i].execution_idLinked subject’s execution ID.
linked_subjects[i].urlLinked subject’s journey URL.
linked_subjects[i].embed_urlIframe-friendly URL for the linked subject.
linked_subjects[i].subject_statusLinked subject’s initial status (created).
linked_subjects[i].notify_sentAlways false in this response. Representative dispatch is deferred to the minor’s completion, so the live outcome is read later from representative_notify_sent on GET /api/v1/link/{token} (see Dispatch Timing).

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 message fields are localized by Accept-Language. Match on code for programmatic branching, not on message text.

HTTPCodeCause
400INVALID_REQUESTMalformed JSON, unknown field (the message names the field), or wrong field type.
400VALIDATION_FAILEDField-level validation failed: neither flow_id nor flow_type was sent, flow_id is not a UUID, flow_id and flow_type disagree, the resolved flow is inactive, flow_type not in the enum, channel outside the enum, relationship or notification_channel outside the enum, missing relationship on the kyc_minor representative, kyc_minor birth_date outside the 14-17 range, or invalid linked_subjects[i].role. The envelope does not name which of these fired; correlate with the request_id.
400INVALID_TAX_IDprimary.tax_id does not match the shape the resolved flow’s country issues. See Tax ID by Flow Country.
400LINKED_SUBJECTS_REQUIREDkyc_minor request with missing, empty, or oversized linked_subjects array, or with a role other than representative.
400INVALID_FLOW_TYPEThe 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).
400MISSING_REQUIRED_FIELDSkyc_minor request without primary.tax_id or without primary.full_name.
401MISSING_API_KEYAPI Key header not provided.
401INVALID_API_KEYAPI Key invalid, revoked, or expired.
404FLOW_NOT_FOUNDThe flow_id does not exist.
409ACTIVE_SESSION_EXISTSAn active session already exists for the same primary subject and flow.
422PHONE_POLICY_VIOLATIONA mobile-only flow received a landline number in primary.phone or linked_subjects[i].subject.phone.
422CPF_MINOR_REPRESENTATIVE_MATCHkyc_minor request where the representative’s tax_id equals the minor’s tax_id.
422REPRESENTATIVE_AGE_BELOW_MINIMUMThe representative’s birth_date indicates age under 18.
422CUIT_NOT_JURIDICAL_PERSONcompany.tax_id is not a juridical-person CUIT (prefix 30, 33 or 34).
422UNPROCESSABLEredirect_url is not an absolute https URL with a host, or exceeds 2048 characters.
429RATE_LIMITEDRequest limit exceeded. Respect the Retry-After header.
502ONBOARDING_ERRORInternal error during session creation. Retry after a short delay; persistent failures should be reported to support with the request_id.
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.

{
"error": false,
"data": {
"execution_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c",
"flow": {
"id": "61000000-0000-0000-0000-000000000010",
"type": "kyc_minor",
"name": "KYC Menor",
"layout_mode": "stepper",
"cpf_match_mode": "block",
"phone_policy": "mobile-only",
"settings": {},
"steps": [
{
"id": "...",
"execution_step_id": "...",
"step_type_code": "welcome",
"step_type_name": "Boas-vindas",
"step_order": 1,
"title": "Boas-vindas",
"config": {},
"is_required": true,
"is_enabled": true,
"status": "pending"
}
]
},
"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"
},
"minor": null,
"relationship": "mother",
"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
}
}
FieldDescription
execution_idExecution identifier of the subject this token belongs to.
flowFlow definition: id, type, name, layout_mode, cpf_match_mode (alert or block), phone_policy (any or mobile-only), settings, and the ordered steps. Each step carries id, execution_step_id, step_type_code, step_type_name, step_order, title, description, config, is_required, is_enabled, status, and (default surface only) display_data and completed_fields.
customerCustomer record. Fields below.
customer.idUUID of the customer record. Absent when the execution is in cold-start.
customer.nameFull name.
customer.cpfUnmasked CPF. Removed when surface_hint=embed.
customer.cpf_maskedMasked CPF (always present when cpf is present).
customer.emailEmail. Removed when surface_hint=embed.
customer.phonePhone in E.164. Removed when surface_hint=embed.
customer.birth_dateDate of birth in YYYY-MM-DD. Removed when surface_hint=embed.
minorWhen this token belongs to the representative of a kyc_minor pair, carries the minor’s customer block (same shape as customer); null otherwise. Removed when surface_hint=embed.
relationshipRepresentative’s relationship to the minor (mother, father, etc.). Present only on kyc_minor tokens; omitted otherwise.
statusCurrent execution status. See Execution Lifecycle.
expires_atLink expiration.
allowed_originsOrigins allowed to embed the iframe (CSP frame-ancestors).
representative_linkPresent 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_tokenLinked representative’s token (FE uses for redirection). Removed when surface_hint=embed.
representative_statusCurrent status of the representative’s execution (pending, in_progress, etc.). Preserved under surface_hint=embed so the FE knows whether to poll.
representative_notify_senttrue when the representative dispatch (fired on minor completion, see Dispatch Timing) returned a provider message ID; false when notify.linked was off or phone was absent; absent when the flow has no representative. Removed when surface_hint=embed.
redirect_urlEcho of the redirect_url the session was created with, so the journey knows where to send the person after the terminal screen. Absent when the session declared none, in which case the terminal screen offers no exit. Re-validated on read: a stored value that no longer meets the absolute-https policy is returned as absent rather than honored.
companyCompany bound to this execution, so a KYB journey can open with the CUIT the integrator sent instead of asking for it again. Carries id, name (razon social, may be empty) and tax_id (CUIT, digits only, returned unmasked). Absent on person journeys and on a KYB execution whose company is not materialized yet. Preserved when surface_hint=embed, see below.
beneficial_owners_snapshotThe company’s beneficial-owner declaration (owners, DDJJ, razon social), seeded onto a representative child at declare time so the representative’s KYC can render and confirm it. Absent on non-representative journeys.

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, minor (set to null), representative_link, representative_token, representative_notify_sent, all flow.steps[].display_data and flow.steps[].completed_fields.
  • Preserved: customer.id, customer.name, customer.cpf_masked, the entire flow block (id, type, name, layout_mode, cpf_match_mode, phone_policy, settings, steps), relationship, status, expires_at, allowed_origins, representative_status, execution_id, company, redirect_url, beneficial_owners_snapshot.

company is preserved deliberately, and the asymmetry with customer.cpf is only apparent. What the rule removes is the identity document of a natural person, which is what customer.cpf carries whatever the local journey calls it. A CUIT is a company’s fiscal id and is public in the ARCA padron that the same journey queries by CUIT, so blanking it would buy no privacy and would break the pre-fill it exists for. The two also cannot coexist: an execution’s subject is either a person or a company, never both, so on a KYB journey the customer block is empty anyway.

Sessions created without surface_hint (default) receive the full payload.

Note: error message fields are localized by Accept-Language. See Error Message Locale.

HTTPCodeCause
404LINK_NOT_FOUNDToken does not exist.
410LINK_EXPIREDToken expired.
429RATE_LIMITED10 requests per hour per source IP exceeded, shared across all tokens.
POST /api/v1/onboarding/executions/{execution_id}/resend-representative-link

Re-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.

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.

The endpoint honors the Idempotency-Key header; see Idempotency.

A maximum of 3 resends per execution. The 4th attempt returns 429 RATE_LIMITED.

HTTPCodeCause
401MISSING_TOKENJWT not provided.
403FORBIDDENJWT does not have admin or supervisor role.
404EXECUTION_NOT_FOUNDThe primary execution does not exist.
404NO_RESENDABLE_CHILDNo representative child in a non-terminal state to resend.
409EXECUTION_TERMINATEDPrimary execution is in a terminal state.
429RATE_LIMITED3-resend cap reached.

A KYB journey does not know upfront how many people can legally bind the company. The count and identity are declared mid-journey, at the representatives-setup step, once the company subject is established. Each declared representative becomes its own KYC subflow with its own link, so the KYB parent and every representative progress independently.

This generalizes the single-representative pattern of kyc_minor to 1..N, and it is why KYB representatives are not declared in linked_subjects[] at session creation.

POST /api/v1/onboarding/executions/{execution_id}/representatives

{execution_id} is the KYB parent execution. Honors Idempotency-Key.

FieldTypeRequiredDescription
representativesarray (1 to 20)YesOne entry per legal representative. Each entry uses the same subject block as linked_subjects[i].subject: full_name, tax_id, email, phone, birth_date, relationship, notification_channel.
notifyobjectNoNotification opt-in flags, same semantics as on POST /sessions.
{
"representatives": [
{ "full_name": "María Gómez", "tax_id": "27123456784", "phone": "+543794123456" },
{ "full_name": "Juan Pérez", "tax_id": "20123456786", "email": "juan@example.com" }
],
"notify": { "linked": true }
}

Response (200):

{
"error": false,
"data": {
"representatives": [
{
"role": "legal_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",
"link_token": "<link_token>",
"expires_at": "2026-05-20T12:49:34Z",
"notify_sent": true
}
]
}
}

Each entry carries the representative’s own child execution and a fresh link. url resumes that representative’s KYC subflow and is what the integrator shares when it handles delivery itself; notify_sent reports whether Guardline dispatched the link, and requires phone on the entry.

GET /api/v1/onboarding/executions/{execution_id}/representatives

Returns the declared children with their live status, so the integrator (or the journey) can render per-representative progress without opening each link.

{
"error": false,
"data": {
"representatives": [
{
"execution_id": "c0aef12b-7c2a-4b3e-9a8d-f1e2d3c4b5a6",
"status_code": "in_progress",
"full_name": "María Gómez",
"link_token": "<link_token>",
"expires_at": "2026-05-20T12:49:34Z",
"notify_sent": true
}
]
}
}

Both endpoints accept an API key or a JWT, and also answer without either: the KYB journey opens them from a magic link that carries no credential, and the path execution_id is the capability. The handler validates the execution, and both are rate limited to 60 requests per minute per source IP, sized for the progress poll (roughly 6 per minute per journey).

HTTPCodeCause
400INVALID_IDexecution_id is not a valid UUID.
400INVALID_REQUESTMalformed JSON or wrong field type.
400VALIDATION_FAILEDEmpty representatives, more than 20 entries, or a field-level failure inside an entry.
404EXECUTION_NOT_FOUNDThe parent execution does not exist.
429RATE_LIMITED60/minute per IP exceeded.
502INTERNAL_ERRORInternal error while provisioning or listing the children.

A dispenser is a stable token behind a QR code that mints a fresh onboarding session on every scan. It is the answer to the branch and event use case, where one printed code serves many different people, and it exists because a normal session link is single-subject: reusing it would put a second person into the first person’s execution.

Only self-service flow types are eligible. kyc and kyb can have a dispenser; kyc_minor and kyc_representative cannot, because those journeys are provisioned as a declared pair rather than opened by whoever walks up.

POST /api/v1/onboarding/dispensers

Requires an API key or JWT. Idempotent by design: one active dispenser per flow, so calling it again returns the existing one rather than minting a second.

{ "flow_id": "61000000-0000-0000-0000-000000000090" }

Response (200):

{
"error": false,
"data": {
"dispenser_token": "<dispenser_token>",
"flow_id": "61000000-0000-0000-0000-000000000090",
"url": "https://acme.onp.prod.guardline.com.br/onboarding/d/kQ8xR2mN7pL4"
}
}

url is the value to render as a QR code. It is a scan target, not a journey link.

HTTPCodeCause
400VALIDATION_FAILEDflow_id absent or not a UUID.
404FLOW_NOT_FOUNDThe flow does not exist.
422INVALID_FLOW_TYPEThe flow type does not support a reusable dispenser (kyc_minor, kyc_representative).
POST /api/v1/onboarding/dispensers/{token}/sessions

Public, called once per scan by the person’s own browser. Possession of the dispenser token is the capability, so no API key or JWT is sent. Rate limited to 30 requests per minute per source IP.

{
"error": false,
"data": {
"url": "https://acme.onp.prod.guardline.com.br/onboarding/link/abc123def456",
"token": "<token>",
"expires_at": "2026-05-20T12:49:34Z"
}
}

Each call creates a new cold-start session, so every scan yields a different link and a different execution. This endpoint deliberately does not honor Idempotency-Key: replaying a cached response would hand two people the same journey, which is exactly what the dispenser exists to prevent.

HTTPCodeCause
400MISSING_TOKENDispenser token absent from the path.
404LINK_NOT_FOUNDDispenser does not exist or is inactive.
429RATE_LIMITED30/minute per IP exceeded.
StateTerminalDescription
createdNoSession created, user has not yet accessed the link.
in_progressNoUser is navigating through journey steps.
pending_representativeNoMinor’s journey finished, waiting for the legal representative. Applies only to kyc_minor.
processingNoAll steps submitted; decision engine processing.
pending_reviewNoReferred for manual review. Resolution SLA: 72 hours.
approvedYesApproved by the engine or by an analyst.
rejectedYesRejected by the engine or by an analyst.
blockedYesJourney interrupted by a security rule (CPF restriction, biometric attempt limit).
cancelledYesCancelled by the integrator or by an administrator.
expiredYesSession expired before completion.

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 createdin_progressprocessing → 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.

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.

{
"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"
},
"customer": {
"full_name": "Carlos Oliveira",
"tax_id": "11122233344",
"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"
}
},
"data_divergences": [],
"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"
}
}
FieldDescription
execution_idExecution identifier.
statusCurrent execution status. See Execution Lifecycle.
flow_typeJourney type.
reference_idEcho of the integrator’s correlator, when the session declared one.
parent_execution_idNames the parent on a linked child execution (a kyb_representative, or a kyc_minor representative), so a representative’s journey can be correlated back to the onboarding that spawned it without keeping a local map. Absent on root executions.
steps_completed / steps_totalJourney progress.
decisionDecision block: result, risk_level, decided_by, decided_at. Absent until a decision exists.
customerPerson subject of the execution. Fields below. Absent on KYB executions.
companyCompany subject, on KYB executions: legal_name, tax_id (raw, no formatting), trade_name, tax_category, company_type. An execution’s subject is a person or a company, never both, so this block and customer are mutually exclusive. List rows carry only legal_name and tax_id; the single-execution read adds the rest.
deviceDevice-risk projection of the journey. Fields below. Single-execution read only: the list endpoint omits it on purpose, since filling it costs a payload read and parse per item on every page.
data_divergencesAlways present as an array, empty when there is none. Each entry reports a field whose value submitted during the journey diverged from the pre-filled value: field, expected_masked, submitted_masked, step_id, recorded_at. Values are masked at insertion; raw values never leave the backend.
created_at / started_at / completed_atJourney timestamps.
expires_atThe execution deadline, derived from the flow’s expires_in_hours.
link_expires_atThe separate window the one-time link stays openable for.

expires_at and link_expires_at are both returned because neither alone says when the journey dies. The expiration worker retires an execution on whichever of three clocks fires first: link_expires_at while still created, expires_at while created or in_progress, and the session-inactivity window once in_progress. Returning only expires_at made integrators read the 7-day link figure from POST /sessions and the shorter execution figure here under the same name, with no way to tell which governs.

FieldDescription
full_nameFull name as collected.
given_name / surnameThe discriminated halves of full_name, resolved from the columns the localized name step writes, then that step’s payload, then a split of full_name. full_name is always sent alongside so a consumer can recompose the exact value when a compound given name splits differently than expected.
tax_idThe person’s identity document, raw and unformatted. On Brazilian journeys this is the CPF. On localized journeys it is whatever the journey collects as the identity document: the DNI, the cédula or the passport.
tax_numberThe person’s fiscal id, on Argentine journeys: the CUIL, or the CUIT when they declared one. A deliberately separate field from tax_id, because moving the fiscal id into tax_id would silently change the meaning of a field integrators already read as the identity document. Collected by the document step, so it is empty on Brazilian journeys and on any localized flow that does not collect it. Treat it as optional. This is the field to key a user profile by on an Argentine integration.
tax_id_maskedMasked variant, kept for backward compatibility. Soft-deprecated: prefer tax_id. New integrations should ignore it; existing ones keep receiving it unchanged.
birth_date, nationality, email, phone, incomeAs collected. phone carries the dial prefix whenever the source does, so a number collected by a localized journey reads as E.164 rather than as national digits with no country.
is_pep, has_foreign_tax_obligationAlways emitted, including when false.
addresszip_code, street, number, complement, neighborhood, city, state.
document, biometricVerification status blocks: type and status.
representativeRepresentative data nested inside customer, carrying the same shape plus relationship, guardianship_document and status. Reserved for backward compatibility; see the note on kyc_minor below.

Present when the journey has a readable device profile. Deliberately narrower than the analyst-facing profile: IP forensics (address, ISP, city, coordinates, ASN, postal code) and browser fingerprint detail stay behind the authenticated internal surfaces rather than riding an outbound call. ip_country is the one geographic field kept, because country granularity is what the anti-fraud reading needs and it does not locate a person.

FieldDescription
provider, device_id, adviceSource provider, its device identifier, and its recommendation.
risk_score, risk_labelProvider risk score and band.
is_rooted, is_emulator, is_vpn, is_proxy, is_tor, is_bot, is_remote_access, is_incognitoAlways emitted, including when false, so a receiver can distinguish false from absent. Absence is carried by the whole device block being omitted.
device_type, os, os_version, ip_countryDevice and origin metadata.
signalsProvider signal names behind the flags above (for example Jailbreak, Root hiding application), so a receiver wanting the evidence for a flag does not have to call back. Names only.

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 key is absent from the response, not present as null. The same holds for every optional block on this payload (customer, company, device, parent_execution_id, address, document, biometric): test for the presence of the key. The one deliberate exception is guardianship_document, which is emitted as an explicit null when no document was uploaded, and data_divergences, which is always present as an array.

HTTPCodeCause
401MISSING_API_KEYAPI Key header not provided.
401INVALID_API_KEYAPI Key invalid, revoked, or expired.
404EXECUTION_NOT_FOUNDThe execution_id does not exist or does not belong to the integrator.
GET /api/v1/onboarding/executions

Returns active executions with valid sessions. Executions in a terminal state (approved, rejected, blocked, expired, cancelled) are not included in the response.

ParameterTypeDefaultDescription
pageinteger1Requested page.
per_pageinteger20Items per page (maximum: 100).
statusstring(none)Filter by execution status. When omitted, terminal states (approved, rejected, blocked, expired, cancelled) are excluded; passing an explicit status includes it.
reference_idstring (≤ 255)(none)Exact match on the integrator’s reference_id. As an exact-key lookup it returns the row in any status: the default terminal-status exclusion does not apply, so this is the way to fetch an execution you already know the id of without having to guess its status.
flow_idUUID(none)Filter by a specific flow version.
namestring (≤ 255)(none)Case-insensitive substring match on the customer’s name.
tax_idstring(none)Filter by CPF (11 digits) or CNPJ (14 digits).
{
"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": 5,
"steps_total": 9,
"created_at": "2026-03-31T09:00:00Z",
"started_at": "2026-03-31T09:03:00Z",
"expires_at": "2026-04-07T09:00:00Z",
"customer": {
"full_name": "João Santos",
"tax_id": "12345678900",
"tax_id_masked": "123.***.**9-00",
"email": "joao@exemplo.com"
}
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1,
"total_pages": 1
}
}
}

Pagination metadata is nested under data.pagination (page, per_page, total, total_pages). Each item carries summary fields plus a customer block (full_name, tax_id, tax_id_masked, email); the block is null when no customer is linked yet (cold-start). The decision block is detail-only: fetch it via the individual query endpoint.

POST /api/v1/executions/{execution_id}/cancel

Transitions 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.

Accepts either:

  • X-API-Key (gl_live_* or gl_test_*), or
  • Authorization: Bearer <JWT> (backoffice JWT).

Honors the Idempotency-Key header; see Idempotency.

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"
}
FieldTypeRequiredDescription
reasonstring (max 500)NoFree-text reason recorded in audit and event payload.
{
"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"
}
}
FieldTypeDescription
execution_idUUIDExecution that was cancelled.
cancelled_childrenUUID arrayUUIDs of child executions cancelled in cascade (the representative under a kyc_minor). Empty when the execution has no children.
status_codestringAlways cancelled on success.
cancelled_atISO 8601Cancellation timestamp.
HTTPCodeCause
400INVALID_IDThe path UUID is malformed.
400INVALID_REQUESTThe request body is present but malformed.
401MISSING_TOKEN / INVALID_API_KEYAuthentication missing, invalid, or expired.
404NOT_FOUNDNo execution with the given UUID.
409EXECUTION_TERMINALExecution already in a terminal state (approved, rejected, cancelled, expired, blocked).
429RATE_LIMITEDRate limit exceeded.

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.

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. This covers the legal representative for kyc_minor and the legal representatives declared on a kyb parent. Emitted on the parent execution, with the linked subject’s outcome reflected in the payload.
EventDescription
onboarding.startedSession created via POST /sessions.
onboarding.completedAll steps submitted; decision engine processing.
onboarding.approvedApproved by the engine or by an analyst.
onboarding.rejectedRejected by the engine or by an analyst.
onboarding.reviewReferred for manual review.
onboarding.blockedJourney interrupted by a security rule.
onboarding.expiredSession expired before completion.
onboarding.cancelledCancelled by the integrator or by an administrator.
onboarding.documents_requestedAn analyst reopened specific steps and reissued the link. See below.

The only event that asks the integrator to act rather than merely reporting an outcome. It fires when an analyst in the backoffice reopens specific steps of an execution under review, which rotates the link and restarts its clock. The payload carries what the integrator needs to send the customer back without querying anything:

{
"event": "onboarding.documents_requested",
"execution_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c",
"flow_type": "kyc",
"reference_id": "SOL-2026-00042",
"timestamp": "2026-05-13T12:49:34Z",
"data": {
"reason": "Foto do documento ilegível",
"requested_steps": ["document-front", "document-back"],
"resume_url": "https://acme.onp.prod.guardline.com.br/onboarding/link/<token>",
"link_expires_at": "2026-05-20T12:49:34Z"
}
}

resume_url travels in the payload because the link was rotated: the URL the integrator already held is no longer the one to send. reference_id falls back to the execution id when the session was created without one, so the field is never an empty string.

EventDescription
linkage.pendingThe 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.startedThe legal representative accessed their link and submitted the first step.
linkage.completedThe legal representative completed their journey; the parent’s consolidation has been triggered.
linkage.rejectedAn analyst rejected the representative’s execution in manual review.
linkage.expiredThe representative’s execution expired before completion.

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.

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.

Automatic approval:

onboarding.started (1) session created
onboarding.completed (2) all steps submitted
onboarding.approved (3) approved by engine

Rejection:

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)

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 created
linkage.pending (2) minor completed; parent in pending_representative
linkage.started (3) representative accessed their link
linkage.completed (4) representative finished
onboarding.completed (5) consolidation; decision engine processing
onboarding.approved (6) approved by engine

Rejection:

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 expired
onboarding.expired (4) parent expired as a consequence

Representative 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 representative
onboarding.rejected (8) parent decision propagates
{
"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"
}
FieldDescription
eventEvent type.
execution_idParent 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_typeJourney type.
reference_idIntegrator identifier, as sent during session creation.
event_sequenceSequential event number within the execution, for canonical ordering.
timestampEvent emission timestamp in ISO 8601.
dataAdditive block, absent when it would be empty. Contents below.

Everything here is additive: a key is simply absent when there is nothing to report, so a receiver written against an older envelope keeps working.

KeyWhen presentContents
data.decisionOnce the execution has a decisionresult, risk_level, decided_by, decided_at.
data.deviceOn every event except onboarding.started, when the journey has a readable device profileThe same device-risk block as the execution read (see device block). onboarding.started is skipped on purpose: it fires at session creation, before the browser profiler has run, so the read is guaranteed empty.
data.companyOn kyb parent eventstax_id, legal_name, and trade_name when known.
data.legal_representativesOn kyb parent eventsOne entry per declared representative: execution_id, status, flag (true when the representative is rejected or expired, so a consumer can spot the blocking one without evaluating statuses itself), plus reference_id, full_name, name, surname and document when the representative’s record carries them.

The KYB shape is deliberate: a company event carries the company identity and all its representatives in one payload, so the integrator receives the whole company at once instead of correlating N per-execution events. Only the kyb parent emits it; the children are kyb_representative executions and emit their own ordinary events.

For onboarding.documents_requested the data block carries a different, event-specific shape, documented with that event.

HeaderDescription
Content-TypeAlways application/json.
X-Guardline-Event-IDStable event UUID, identical across retries. Use for deduplication.
X-Guardline-Delivery-IDUnique UUID per delivery attempt. Changes on each retry.
X-Guardline-Attempt-NumberSequential attempt number (1 for first delivery, 2 for first retry, etc.).
X-Guardline-TimestampUnix timestamp of the signature moment (when HMAC is enabled).
X-Guardline-SignatureHMAC-SHA256 signature in hexadecimal (when HMAC is enabled).
AuthorizationBearer token (when OAuth is enabled).
X-API-KeyStatic key the integrator’s endpoint expects, when one is configured on the webhook endpoint. Optional and independent of the HMAC signature.

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.

ModeHMAC SignatureOAuth BearerClient Certificate
HMAC only (default)YesNoNo
OAuth 2.0YesYesNo
mTLSYesNoYes
OAuth 2.0 + mTLSYesYesYes

HMAC authentication uses a shared secret provisioned during integrator setup in the admin panel.

Verification process on the receiver:

  1. Extract the X-Guardline-Timestamp header.
  2. Reject if the timestamp is older than 5 minutes relative to the server clock.
  3. Concatenate timestamp and request body: {timestamp}.{body}.
  4. Compute HMAC-SHA256 of the concatenation using the shared secret.
  5. Encode the result in hexadecimal.
  6. Compare with the X-Guardline-Signature header using constant-time comparison.
  7. Reject if there is a mismatch.

Pseudocode:

import hmac, hashlib, time
def 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)

Mutual TLS authentication ensures Guardline’s identity at the transport layer, without requiring application-level validation.

Setup:

  1. During provisioning, Guardline provides the root CA certificate (guardline-ca.pem).
  2. The integrator configures their webhook server to require client certificates.
  3. The integrator adds guardline-ca.pem to the server’s trusted CA list.
  4. On each delivery, Guardline presents a client certificate signed by the root CA.
  5. 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 authentication allows Guardline to obtain an access token before each delivery, using the Client Credentials flow (RFC 6749, section 4.4).

Setup:

  1. The integrator provides in the admin panel: token_endpoint, client_id, client_secret, and optionally scope.
  2. Before each delivery, Guardline requests a token from the provided endpoint.
  3. The token request follows the standard format:
POST {token_endpoint}
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}&scope={scope}
  1. Guardline includes the obtained token in the webhook authorization header:
Authorization: Bearer {access_token}
  1. 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.

The webhook endpoint is configured in the Guardline admin panel. Requirements:

RequirementDetail
ProtocolHTTPS required.
TLS version1.2 or higher.
Success responseAny 2xx code (body ignored).
TimeoutRespond within 10 seconds.
IdempotencyDeduplicate 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.

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.

When the integrator’s endpoint returns a non-2xx code or does not respond within 10 seconds, Guardline resends the notification with exponential backoff:

AttemptInterval after failure
15 seconds
230 seconds
32 minutes
410 minutes
530 minutes
61 hour
72 hours
84 hours
98 hours
1012 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.

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.

GET /api/v1/onboarding/notifications

This endpoint uses cursor-based pagination.

ParameterTypeDefaultDescription
statusstringpendingFilter by state: pending, failed, retrying. Comma-separated for multiple values.
limitinteger50Items per page (1 to 200; values outside the range fall back to 50).
after_cursorstring(none)Cursor for the next page: the base64-encoded delivery_id returned in next_cursor by the previous call.

Response (200):

{
"error": false,
"data": {
"items": [
{
"delivery_id": "d4e5f6a7-b8c9-0123-def0-123456789abc",
"execution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"event": "onboarding.approved",
"flow_type": "kyc_minor",
"reference_id": "SOL-2026-00042",
"event_sequence": 6,
"status": "pending",
"attempts": 3,
"last_attempt_at": "2026-04-01T10:48:00Z",
"timestamp": "2026-04-01T10:45:00Z"
}
],
"next_cursor": null
}
}

next_cursor is a base64 string when more pages may exist (returned when the page is full), or null when there are no further items. Pass it back as after_cursor to fetch the next page. Each item carries flow_type and reference_id, the current attempts count, the timestamp of the delivery, and last_attempt_at (the most recent attempt; omitted before the first attempt).

POST /api/v1/onboarding/notifications/{delivery_id}/ack

Marks 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"
}
}
CodeMeaningGuidance
200SuccessProcess normally.
201Resource createdProcess normally.
400Invalid requestFix the payload. Do not retry.
401Authentication failedCheck the API Key (or JWT for resend-representative-link). Do not retry.
404Resource not foundCheck the identifiers. Do not retry.
409ConflictHandle according to the specific error code.
410GoneThe resource was valid but is no longer accessible (e.g., expired link).
429Rate limitedWait for the interval indicated in the Retry-After header.
500Internal errorRetry with exponential backoff (maximum 3 attempts).
502Internal error during processingRetry with exponential backoff.

Note: error message fields are localized by Accept-Language (see Error Message Locale). Match on code for programmatic branching.

CodeHTTPDescription
MISSING_API_KEY401API Key header not provided.
INVALID_API_KEY401API Key invalid, revoked, or expired.
MISSING_TOKEN401JWT not provided (resend-representative-link), or dispenser token absent from the path (400 on that route).
FORBIDDEN403JWT lacks the required role (admin or supervisor).
INVALID_REQUEST400Malformed JSON, unknown field, or wrong field type.
INVALID_ID400A path UUID is malformed.
VALIDATION_FAILED400Field-level validation failed, including a request that sends neither flow_id nor flow_type.
INVALID_TAX_ID400primary.tax_id does not match the shape the resolved flow’s country issues.
INVALID_IDEMPOTENCY_KEY400Idempotency-Key header longer than 255 characters.
LINKED_SUBJECTS_REQUIRED400kyc_minor request with missing/empty/oversized linked_subjects or wrong role.
INVALID_FLOW_TYPE400 / 422400 when flow_id resolves to an internal-use flow type; 422 when a flow type does not support a reusable QR dispenser.
MISSING_REQUIRED_FIELDS400kyc_minor missing primary.tax_id or primary.full_name.
PHONE_POLICY_VIOLATION422A mobile-only flow received a landline phone number.
CUIT_NOT_JURIDICAL_PERSON422company.tax_id is not a juridical-person CUIT (prefix 30, 33 or 34).
COMPANY_TAX_ID_LOCKED409The company CUIT was supplied at session creation and cannot be edited inside the journey.
UNPROCESSABLE422A URL policy was violated, such as a redirect_url that is not an absolute https URL.
CPF_MINOR_REPRESENTATIVE_MATCH422The representative’s CPF equals the minor’s CPF.
REPRESENTATIVE_AGE_BELOW_MINIMUM422The representative’s birth_date indicates age under 18.
ACTIVE_SESSION_EXISTS409Active session exists for the same primary subject and flow.
EXECUTION_NOT_FOUND404Execution not found or does not belong to the integrator.
EXECUTION_TERMINAL409A cancel was requested on an execution already in a terminal state.
EXECUTION_TERMINATED409The primary execution has reached a terminal state (resend-representative-link).
NO_RESENDABLE_CHILD404No representative child to resend (resend-representative-link only).
LINK_NOT_FOUND404Link token does not exist.
LINK_EXPIRED410Link token expired.
FLOW_NOT_FOUND404Flow not found.
RATE_LIMITED429Request limit exceeded.
ONBOARDING_ERROR502Internal error during session creation.
INTERNAL_ERROR502Internal error on the KYB representatives endpoints.
MethodPathAuthDescription
GET/api/v1/onboarding/flowsAPI KeyList available journeys.
POST/api/v1/onboarding/sessionsAPI KeyCreate an onboarding session (with optional linked subjects, company pre-fill, and return URL).
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 KeyQuery execution with complete data.
GET/api/v1/onboarding/executionsAPI KeyList active executions.
POST/api/v1/onboarding/executions/{execution_id}/representativesAPI Key, JWT, or journey linkDeclare a KYB company’s 1..N legal representatives and mint their KYC links.
GET/api/v1/onboarding/executions/{execution_id}/representativesAPI Key, JWT, or journey linkPoll the declared representatives’ live KYC status.
POST/api/v1/onboarding/executions/{execution_id}/resend-representative-linkAdmin/Supervisor JWTResend the representative link via SMS/WhatsApp.
POST/api/v1/executions/{execution_id}/cancelAPI KeyCancel an execution (and its representative child for kyc_minor), releasing the active-session slot for reuse.
POST/api/v1/onboarding/dispensersAPI KeyCreate or fetch a flow’s reusable QR dispenser.
POST/api/v1/onboarding/dispensers/{token}/sessionsPublic (rate-limited)Mint a fresh session from a QR scan.
GET/api/v1/onboarding/notificationsAPI KeyPoll pending webhook notifications.
POST/api/v1/onboarding/notifications/{delivery_id}/ackAPI KeyAcknowledge a webhook notification.