Skip to main content
POST
/
api
/
kyc
/
validations
Create KYC Validation
curl --request POST \
  --url http://api.gu1.ai/api/kyc/validations \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "entityId": "<string>",
  "integrationCode": "<string>",
  "doubleCheckRenaper": true,
  "omitWarnings": [
    "<string>"
  ]
}
'
{
  "id": "<string>",
  "entityId": "<string>",
  "organizationId": "<string>",
  "sessionId": "<string>",
  "status": "<string>",
  "provider": "<string>",
  "providerSessionUrl": "<string>",
  "isCurrent": true,
  "metadata": {},
  "createdAt": "<string>",
  "updatedAt": "<string>"
}

Overview

This endpoint creates a new KYC validation session for a person entity using a configured integration provider. After creating the validation, you’ll receive a verification URL that you can share with your customer to complete identity verification.

Complete Flow Diagram

Full sequence from entity creation to KYC validation (production flow; in sandbox with test document numbers the provider step is skipped and the API returns the mock result and webhooks immediately—see Sandbox mock data):
1. Entity and duplicate taxId
  • Create the person entity first via POST /api/entities (with countryCode). If the taxId already exists in the organization, the API returns 409 and does not create a duplicate; use the existing entity’s ID.
  • You need an existing entityId to create a KYC validation.
2. Integration code
  • global_gueno_validation_kyc is the standard code for full KYC verification and works in sandbox with no extra setup.
3. Verification URL
  • In production, the response includes providerSessionUrl. Send that URL to your user; they complete the flow on the provider’s hosted page (document + selfie). The URL is valid until expiresAt.
  • In sandbox, if the entity’s document number is in the test list, no provider session is used: the API returns 201 with pending status and moments later updates the validation and sends webhooks (e.g. kyc.validation_approved or kyc.validation_rejected) with no user step. Important: You must have a webhook endpoint configured to receive the responses - see Sandbox mock data.
4. Webhook events
  • When the validation finishes, the API sends a webhook to your URL. The event is one of: kyc.validation_approved, kyc.validation_rejected, kyc.validation_abandoned, kyc.validation_expired, kyc.validation_cancelled (not a single “completed” event). The payload is the full validation object.
  • You can also poll GET /api/kyc/validations/:id for status updates.

Prerequisites

Before creating a KYC validation:
  1. Person entity must exist: Create a person entity using the Entities API
  2. KYC integration configured: Your organization must have a KYC integration provider activated (e.g., global_gueno_validation_kyc)
  3. Valid API key: Authenticate with your API key
Sandbox vs Production: Sandbox environments do NOT require profile setup or pre-configuration. You can test KYC validations immediately in sandbox with test data. Production environments require:
  • Completed organization onboarding
  • KYC integration provider activated by gu1 team
  • Sufficient credit balance for KYC operations
To get started in sandbox, simply use your sandbox API key - no additional setup needed.

Mock Data (Sandbox)

In sandbox, when the person entity’s document number (taxId) matches one of our test values, the API returns an immediate mock result (e.g. approved, rejected, cancelled) and sends the corresponding webhooks—no real verification is run. Document format does not matter (e.g. 99.990.001 and 99990001 both work). For the full list of test document numbers by format (Argentina DNI/CUIT, Brazil CPF/CNPJ), expected outcomes, and example responses, see Sandbox mock data.

Important Behaviors

Duplicate taxId (POST /entities)

When you call POST /api/entities with a taxId that already exists in the same organization for the same entity type (person/company), the API does not create a second entity. It returns 409 Conflict with error code DUPLICATE_TAX_ID and includes the existing entity’s id, name, and type in the response details.What you should do:
  1. Option A – Check first: Use GET /api/entities?taxId=12345678 (or the by-tax-id endpoint) before creating. If an entity exists, use its entityId for KYC.
  2. Option B – Handle 409: If you get 409, read error.details.existingEntityId from the response and use that entityId for your KYC validation.
  3. Reuse the same entity: Use one entity per person/company and create multiple KYC validations on that same entityId if you need re-verification or new attempts.
Example – Check before create:
// Check if entity exists
const existing = await fetch('https://api.gu1.ai/api/entities?taxId=12345678', {
  headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const data = await existing.json();

let entityId;
if (data.data && data.data.length > 0) {
  entityId = data.data[0].id; // Use existing entity
} else {
  const createRes = await fetch('https://api.gu1.ai/api/entities', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
    body: JSON.stringify({ type: 'person', taxId: '12345678', name: 'John Doe', countryCode: 'AR' })
  });
  const created = await createRes.json();
  entityId = created.entity?.id ?? created.data?.id;
}

// Create KYC validation with the entityId

Multiple KYC Validations per Entity

You can create multiple KYC validations for the same entity:
  • Each validation gets a unique ID and session
  • Only the most recent approved validation is marked as isCurrent: true
  • Use cases: Re-verification, expired validations, failed attempts

Request

Endpoint

POST https://api.gu1.ai/api/kyc/validations

Headers

{
  "Authorization": "Bearer YOUR_API_KEY",
  "Content-Type": "application/json"
}

Query Parameters (Optional)

doubleCheckRenaper
boolean
When true, enables RENAPER double-check for Argentina entities. On terminal statuses the API queries the official registry (data and, when applicable, biometrics) and stores results in metadata. Only when OCR KYC verification returns status approved can a cross-check failure automatically set the validation to rejected; on in_review or rejected the check is informational (enforcementApplied: false).Type: boolean (e.g. ?doubleCheckRenaper=true)Requirements: Entity must be from Argentina (countryCode === 'AR'), and the organization must have RENAPER credentials configured in System Settings → Organizations → Config. If credentials are missing, the creation may fail or the double-check will be skipped depending on configuration.

Body Parameters

entityId
string
required
The UUID of the person entity to verifyType: string (uuid)
integrationCode
string
required
The integration provider code for KYC validationStandard Value: global_gueno_validation_kyc (recommended for most use cases)Type: string (min length: 1)
What is integrationCode?The integrationCode identifies which KYC provider integration to use for verification. Think of it as selecting the verification service.Available Integration Codes:
  • global_gueno_validation_kyc - Recommended - Full KYC with document + selfie + face match + liveness
  • Custom codes may be configured for your organization (contact support)
How to find your integration code:
  1. Log in to gu1 Dashboard
  2. Navigate to Settings → Integrations → KYC Providers
  3. Your active integration code will be listed there
In sandbox environments, global_gueno_validation_kyc works immediately with no configuration.
doubleCheckRenaper
boolean
Same as query param doubleCheckRenaper. When true, enables RENAPER double-check for Argentina. Can be sent in the body or as ?doubleCheckRenaper=true. If both are present, the query param takes precedence.
omitWarnings
string[]
Optional list of KYC warning risk codes (exact strings). Stored on the validation as metadata.omitWarnings. After the session completes, if the validation would be in_review, warnings is non-empty, and every code in warnings appears in this list, the API sets status to approved and keeps all warnings for display and audit. If any warning is not in omitWarnings, status stays in_review. If warnings is empty while status is in_review, this rule does not auto-approve. Invalid or unknown codes in the request body return 400. When the rule applies, metadata.kycOmitWarningsApplied records the decision timestamp and matched warnings.Non-omittable codes (e.g. GUENO_CROSS_ENTITY_DUPLICATED) are rejected in this field with 400 and always block omit auto-approve even if present in warnings.Type: string[] (each element must be an allowlisted code; duplicates are ignored)

Response

Success Response (201 Created)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "entityId": "123e4567-e89b-12d3-a456-426614174000",
  "organizationId": "org_abc123",
  "sessionId": "session_xyz789",
  "status": "pending",
  "provider": "kyc_provider",
  "providerSessionUrl": "https://verify.example.com/session_xyz789",
  "isCurrent": true,
  "metadata": {
    "customField": "customValue",
    "doubleChecks": { "renaper": true }
  },
  "createdAt": "2025-01-15T10:30:00Z",
  "updatedAt": "2025-01-15T10:30:00Z"
}
When RENAPER double-check is enabled, metadata.doubleChecks.renaper is set to true at creation. After OCR KYC verification completes and the double-check runs, metadata.responseDoubleChecks.renaper is populated (see RENAPER double-check below).

Response Fields

id
string
Unique identifier for this KYC validation
entityId
string
The person entity being verified
organizationId
string
Your organization ID
sessionId
string
Identity verification session identifier
status
string
Current validation status. Possible values:
  • pending - Validation created, waiting for customer to start
  • in_progress - Customer is completing verification (filling out form)
  • in_review - Verification completed, requires manual review from compliance team
  • approved - Verification successful
  • rejected - Verification failed
  • expired - Verification session expired (e.g. after 7 days)
  • abandoned - Customer started but didn’t complete
  • cancelled - Validation manually cancelled
provider
string
KYC provider name
providerSessionUrl
string
The verification URL to share with your customer
isCurrent
boolean
Whether this is the current active validation for the entity
metadata
object
Custom metadata. When RENAPER double-check is requested, includes doubleChecks: { renaper: true }. After approval and double-check execution, may include responseDoubleChecks.renaper (see RENAPER double-check section).
createdAt
string
Timestamp when validation was created
updatedAt
string
Timestamp of last update
After approval, media keys appear inside decision. To download files (images, video), use GET /api/kyc/validations/:id/media?key=... with Authorization: Bearer. Full details: Get KYC validation media.

Example Request

const response = await fetch('https://api.gu1.ai/api/kyc/validations', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    entityId: '123e4567-e89b-12d3-a456-426614174000',
    integrationCode: 'global_gueno_validation_kyc'
  })
});

const validation = await response.json();
console.log('Verification URL:', validation.providerSessionUrl);

Error Responses

Entity Not Found (404)

{
  "error": "ENTITY_NOT_FOUND",
  "message": "Entity not found"
}

Invalid Entity Type (400)

{
  "error": "INVALID_ENTITY_TYPE",
  "message": "KYC validation is only available for person entities"
}

KYC Not Configured (400)

{
  "error": "KYC_NOT_CONFIGURED",
  "message": "KYC integration is not configured for this organization. Please contact your administrator."
}

RENAPER Double-Check (Argentina)

When doubleCheckRenaper is true and the entity is from Argentina, on each terminal status (approved, rejected, in_review) the API runs a cross-check against the official Argentine registry (RENAPER) when sufficient OCR data exists. Automatic rejection by RENAPER only applies when OCR KYC verification returned status approved.

How to Send It

  • Body: { "entityId": "...", "integrationCode": "...", "doubleCheckRenaper": true }
  • Query: POST /api/kyc/validations?doubleCheckRenaper=true with the same body (without the field). Query param overrides body if both are present.

When It Runs

  1. Validation is created with metadata.doubleChecks.renaper: true.
  2. User completes verification; OCR KYC verification returns a terminal status.
  3. API calls RENAPER (data + biometrics when applicable) and stores comparisonResults in metadata.
  4. If status is approved and the cross-check fails → validation becomes rejected.
  5. If status is in_review or rejected → results are informational; mismatch codes are added to warnings without replacing OCR verification warnings.
  6. Manual approve from in_review does not re-run RENAPER; the human decision stands on the data already shown in review.

Where the Result Is Stored

All RENAPER double-check results are stored in metadata.responseDoubleChecks.renaper. Mismatch codes are added to metadata.warnings alongside OCR KYC verification warnings. errorCode on the renaper object keeps the first failure for compatibility; UIs can list every code from comparisonResults and warnings. Shape of metadata.responseDoubleChecks.renaper:
FieldTypeDescription
verifiedbooleantrue if the double-check passed.
matchResultstring"match" | "mismatch" | "error".
verifiedAtstringISO timestamp when the check was run.
personalNumberstringTransaction number from KYC (normalized).
idTramitePrincipalstringTransaction number from RENAPER.
renaperDataobjectRaw RENAPER API response (see renaperData shape).
comparisonResultsobjectPer-field OCR vs registry comparison (when applicable).
renaperBiometricobjectABIS biometric result (validate-dni): matchResult, score, renaperData (raw response).
errorCodestringPresent when failed; see error codes below.
errorstringOptional legacy message; prefer using errorCode and translating in your UI.

renaperData shape

This is the unmodified body returned by the registry via ms-providers (POST …/provider-records/renaper/data). Gu1 forwards it as-is in metadata.responseDoubleChecks.renaper.renaperData. Field names use snake_case; all fields are optional depending on what RENAPER returns for each lookup. Example (successful double-check, matchResult: "match"):
{
  "id_tramite_principal": "987654321",
  "id_tramite_tarjeta_reimpresa": "112233445",
  "ejemplar": "A",
  "vencimiento": "2030-05-20",
  "emision": "2015-05-20",
  "apellido": "García",
  "nombres": "Juan",
  "fecha_nacimiento": "1990-01-15",
  "cuil": "20-30123456-9",
  "calle": "Av. Corrientes",
  "numero": "1234",
  "piso": "5",
  "departamento": "B",
  "codigo_postal": "1043",
  "barrio": "San Nicolás",
  "monoblock": "",
  "ciudad": "Ciudad Autónoma de Buenos Aires",
  "municipio": "Comuna 1",
  "provincia": "Buenos Aires",
  "pais": "Argentina",
  "nacionalidad": "Argentina",
  "codigo_fallecido": "",
  "mensaje_fallecido": "",
  "fecha_fallecimiento": "",
  "id_ciudadano": "30123456",
  "codigo": "",
  "mensaje": ""
}
Other common cases:
SituationTypical renaperData
Error before registry data (e.g. RENAPER_DNI_MISSING){}
Service unavailable (RENAPER_VERIFICATION_UNAVAILABLE){ "error": "…", "code": "SERVICE_UNAVAILABLE" }
Failed cross-check with partial registry payloadSubset of fields (e.g. id_tramite_principal, apellido, nombres, fecha_nacimiento)
In sandbox, the same mock values appear in KYC sandbox mock data.

comparisonResults shape

Per-field map (dni, tramite, name, ejemplar, dateOfBirth, expirationDate). Each entry may include:
FieldTypeDescription
comparedbooleantrue if comparison was attempted; false if skipped due to missing data.
passedbooleanOutcome when compared is true.
skipReasonstringocr_missing, renaper_missing, or both_missing when not compared.
ocrValuestringOCR-extracted value (when applicable).
renaperValuestringRegistry value from RENAPER (when applicable).
similarityPercentnumberFor name only: Levenshtein similarity (0–100).
thresholdnumberFor name only: applied threshold (e.g. 80).
errorCodestringField failure code (e.g. RENAPER_DNI_NOT_MATCH).
For ejemplar, the OCR value is extractedData.ejemplar (see extractedData fields). Comparison runs when both OCR and RENAPER values exist. Example — extractedData on an approved KYC validation (Argentina):
"extractedData": {
  "documentNumber": "38966181",
  "personalNumber": "00460759387",
  "taxNumber": "20389661814",
  "ejemplar": "C",
  "dateOfBirth": "1995-05-22",
  "expirationDate": "2031-10-17",
  "nationality": "ARG"
}

renaperBiometric shape

Nested under metadata.responseDoubleChecks.renaper.renaperBiometric when the org has biometric credentials and the KYC session provides a selfie. Gu1 sends one selfie to validate-dni; RENAPER compares it to the ID photo on file.
FieldTypeDescription
verifiedbooleantrue when the ABIS biometric check returns resultado.match.
matchResultstring"match" | "mismatch" | "error".
scorenumberScore from the biometric check (when present).
verifiedAtstringISO timestamp of the biometric check.
renaperDataobjectRaw validate-dni response (includes resultado.match, resultado.score, etc.).
submittedSelfieRefstringReference of the selfie sent: storage path (kyc/...) or URL (https://...).
submittedSelfieRefKindstringFormat of submittedSelfieRef: s3_key (internal path) | url (public URL).
submittedSelfiePickedFromstringSource in decision: liveness_reference_image or face_match_target_image.
errorCodestringOn failure (e.g. RENAPER_BIOMETRIC_NOT_MATCH).
skipReasonstringWhen not run (e.g. selfie unavailable).

Full responseDoubleChecks.renaper example

{
  "verified": true,
  "matchResult": "match",
  "personalNumber": "00123456789",
  "idTramitePrincipal": "987654321",
  "verifiedAt": "2026-06-05T14:30:00.000Z",
  "enforcementApplied": true,
  "renaperData": {
    "id_tramite_principal": "987654321",
    "apellido": "García",
    "nombres": "Juan",
    "fecha_nacimiento": "1990-01-15",
    "ejemplar": "A",
    "vencimiento": "2030-05-20"
  },
  "comparisonResults": {
    "dni": {
      "field": "dni",
      "compared": true,
      "passed": true,
      "ocrValue": "30123456",
      "renaperValue": "30123456"
    },
    "tramite": {
      "field": "tramite",
      "compared": true,
      "passed": true,
      "ocrValue": "00123456789",
      "renaperValue": "987654321"
    },
    "name": {
      "field": "name",
      "compared": true,
      "passed": true,
      "ocrValue": "Juan García",
      "renaperValue": "García Juan",
      "similarityPercent": 92,
      "threshold": 80
    },
    "ejemplar": {
      "field": "ejemplar",
      "compared": true,
      "passed": true,
      "ocrValue": "A",
      "renaperValue": "A"
    },
    "dateOfBirth": {
      "field": "dateOfBirth",
      "compared": true,
      "passed": true,
      "ocrValue": "1990-01-15",
      "renaperValue": "1990-01-15"
    }
  },
  "renaperBiometric": {
    "verified": true,
    "matchResult": "match",
    "score": 0.91,
    "verifiedAt": "2026-06-05T14:30:02.000Z",
    "submittedSelfieRef": "kyc/org-id/entity-id/session-id/selfie.jpg",
    "submittedSelfieRefKind": "s3_key",
    "submittedSelfiePickedFrom": "liveness_reference_image",
    "renaperData": {
      "resultado": {
        "match": true,
        "score": 0.91
      }
    }
  }
}
renaperBiometric and comparisonResults entries may be omitted depending on credentials, OCR data, or selfie availability.

Error Codes (When RENAPER Fails)

When the double-check fails, metadata.warnings contains one of the following codes (and metadata.responseDoubleChecks.renaper.errorCode is set to the same value). Your UI should translate these codes into user-facing messages.
CodeMeaning
RENAPER_DNI_MISSINGNo document number (DNI) was obtained from the KYC verification.
RENAPER_GENDER_MISSINGGender (M/F) is required to call RENAPER and was not available.
RENAPER_VERIFICATION_UNAVAILABLECould not complete the cross-check with the registry (e.g. network/API error). Retry later.
RENAPER_DNI_NOT_MATCHThe document number from the verification does not match the official registry.
RENAPER_TRAMITE_DATA_MISSINGTransaction number could not be compared (missing from KYC or RENAPER response).
RENAPER_TRAMITE_ID_NOT_MATCHThe document transaction number does not match the registry. Common when the person used an older copy of the ID, the document is expired, or it was reissued; they should verify again with their current, valid ID.
RENAPER_NAME_NOT_MATCHOCR name is below the 80% similarity threshold vs the RENAPER registry.
RENAPER_EJEMPLAR_NOT_MATCHID copy letter (ejemplar) does not match the RENAPER registry.
RENAPER_DOB_NOT_MATCHOCR date of birth does not match the RENAPER registry.
RENAPER_EXPIRY_NOT_MATCHOCR expiration date does not match the RENAPER registry.
RENAPER_BIOMETRIC_NOT_MATCHSelfie does not match the ID photo in the biometric registry.
RENAPER_BIOMETRIC_UNAVAILABLEBiometric check could not be completed (network, credentials, or service).

When RENAPER enforcement applies (automatic rejection)

OCR KYC verification statusRENAPER runs?Can RENAPER reject?
approved (direct approval)YesYes — cross-check failure → rejected
in_reviewYesNo — informational; compliance decides via manual review
rejected (OCR verification or Gu1 rules)Yes (if OCR available)No — informational; registry data kept in metadata
Manual approve from in_review (POST …/approve)No (reuses stored check)No — human decision

Why the validation may be rejected

RENAPER can change status to rejected only on the direct approved path. On in_review / rejected / manual approve, failures appear as codes in metadata.warnings and in metadata.responseDoubleChecks.renaper without auto-rejection. Use the table above and translation keys (e.g. kyc.rejectionReasonCodes.*).

Next Steps

After creating a validation:
  1. Extract the verification URL from providerSessionUrl
  2. Share the URL with your customer via email, SMS, or in-app
  3. Set up webhook endpoint to receive completion notifications
  4. Monitor validation status using the validation ID
  5. After approval, read media keys from decision and download files — see Get KYC validation media

Get KYC URL

Learn how to retrieve the URL

Webhook Integration

Configure webhook notifications