# QI Tech — Risk Solutions › Onboarding

Documentação da QI Tech em texto corrido, para colar em um LLM.
Fonte: https://docs.qitech.com.br
13 página(s).

Índice:
- Authentication (/en/documentation/caas/onboarding/authentication)
- HTTP Status Codes (/en/documentation/caas/onboarding/http_status)
- Integration (/en/documentation/caas/onboarding/integrations)
- Introduction (/en/documentation/caas/onboarding/introduction)
- Legal Person Object (/en/documentation/caas/onboarding/legal_person)
- Natural Person Object (/en/documentation/caas/onboarding/natural_person)
- Shared Objects (/en/documentation/caas/onboarding/objects)
- Retrieve a Registration (/en/documentation/caas/onboarding/query_registration)
- Integrating SDK data (face, documents and device) (/en/documentation/caas/onboarding/sdk_integration)
- Standards (/en/documentation/caas/onboarding/standards)
- Status dynamics (/en/documentation/caas/onboarding/status_dynamics)
- Update a registration (/en/documentation/caas/onboarding/update_registration)
- Webhook (/en/documentation/caas/onboarding/webhook)

---

# Authentication

URL: /en/documentation/caas/onboarding/authentication

> To authenticate a request, use the following code:

```shell
# In the shell, you only need to add the appropriate header to each request
curl "api_endpoint_here"
  -H "Authorization: EXAMPLE-OF-API-KEY"
```

> Replace the API Key 'EXAMPLE-OF-API-KEY' with your own key, which should be obtained from our support team.

We use an API Key to grant access to our API. It was likely sent to you by email. If you haven’t received your key yet, please send an email to suporte.caas@qitech.com.br .

Our API expects to receive the API Key in all requests to our server in a header like the one below:

`Authorization: EXAMPLE-OF-API-KEY`

:::info **Attention**

You must replace EXAMPLE-OF-API-KEY with your own key, which should be obtained from our support team.
:::

---

# HTTP Status Codes

URL: /en/documentation/caas/onboarding/http_status

All QI Tech APIs follow the standard HTTP status codes as defined in the [RFC 7231](https://tools.ietf.org/html/rfc7231):

HTTP Status | Meaning | Description
----------- | ------- | ---------------------------------
400 | Bad Request | The request sent contains a formatting error. In most cases, we return a message body explaining where the error is.
401 | Unauthorized | There was a problem with authentication. Check if the API Key is correct and placed in the proper header, as explained in the [Authentication](https://docs.qitech.com.br/en/documentation/caas/onboarding/authentication/index.html) section.
403 | Forbidden | The accessed endpoint is for internal use and not available for this API Key.
404 | Not Found | The requested data could not be found using the provided key. This status is also returned when an invalid endpoint is requested.
405 | Method Not Allowed | The HTTP method used is not supported by the requested endpoint.
406 | Not Acceptable | The data sent in the request body is invalid. Usually, this means the payload is not valid JSON.
409 | Conflict | The request ID corresponds to an ID that has already been processed. This status is returned in case of duplicate requests.
500 | Internal Server Error | We encountered an issue while processing the request. When this happens, our specialists are automatically notified and begin investigating immediately.
503 | Service Unavailable | You encountered an infrastructure outage, whether planned or unplanned, on our servers.

---

# Integration

URL: /en/documentation/caas/onboarding/integrations

Our mobile solutions are compatible with a wide range of technologies such as Flutter, Ionic Cordova, Capacitor, React Native, Java, Swift, and others. If you're interested in integrating with any of these, please get in touch with our support team to grant access to our private repositories.

---

# Introduction

URL: /en/documentation/caas/onboarding/introduction

Welcome to QI Tech's Onboarding API! This API provides access to Fraud Prevention, Anti-Money Laundering and KYC services in a Registration on your platform!

This API can be used to validate customer registration for:

* Opening Digital Accounts or Wallets
* Card Issuance
* Application User Validation
* Registration for Credit Approval
* Insurance Hiring Registration
* Registration Data Validation

You can use our API to access the endpoints for evaluating the following types of registration:

* **Natural Person** - used for registration validation of Natural Persons
* **Legal Person** - used for registration validation of Legal Persons

The different registration types above have specific objects and endpoints designed to cover the particularities of each entity.

## How it works

| Step | Call | What it does |
| --- | --- | --- |
| 1 | `POST /onboarding/natural_person` or `/legal_person` | Submits the registration; QI Tech runs your decision tree and returns the result in `analysis_status`. |
| 2 | `PUT /onboarding/{type}/{id}` | Reports the outcome on your platform (`client_status`). Feeds the models back. |
| 3 | `GET /onboarding/{type}/{id}` | Retrieves the current state and the event history. |

Asynchronous results arrive via [Webhook](/documentation/caas/onboarding/webhook).

:::tip Integrate in minutes
There are only **3 required fields** in each registration type. Start with the minimum payload in [Natural Person](/documentation/caas/onboarding/natural_person#minimum-payload) or [Legal Person](/documentation/caas/onboarding/legal_person#minimum-payload), with examples in Python, PHP, Node.js, Java, C# and curl.
:::

:::info Using the biometrics, OCR or Device Scan SDKs?
The keys returned by the SDKs go in different places for Natural Person and Legal Person. See [SDK data](/documentation/caas/onboarding/sdk_integration).
:::

## Issues?

We're not a company that hides behind an API! Reach out to our support team and we'll get back to you as soon as possible. Feel free to give us a call if you want a quicker answer!

### We love feedback

Even if you've already solved your issue or it is something simple (like a typo or a small organizational detail), send us an email. That way, we can keep improving our documentation and help the next person avoid the same difficulties you faced!

## Environments

We provide two environments for our clients. The base URLs for the APIs are:

* Production - `https://api.caas.qitech.app/onboarding/`
* Sandbox - `https://api.sandbox.caas.qitech.app/onboarding/`

:::danger Important Warning!
Real natural person and/or legal person data must not be used in QI Tech's Sandbox environments.  
:::

In the Sandbox environment, submitted analyses are not charged and are responded to according to the following rule based on the first digit of the document number - CPF for Natural Persons and CNPJ for Legal Persons:

Digit | Decision
------ | -------
0 | In Manual Analysis
1 | In Manual Analysis
2 | In Manual Analysis
3 | In Manual Analysis
4 | Automatically Challenged
5 | Referred for Manual Analysis - Later Rejected
6 | Referred for Manual Analysis - Later Approved
7 | Pending
8 | Automatically Rejected
9 | Automatically Approved

For CPF or CNPJ numbers starting with digits 1 or 2, please contact our support team to ensure the manual handling is done correctly.

## Only HTTPS

For security reasons, all communication with QI Tech's APIs must be conducted over HTTPS. To ensure that no HTTP calls are made, whether by oversight or any other reason, this server only makes port 443 available with TLS 1.2 communication. Requests using other protocols will be automatically rejected.

---

# Legal Person Object

URL: /en/documentation/caas/onboarding/legal_person

Legal Person Object

Submits the registration of a **legal person** for fraud and KYC analysis. QI Tech runs your decision tree against the submitted data and returns in `analysis_status` the result that **your policy** determined — see [Status dynamics](/documentation/caas/onboarding/status_dynamics).

:::tip Start with the minimum payload
There are only **3 required fields**. Go straight to [Minimum payload](#minimum-payload).
:::

:::danger Where the SDK keys go
In Legal Person, `face` and identity documents go **inside `legal_representatives[]`**, and `source.session_id` goes at the **root**. This is the main difference compared to Natural Person — see [Integrating SDK data](/documentation/caas/onboarding/sdk_integration).
:::

---

## Minimum payload

```json title="Minimum payload — 3 required fields"
{
  "id": "87654321",
  "registration_date": "2026-08-07T11:37:15-03:00",
  "document_number": "11.111.111/0001-11"
}
```

Response:

```json
{
  "id": "87654321",
  "analysis_status": "automatically_approved",
  "reason": "rule_decision_enum"
}
```

:::info Additional fields
The more data you send, the more validations the rules engine is able to perform.
:::

---

## Submit a registration

ENDPOINT /onboarding/legal_person
METHOD POST

### Query parameters

analyze
boolean
optional — defaults to true
With true , the registration is analyzed. With false , it is only recorded (with no charge) and the response returns not_analysed .

**Python**

```python
import requests

BASE_URL = "https://api.sandbox.caas.qitech.app"
API_KEY = "YOUR_API_KEY"

payload = {
    "id": "87654321",
    "registration_date": "2026-08-07T11:37:15-03:00",
    "document_number": "11.111.111/0001-11"
}

response = requests.post(
    f"{BASE_URL}/onboarding/legal_person",
    params={"analyze": "true"},
    json=payload,
    headers={"Authorization": API_KEY},
    timeout=30,
)

response.raise_for_status()
print(response.json()["analysis_status"])
```

**PHP**

```php
<?php

$baseUrl = 'https://api.sandbox.caas.qitech.app';
$apiKey  = 'YOUR_API_KEY';

$payload = [
    'id'                => '87654321',
    'registration_date' => '2026-08-07T11:37:15-03:00',
    'document_number'   => '11.111.111/0001-11'
];

$ch = curl_init($baseUrl . '/onboarding/legal_person?analyze=true');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Authorization: ' . $apiKey
    ],
    CURLOPT_POSTFIELDS => json_encode($payload)
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("Onboarding returned HTTP {$status}: {$body}");
}

echo json_decode($body, true)['analysis_status'];
```

**Node.js**

```javascript
const BASE_URL = "https://api.sandbox.caas.qitech.app";
const API_KEY = "YOUR_API_KEY";

const payload = {
  id: "87654321",
  registration_date: "2026-08-07T11:37:15-03:00",
  document_number: "11.111.111/0001-11"
};

async function createLegalPerson() {
  const response = await fetch(
    `${BASE_URL}/onboarding/legal_person?analyze=true`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY
      },
      body: JSON.stringify(payload)
    },
  );

  if (!response.ok) {
    throw new Error(`Onboarding returned HTTP ${response.status}`);
  }

  const result = await response.json();
  console.log(result.analysis_status);
  return result;
}

createLegalPerson();
```

**Java**

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class CreateLegalPerson {

    private static final String BASE_URL = "https://api.sandbox.caas.qitech.app";
    private static final String API_KEY = "YOUR_API_KEY";

    public static void main(String[] args) throws Exception {
        String payload = """
            {
              "id": "87654321",
              "registration_date": "2026-08-07T11:37:15-03:00",
              "document_number": "11.111.111/0001-11"
            }
            """;

        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL + "/onboarding/legal_person?analyze=true"))
                .header("Content-Type", "application/json")
                .header("Authorization", API_KEY)
                .timeout(Duration.ofSeconds(30))
                .POST(HttpRequest.BodyPublishers.ofString(payload))
                .build();

        HttpResponse<String> response =
                client.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 200) {
            throw new IllegalStateException(
                    "Onboarding returned HTTP " + response.statusCode() + ": " + response.body());
        }

        System.out.println(response.body());
    }
}
```

**C#**

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class CreateLegalPerson
{
    private const string BaseUrl = "https://api.sandbox.caas.qitech.app";
    private const string ApiKey = "YOUR_API_KEY";

    public static async Task Main()
    {
        var payload = new
        {
            id = "87654321",
            registration_date = "2026-08-07T11:37:15-03:00",
            document_number = "11.111.111/0001-11"
        };

        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
        client.DefaultRequestHeaders.Add("Authorization", ApiKey);

        var content = new StringContent(
            JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

        var response = await client.PostAsync(
            $"{BaseUrl}/onboarding/legal_person?analyze=true", content);

        var body = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"Onboarding returned HTTP {(int)response.StatusCode}: {body}");
        }

        Console.WriteLine(body);
    }
}
```

**curl**

```bash
curl -X POST \
  'https://api.sandbox.caas.qitech.app/onboarding/legal_person?analyze=true' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: YOUR_API_KEY' \
  -d '{
    "id": "87654321",
    "registration_date": "2026-08-07T11:37:15-03:00",
    "document_number": "11.111.111/0001-11"
  }'
```

---

## Object fields

### Required

id
string
required
Identifier of the analysis. 1 to 50 characters. Unique per request — a repeated value returns HTTP 409.

registration_date
datetime
required
Date and time of the registration, with time zone. Same format as Natural Person.

document_number
string
required
CNPJ with punctuation , in the XX.XXX.XXX/XXXX-XX format. 18 characters.

### Company data

registration_id
string
optional
Identifier of the registration in your system. Takes the value of id when omitted.

legal_name
string
optional
Registered corporate name.

trading_name
string
optional
Trade name.

foundation_date
date
optional
Date of incorporation in the YYYY-MM-DD format.

website
string
optional
Company website. Up to 10,000 characters.

activity
string
optional
Description of the economic activity.

activity_code
string
optional
CNAE in the XX.XX-X-XX format. Exactly 10 characters.

merchant_category_code
enum
optional
4-digit MCC as defined by ISO 18245. Only codes from the official list are accepted.

tier
string
optional
Company size. Up to 10 characters. E.g.: mei , epp , me .

annual_revenues
integer
optional
Annual revenue in cents .

monthly_revenues
integer
optional
Monthly revenue in cents .

### Contact and location

emails
array
optional
List of Email objects. Each item requires email .

phones
array
optional
List of Phone objects. Each item requires international_dial_code , area_code and number .

address
object
optional
Address object. If sent, it requires postal_code .

documents
object
optional
Company documents. Only accepts ie , company_statute and proof_of_address — see the warning below, SDK data and Shared objects .

source
object
optional
Origin of the request. It is here at the root that the Device Scan session_id must be sent — see SDK data .

### Ownership structure

legal_representatives
array
optional
List of LegalRepresentative objects. It is inside this array that face and identity documents (RG, CNH) go — see SDK data .

partners
array
optional
List of Partner objects with the company partners.

final_beneficiaries
array
optional
List of FinalBeneficiary objects with the ultimate beneficiaries.

### Classification and extras

analysis_type
string
optional
Type of analysis, when your account has more than one flow configured.

client_category
string
optional
Customer category on your platform.

partnership_key
string
optional
Identifier of the associated partnership.

related_account_type
string
optional
Type of related account.

custom_data
object
optional
Custom fields. Requires a schema previously registered by QI Tech.

```json title="Full payload"
{
  "id": "87654321",
  "registration_id": "cad-pj-1234",
  "registration_date": "2026-08-07T11:37:15-03:00",
  "client_category": "Premium Account",
  "legal_name": "Empresa Exemplo LTDA",
  "trading_name": "Barbearia do John",
  "document_number": "11.111.111/0001-11",
  "foundation_date": "1992-09-15",
  "website": "www.exemplo.com.br",
  "activity": "Barber Shops",
  "activity_code": "96.02-5-01",
  "merchant_category_code": "0742",
  "tier": "epp",
  "annual_revenues": 180000000,
  "monthly_revenues": 15000000,
  "emails": [{ "email": "contato@exemplo.com.br" }],
  "address": {
    "street": "Rua do Teste",
    "number": "111",
    "neighborhood": "Centro",
    "city": "São Paulo",
    "uf": "SP",
    "postal_code": "04570-140",
    "country": "BRA"
  },
  "phones": [
    {
      "international_dial_code": "55",
      "area_code": "11",
      "number": "999999999",
      "type": "commercial"
    }
  ],
  "source": {
    "channel": "web",
    "platform": "web",
    "ip": "255.201.26.1",
    "session_id": "54b8e3cf-15de-41e5-9305-0ecf059d6e2a"
  },
  "documents": {
    "company_statute": {
      "ocr_key": "d8fa2fb0-5199-47c3-daae-bd8ef03f4ea9"
    },
    "proof_of_address": {
      "document_analysis_id": "e9ab3ac1-62aa-48d4-ebbf-ce9fa14a5fb0"
    }
  },
  "legal_representatives": [
    {
      "id": "rep-001",
      "name": "Maria Sample",
      "document_number": "222.222.222-22",
      "birthdate": "1985-03-22",
      "mother_name": "Ana Sample",
      "pleaded_pep": false,
      "face": {
        "type": "zaig_sdk",
        "registration_key": "46f38cf4-07b2-4de6-93e9-64b51a68378a"
      },
      "documents": {
        "cnh": {
          "register_number": "05163811694",
          "issuer_state": "SP",
          "ocr_key": "c7ef1eaf-4088-46b2-c9fd-ac7de92e3d98"
        }
      }
    }
  ],
  "partners": [
    {
      "name": "Maria Sample",
      "document_number": "222.222.222-22"
    }
  ],
  "final_beneficiaries": [
    {
      "name": "Maria Sample",
      "document_number": "222.222.222-22"
    }
  ]
}
```

:::warning Fields not defined in the schema are rejected
The schema uses `additionalProperties: false`. Any field outside the ones listed returns **HTTP 400**.
:::

:::danger RG and CNH do not go at the root
At the root of Legal Person, `documents` accepts **only** `ie`, `company_statute` and `proof_of_address`. Identity documents belong to the legal representative, inside `legal_representatives[].documents`. Sending them at the root returns **HTTP 400**.
:::

---

## Field formats

### `document_number` — CNPJ

The `XX.XXX.XXX/XXXX-XX` format, with punctuation, 18 characters. Digits only are rejected.

### `activity_code` — CNAE

The `XX.XX-X-XX` format, exactly 10 characters. E.g.: `96.02-5-01`.

### `registration_date`

Same format as Natural Person: ISO 8601 with time zone, offset ending in `:00`/`:30` or the `Z` suffix.

### Monetary values

`annual_revenues` and `monthly_revenues` are integers in **cents**. R$ 150,000.00 → `15000000`.

---

## Representatives, partners and beneficiaries

All three arrays accept the same identification fields as a natural person (`name`, `document_number`, `birthdate`, `gender`, `nationality`, `mother_name`, `occupation`, `emails`, `phones`, `address`, `pleaded_pep`).

**Only `legal_representatives[]` accepts `face` and `documents`** — that is where the representative's biometrics and OCR keys go. See [Integrating SDK data](/documentation/caas/onboarding/sdk_integration).

---

## Testing in Sandbox

The decision is deterministic, defined by the **first digit of the CNPJ**:

| First digit | Result |
| --- | --- |
| `9` | `automatically_approved` |
| `8` | `automatically_reproved` |
| `7` | `pending` |
| `6` | Manual analysis, with later approval |
| `5` | Manual analysis, with later rejection |
| `4` | `automatically_challenged` |
| `0` to `3` | `in_manual_analysis` |

:::danger Important warning
Do not use real legal person data in the Sandbox environment.
:::

---

## Errors

| Status | Situation | How to resolve |
| --- | --- | --- |
| 400 | Missing required field, invalid format or field not defined in the schema. | Check the `description` in the response. |
| 400 | `rg`/`cnh` at the root of `documents`. | Move them into `legal_representatives[]`. |
| 400 | `custom_data` without a registered schema. | Ask support to register it. |
| 401 | Missing `Authorization` header or deactivated key. | Check the key. |
| 403 | Invalid API Key. | Confirm it with support. |
| 409 | `id` already used. | Generate a unique `id`. |
| 500 | Internal error. | Our team is notified automatically. |

Full list in [HTTP Status](/documentation/caas/onboarding/http_status).

---

## Integration checklist

- [ ] `POST /onboarding/legal_person` with the minimum payload returning `200` in Sandbox.
- [ ] CNPJ with punctuation and CNAE in the `XX.XX-X-XX` format.
- [ ] `source.session_id` filled in **at the root** (without it, the device does not show up on the Legal Person dashboard).
- [ ] `face` and identity documents inside `legal_representatives[]`.
- [ ] At the root of `documents`, only `ie`, `company_statute`, `proof_of_address`.
- [ ] Monetary values in cents.
- [ ] [Webhook](/documentation/caas/onboarding/webhook) configured for the asynchronous result.

---

# Natural Person Object

URL: /en/documentation/caas/onboarding/natural_person

Natural Person Object

Submits the registration of a **natural person** for fraud and KYC analysis. QI Tech runs your decision tree against the submitted data and returns in `analysis_status` the result that **your policy** determined — see [Status dynamics](/documentation/caas/onboarding/status_dynamics).

:::tip Start with the minimum payload
There are only **3 required fields**. Go straight to [Minimum payload](#minimum-payload) and then add whatever makes sense for your use case.
:::

:::caution Send final data
The data you submit must be definitive. CPF, name and date of birth should not change after this call — this ensures consistency of the anti-fraud database and a realistic risk assessment.
:::

:::info Using the biometrics, OCR or Device Scan SDKs?
The keys returned by the SDKs go into the `face`, `documents` and `source` blocks of this payload. Where each one goes — and what changes compared to Legal Person — is covered in **[SDK data (face, documents and device)](/documentation/caas/onboarding/sdk_integration)**.
:::

---

## Minimum payload

This is the smallest body accepted by `POST /onboarding/natural_person`.

```json title="Minimum payload — 3 required fields"
{
  "id": "12345678",
  "registration_date": "2026-08-07T11:37:15-03:00",
  "document_number": "111.111.111-11"
}
```

Response:

```json
{
  "id": "12345678",
  "analysis_status": "automatically_approved",
  "reason": "rule_decision_enum"
}
```

:::info Additional fields
The more data you send, the more validations the rules engine is able to perform.
:::

---

## Submit a registration

ENDPOINT /onboarding/natural_person
METHOD POST

### Query parameters

analyze
boolean
optional — defaults to true
With true , your decision tree is executed and the response returns the result. With false , the registration is only recorded (with no charge) and becomes part of the history used in future analyses — the response returns not_analysed .

### Request examples

**Python**

```python
import requests

BASE_URL = "https://api.sandbox.caas.qitech.app"
API_KEY = "YOUR_API_KEY"

payload = {
    "id": "12345678",
    "registration_date": "2026-08-07T11:37:15-03:00",
    "document_number": "111.111.111-11"
}

response = requests.post(
    f"{BASE_URL}/onboarding/natural_person",
    params={"analyze": "true"},
    json=payload,
    headers={"Authorization": API_KEY},
    timeout=30,
)

response.raise_for_status()
result = response.json()
print(result["analysis_status"])  # automatically_approved
```

**PHP**

```php
<?php

$baseUrl = 'https://api.sandbox.caas.qitech.app';
$apiKey  = 'YOUR_API_KEY';

$payload = [
    'id'                => '12345678',
    'registration_date' => '2026-08-07T11:37:15-03:00',
    'document_number'   => '111.111.111-11'
];

$ch = curl_init($baseUrl . '/onboarding/natural_person?analyze=true');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Authorization: ' . $apiKey
    ],
    CURLOPT_POSTFIELDS => json_encode($payload)
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("Onboarding returned HTTP {$status}: {$body}");
}

$result = json_decode($body, true);
echo $result['analysis_status'];
```

**Node.js**

```javascript
const BASE_URL = "https://api.sandbox.caas.qitech.app";
const API_KEY = "YOUR_API_KEY";

const payload = {
  id: "12345678",
  registration_date: "2026-08-07T11:37:15-03:00",
  document_number: "111.111.111-11"
};

async function createRegistration() {
  const response = await fetch(
    `${BASE_URL}/onboarding/natural_person?analyze=true`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY
      },
      body: JSON.stringify(payload)
    },
  );

  if (!response.ok) {
    throw new Error(`Onboarding returned HTTP ${response.status}`);
  }

  const result = await response.json();
  console.log(result.analysis_status);
  return result;
}

createRegistration();
```

**Java**

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class CreateNaturalPerson {

    private static final String BASE_URL = "https://api.sandbox.caas.qitech.app";
    private static final String API_KEY = "YOUR_API_KEY";

    public static void main(String[] args) throws Exception {
        String payload = """
            {
              "id": "12345678",
              "registration_date": "2026-08-07T11:37:15-03:00",
              "document_number": "111.111.111-11"
            }
            """;

        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL + "/onboarding/natural_person?analyze=true"))
                .header("Content-Type", "application/json")
                .header("Authorization", API_KEY)
                .timeout(Duration.ofSeconds(30))
                .POST(HttpRequest.BodyPublishers.ofString(payload))
                .build();

        HttpResponse<String> response =
                client.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 200) {
            throw new IllegalStateException(
                    "Onboarding returned HTTP " + response.statusCode() + ": " + response.body());
        }

        System.out.println(response.body());
    }
}
```

**C#**

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class CreateNaturalPerson
{
    private const string BaseUrl = "https://api.sandbox.caas.qitech.app";
    private const string ApiKey = "YOUR_API_KEY";

    public static async Task Main()
    {
        var payload = new
        {
            id = "12345678",
            registration_date = "2026-08-07T11:37:15-03:00",
            document_number = "111.111.111-11"
        };

        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
        client.DefaultRequestHeaders.Add("Authorization", ApiKey);

        var content = new StringContent(
            JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

        var response = await client.PostAsync(
            $"{BaseUrl}/onboarding/natural_person?analyze=true", content);

        var body = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"Onboarding returned HTTP {(int)response.StatusCode}: {body}");
        }

        Console.WriteLine(body);
    }
}
```

**curl**

```bash
curl -X POST \
  'https://api.sandbox.caas.qitech.app/onboarding/natural_person?analyze=true' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: YOUR_API_KEY' \
  -d '{
    "id": "12345678",
    "registration_date": "2026-08-07T11:37:15-03:00",
    "document_number": "111.111.111-11"
  }'
```

### Response

id
string
The same id sent in the request.

analysis_status
enum
Result of running your decision tree. See Status dynamics .

reason
string
Reason for the decision, when available.

```json
{
  "id": "12345678",
  "analysis_status": "automatically_approved",
  "reason": "rule_decision_enum"
}
```

:::info Asynchronous response
When the analysis takes longer than expected, the response comes back as `in_queue` or `pending` and the final result arrives via [Webhook](/documentation/caas/onboarding/webhook). Treat these two statuses as "waiting" — not as a rejection.
:::

---

## Object fields

### Required

id
string
required
Identifier of the analysis in your system. 1 to 50 characters. Must be unique per request — a repeated id returns HTTP 409.

registration_date
datetime
required
Date and time of the registration, with time zone . See the accepted format .

document_number
string
required
CPF with punctuation , in the XXX.XXX.XXX-XX format. Exactly 14 characters.

### Identification

registration_id
string
optional
Identifier of the registration in your system. Use the same value across different analyses of the same registration to group them. When omitted, it takes the value of id .

name
string
optional
Full name. 1 to 500 characters.

birthdate
date
optional
Date of birth in the YYYY-MM-DD format.

gender
enum
optional
male or female .

nationality
string
optional
Country in ISO 3166-1 alpha-3, 3 uppercase letters. E.g.: BRA .

mother_name
string
optional
Mother's full name. 1 to 500 characters. A relevant signal for bureau validation.

father_name
string
optional
Father's full name. 1 to 500 characters.

### Financial profile

monthly_income
integer
optional
Gross monthly income in cents . R$ 5,000.00 → 500000 .

declared_assets
integer
optional
Declared assets in cents .

occupation
string
optional
Occupation. 1 to 100 characters.

is_us_person
boolean
optional
Indicates whether the person has tax obligations in the US (relevant for FATCA).

pleaded_pep
boolean
optional
Indicates whether the person declared themselves a politically exposed person (PEP).

### Contact and location

emails
array
optional
List of Email objects. Within each item, only email is required.

phones
array
optional
List of Phone objects. If sent, each item requires international_dial_code , area_code and number .

address
object
optional
Address object. If sent, only postal_code is required within it.

documents
object
optional
Identification documents (RG, CNH, passport and others). OCR keys go here — see SDK data and Shared objects .

face
object
optional
Facial validation data. The key returned by the biometrics SDK goes here — see SDK data .

source
object
optional
Origin of the request (channel, platform, IP, session). This is where the Device Scan session_id goes — see SDK data .

### Classification and extras

analysis_type
string
optional
Type of analysis to apply, when your account has more than one flow configured. Align with support before using it.

client_category
string
optional
Customer category on your platform or loyalty program. 1 to 100 characters.

partnership_key
string
optional
Identifier of the partnership associated with the registration. 1 to 500 characters.

related_account_type
string
optional
Type of account related to the registration. 1 to 50 characters.

vehicle_plate
string
optional
Vehicle license plate associated with the registration. 1 to 50 characters.

custom_data
object
optional
Custom fields for your account. Requires a schema previously registered by QI Tech — see the warning below.

```json title="Full payload"
{
  "id": "12345678",
  "registration_id": "cad-98765",
  "registration_date": "2026-08-07T11:37:15-03:00",
  "analysis_type": "default",
  "client_category": "Premium User",
  "name": "John Sample",
  "document_number": "111.111.111-11",
  "birthdate": "1992-09-15",
  "gender": "male",
  "nationality": "BRA",
  "mother_name": "Maria Sample",
  "father_name": "John Sample",
  "monthly_income": 500000,
  "declared_assets": 7500000,
  "occupation": "Teacher",
  "is_us_person": false,
  "pleaded_pep": false,
  "emails": [
    {
      "email": "johnsample@test.com"
    }
  ],
  "documents": {
    "rg": {
      "number": "4.366.477-8",
      "issuer": "II",
      "issuer_state": "PR",
      "issuance_date": "2002-01-12"
    },
    "cnh": {
      "register_number": "05163811694",
      "issuer_state": "PR",
      "first_issuance_date": "2011-03-21",
      "issuance_date": "2016-06-29",
      "expiration_date": "2031-06-25",
      "category": "AB"
    }
  },
  "address": {
    "street": "Rua do Teste",
    "number": "111",
    "neighborhood": "Bairro do Exemplo",
    "city": "Aparecida de Goiânia",
    "uf": "GO",
    "complement": "Térreo",
    "postal_code": "00000-000",
    "country": "BRA"
  },
  "phones": [
    {
      "international_dial_code": "55",
      "area_code": "11",
      "number": "999999999",
      "type": "mobile"
    }
  ],
  "source": {
    "channel": "app",
    "platform": "android",
    "ip": "255.201.26.1",
    "session_id": "54b8e3cf-15de-41e5-9305-0ecf059d6e2a",
    "os_version": "14"
  },
  "face": {
    "type": "zaig_sdk",
    "registration_key": "46f38cf4-07b2-4de6-93e9-64b51a68378a"
  }
}
```

:::warning Fields not defined in the schema are rejected
The schema uses `additionalProperties: false`. Any field outside the ones listed above makes the request return **HTTP 400**, even if the rest of the payload is correct.
:::

:::caution `custom_data` requires its own schema
`custom_data` is validated against a schema specific to your company, registered by QI Tech. If you send this field without having the schema registered, the response is **HTTP 400** with the message *"Custom data not available for you account"*. Talk to [support](mailto:suporte.caas@qitech.com.br) before using it.
:::

---

## Field formats

### `registration_date` format

ISO 8601 format **with a mandatory time zone**. The validator accepts offsets ending in `:00` or `:30`, or the `Z` suffix:

```text
2026-08-07T11:37:15-03:00          ✅
2026-08-07T11:37:15.123456-03:00   ✅  fraction of 1 to 6 digits
2026-08-07T14:37:15Z               ✅  UTC
2026-08-07T11:37:15                ❌  no time zone
2026-08-07T11:37:15-03:15          ❌  offset not allowed
```

### `document_number` — CPF

Must be sent **with punctuation**: `XXX.XXX.XXX-XX`, exactly 14 characters. Sending digits only (`11111111111`) returns HTTP 400.

### `postal_code` — CEP

Inside `address`, the postal code requires the `XXXXX-XXX` format (with a hyphen). `00000000` is rejected.

### Monetary values

`monthly_income` and `declared_assets` are integers in **cents of Brazilian reais**. Multiply by 100: R$ 5,000.00 → `500000`.

---

## Enumerators

### `gender`

| Value | Meaning |
| --- | --- |
| `male` | Male |
| `female` | Female |

### `phones[].type`

| Value | Meaning |
| --- | --- |
| `mobile` | Mobile |
| `residential` | Residential |
| `commercial` | Commercial |

| `visit` | Confirmed by an in-person visit |
| `zaig_sdk` | Confirmed by QI Tech's SDK |
| `zaig_ocr` | Confirmed by OCR of a proof document |

### `face.type`

| Value | Meaning |
| --- | --- |
| `zaig_sdk` | Capture via QI Tech's SDK (use `registration_key`) |
| `base_64` | Image sent directly in the `image` field |

For `analysis_status`, `client_status` and `risk_level`, see [Status dynamics](/documentation/caas/onboarding/status_dynamics).

---

## Testing in Sandbox

In Sandbox the decision is deterministic, defined by the **first digit of the CPF**:

| First digit | Result |
| --- | --- |
| `9` | `automatically_approved` |
| `8` | `automatically_reproved` |
| `7` | `pending` |
| `6` | Manual analysis, with later approval |
| `5` | Manual analysis, with later rejection |
| `4` | `automatically_challenged` |
| `0` to `3` | `in_manual_analysis` |

:::danger Important warning
Do not use real natural person data in the Sandbox environment.
:::

---

## Errors

| Status | Situation | How to resolve |
| --- | --- | --- |
| 400 | Missing required field, invalid format, enum outside the list or field not defined in the schema. | Check the `description` in the response, which points to the field. |
| 400 | `custom_data` without a registered schema. | Ask support to register the schema. |
| 401 | Missing `Authorization` header or deactivated API Key. | Check the key. |
| 403 | Invalid API Key. | Confirm the key with support. |
| 409 | `id` already used. | Generate a unique `id` per request. |
| 500 | Internal error. | Our specialists are notified automatically. |

Full list in [HTTP Status](/documentation/caas/onboarding/http_status).

---

## Integration checklist

- [ ] `POST /onboarding/natural_person` with the minimum payload returning `200` in Sandbox.
- [ ] `id` unique per request (test the `409` by resending the same `id`).
- [ ] `registration_id` stable so analyses of the same registration are grouped.
- [ ] CPF with punctuation and postal code with a hyphen.
- [ ] Monetary values in cents.
- [ ] `in_queue` and `pending` treated as "waiting", not as a rejection.
- [ ] [Webhook](/documentation/caas/onboarding/webhook) configured to receive the asynchronous result.

---

# Shared Objects

URL: /en/documentation/caas/onboarding/objects

Many data structures are shared across different APIs. Below, you'll find simplified definitions for these shared objects.

## *email* Object

Request Body

```json
{
  "email": "johnsample@test.com"
}
```

The *email* object is used to represent emails across the entire API, as well as whether any validation method was used. They are represented as follows:

name | type | constraints | description
---- | :----: | :----: | ----------
email | string | 1–100 characters | Registered email address. *(required)*

## *cnh* Object

Request Body

```json
{
  "register_number": "05163811694",
  "issuer_state": "PR",
  "first_issuance_date":"2011-03-21",
  "issuance_date":"2016-06-29",
  "expiration_date":"2021-06-25",
  "category": "AB",
  "ocr_key":"a5cf9c8f-2f66-4490-a7db-8a5bc70c1b76"
}
```

The *cnh* object is used to represent driver’s licenses (CNHs) throughout the API, including information on whether any validation method was used. They are represented as follows:

name | type | description
---- | :----: | ----------
register_number | string | Registration number of the registered CNH.
issuer_state | enum | Enumerator for the state where the CNH was issued.
first_issuance_date | date | Date of first issuance.
issuance_date | date | Date of issuance.
expiration_date | date | Expiration date.
category | enum | CNH category in uppercase letters.
ocr_key | guid | ID returned by QI Tech’s document validation API.

## *rg* Object

Request Body

```json
{
  "number": "4.366.477-8",
  "issuer": "II",
  "issuer_state": "PR",
  "issuance_date":"2002-01-12",
  "ocr_front_key":"a5cf9c8f-2f66-4490-a7db-8a5bc70c1b76",
  "ocr_back_key":"a5cf9c8f-2f66-4490-a7db-8a5bc70c1b76"
}
```

The *rg* object is used to represent identity documents (RGs) throughout the API, including information on whether any validation method was used. They are represented as follows:

name | type | description
---- | :----: | ----------
number | string | Registered document number, including formatting (dots, hyphens, slashes, etc.).
issuer | string | Issuing authority of the document (abbreviation, e.g.: II, SESP...).
issuer_state | enum | State (UF) where the document was issued.
issuance_date | date | Date the document was issued.
ocr_key | guid | ID returned by QI Tech’s document validation API.

## *ie* Object

Request Body

```json
{
  "number": "388.108.598.269",
  "issuer": "JUCESP",
  "issuer_state": "SP",
  "issuance_date":"2002-01-12",
  "ocr_key": "c64627db-1ba4-48b6-979d-06222a25d5e9"
}
```

The *ie* object is used to represent State Registration documents within the *documents* object in the *legal_person* endpoint, including information on whether any validation method was used. It is represented as follows:

name | type | description
---- | :----: | ----------
number | string | Registered document number, including formatting (dots, hyphens, slashes, etc.).
issuer | string | Issuing authority of the document (abbreviation, e.g.: JUCESP, JUCEGO...).
issuer_state | enum | State (UF) where the document was issued.
issuance_date | date | Date the document was issued.
ocr_key | guid | ID returned by QI Tech’s document validation API.

## *company_statute* Object 

Request Body

```json
{
  "ocr_key": "60ed79c4-5aba-4cc7-aebb-5de5f92b7d0d"
}
```

The *company_statute* object is used to represent company formation documents, such as an Articles of Incorporation, within the *documents* object in the *legal_person* endpoint. It is represented as follows:

name | type | description
---- | :----: | ----------
ocr_key | guid | ID returned by QI Tech’s OCR API after sending the image or PDF of a company formation document.

## *letter_attorney* Object 

Request Body

```json
{
  "ocr_key": "13571175-b1d9-4507-82e0-d266516fc5ae"
}
```

The *letter_attorney* object is used to represent powers of attorney that grant authority to legal representatives within the *documents* object in the *legal_person* endpoint. It is represented as follows:

name | type | description
---- | :----: | ----------
ocr_key | guid | ID returned by QI Tech’s OCR API after sending the image or PDF of a power of attorney document.

## *address* Object

Request Body

```json
{
  "street": "Rua do Teste",
  "number": "111",
  "neighborhood": "Bairro do Exemplo",
  "city": "Aparecida de Goiânia",
  "uf": "GO",
  "complement": "Térreo",
  "postal_code": "00000-000",
  "country": "BRA",
  "ocr_key": "265b1b74-4b93-41dc-ac78-e1c37467225d"
}
```

The *address* object is used to represent addresses throughout the API. Addresses located in Brazilian territory are represented as follows:

name | type | constraints | description
---- | :----: | :----: | ----------
street | string | 1–100 characters | Street name, including thoroughfare, avoiding abbreviations whenever possible.
number | string | 1–50 characters | Property number, including letters if applicable.
neighborhood | string | 1–100 characters | Neighborhood, without abbreviations. **e.g.: Santa Felicidade**
city | string | 1–100 characters | Full city name, without abbreviations.
uf | enum | 2-letter state code | The Brazilian federative unit. Accepts the 27 states plus `EX` (abroad), in uppercase or lowercase. **e.g.: SP, GO, MG, EX**
complement | string | 1–500 characters | Any additional information to help locate the property. **e.g.: Apartment 101, Suite 12**
postal_code | string | Format `XXXXX-XXX` | Brazilian postal code (CEP) with hyphen, exactly 9 characters. Example: `01310-100` *(required)*
country | string | 3 uppercase letters | ISO 3166-1 alpha-3 country code. Example: `BRA`
ocr_key | guid | UUID (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`) | ID returned by QI Tech’s OCR API or SDK after submitting the image of the proof of residence.

For addresses where the country is not Brazil (`BRA`), the `postal_code` and `uf` fields may be filled in freely.

## *phone* Object 

Request Body

```json
{
  "international_dial_code": "1",
  "area_code": "11",
  "number": "999999999",
  "type": "mobile"
}
```

A *phone* object represents a phone number, either domestic or international, and its classification. The fields are:

name | type | constraints | description
---- | :----: | :----: | ----------
international_dial_code | string | 1–7 characters, digits only | International dialing code, without zero or `+`. Example: `55` for Brazil *(required)*
area_code | string | 1–10 characters, digits only | Area code, without zero. Example: `11` *(required)*
number | string | 1–20 characters | Phone number, without hyphen *(required)*
type | enum | `residential`, `commercial` or `mobile` | Type of phone number.

## *source* Object 

Request Body

```json
  {
    "channel": "app",
    "platform": "android",
    "ip":"211.7.142.62",
    "session_id": "733adf2c-a994-4113-aa59-beb646091fea"
  }
```

A *source* object represents the set of information about the platform used by the client during their registration. The fields are:

name | type | description
---- | :----: | ----------
channel | string | Sales channel/ client registration
platform | string | Platform used by the client to complete their registration
ip | string | IP address collected from the device at the time of registration
session_id | string | Unique session identifier, used to match the device scan with the corresponding registration

## *face* Object 

Request Body

```json
  {
    "type":"zaig_face_sdk",
    "registration_key":"46f38cf4-07b2-4de6-93e9-64b51a68378a"
  }
```

A *face* object represents a facial recognition validation performed through QI Tech’s APIs or SDKs to verify the authenticity of the client prior to registration submission. The fields are:

name | type | description
---- | :----: | -----------
registration_key | guid | Identifier returned by QI Tech’s API or SDK to represent the registration.

## *partner* Object

Request Body

```json
  {
    "name": "John Partner",
    "document_number": "111.111.111-11",
    "birthdate": "1992-09-15",
    "gender": "male",
    "nationality": "BRA",
    "mother_name": "Maria Partner's Mother",
    "occupation": "Teacher",
    "emails":[
      {
        "email": "johnsample@test.com"
      }
    ],
    "documents": {
      "rg": {
        "number": "4.366.477-8",
        "issuer": "II",
        "issuer_state": "PR",
        "issuance_date":"2002-01-12",
        "ocr_front_key":"a5cf9c8f-2f66-4490-a7db-8a5bc70c1b76",
        "ocr_back_key":"a5cf9c8f-2f66-4490-a7db-8a5bc70c1b76"
      },
      "cnh": {
        "register_number": "05163811694",
        "issuer_state": "PR",
        "first_issuance_date":"2011-03-21",
        "issuance_date":"2016-06-29",
        "expiration_date":"2021-06-25",
        "category": "AB",
        "ocr_key":"a5cf9c8f-2f66-4490-a7db-8a5bc70c1b76"
      }
    },
    "address": {
      "street": "Rua do Teste",
      "number": "111",
      "neighborhood": "Bairro do Exemplo",
      "city": "Aparecida de Goiânia",
      "uf": "GO",
      "complement": "Térreo",
      "postal_code": "00000-000",
      "country": "BRA"
    },
    "phones": [
      {
        "international_dial_code": "1",
        "area_code": "11",
        "number": "999999999",
        "type": "mobile"
      }
    ],
    "source": {
      "channel": "app",
      "platform": "android",
      "ip":"255.321.321.1",
      "session_id": "54b8e3cf-15de-41e5-9305-0ecf059d6e2a"
    },
    "face":
    {
      "type":"zaig_sdk",
      "registration_key":"46f38cf4-07b2-4de6-93e9-64b51a68378a"
    }
  }
```

A *partner* object represents the data of a company's partner being registered, as well as information related to any validations the partner underwent during the registration process. The fields are:

name | type | description
:----: | :----: | -----------
name | string | Full name of the partner being registered
document_number | string | CPF of the partner, with periods and hyphens, following the standard format *(required)*
birthdate | date | Partner’s birthdate in the expected format
gender | enum | Partner’s gender: 'male' or 'female'
nationality | string | Nationality of the partner, in ISO 3166-1 alpha-3
mother_name | string | Full name of the partner’s mother
occupation | string | Profession of the partner being registered
emails | Email | List of Email objects describing the partner’s email addresses
documents | Document | Document object representing any documents submitted during the partner’s registration
address | Address | Address object representing the partner’s residential address
phones | List of Phone | List of phone objects with the partner’s phone numbers
source | Source | Source object describing the platform used for submitting the registration
face | Face | Face object containing information on the facial validation

## *legal_representative* Object

Request Body

```json
  {
    "name": "Frederic Attorney",
    "document_number": "111.111.111-11",
    "birthdate": "1987-06-12",
    "gender": "male",
    "nationality": "BRA",
    "mother_name": "Jackie Attorney Mother",
    "occupation": "Accountant",
    "emails":[
      {
        "email": "frederic@attorney.com"
      }
    ],
    "documents": {
      "letter_of_attorney": {
        "ocr_key": "6972894d-d2ef-4b5f-b54f-10f178bf3e5d"
      },
      "cnh": {
        "register_number": "05163811694",
        "issuer_state": "PR",
        "first_issuance_date":"2011-03-21",
        "issuance_date":"2016-06-29",
        "expiration_date":"2021-06-25",
        "category": "AB",
        "ocr_key":"a5cf9c8f-2f66-4490-a7db-8a5bc70c1b76"
      }
    },
    "address": {
      "street": "Avenida de Exemplo",
      "number": "99",
      "neighborhood": "Vila do Exemplo",
      "city": "Jundiaí",
      "uf": "SP",
      "complement": "Ap 82",
      "postal_code": "00000-000",
      "country": "BRA",
      "ocr_key": "265b1b74-4b93-41dc-ac78-e1c37467225d"
    },
    "phones": [
      {
        "international_dial_code": "55",
        "area_code": "11",
        "number": "999998877",
        "type": "mobile"
      }
    ],
    "source": {
      "channel": "app",
      "platform": "ios",
      "ip":"175.92.122.2",
      "session_id": "93c68588-7a41-472f-95b3-835ea6ee1ede"
    },
    "face":
    {
      "type":"zaig_sdk",
      "registration_key":"d2677a8c-d575-44e1-a54d-ec00f9310f34"
    }
  }
```

A *legal_representative* object represents the data of a company’s legal representative being registered, as well as information regarding any validations they underwent during the registration process. The fields are:

name | type | description
:----: | :----: | -----------
name | string | Full name of the legal representative being registered
document_number | string | CPF of the legal representative, with periods and hyphens, following the standard format
birthdate | date | Legal representative’s birthdate in the expected format
gender | enum | Legal representative’s gender: 'male' or 'female'
nationality | string | Nationality of the legal representative, in ISO 3166-1 alpha-3
mother_name | string | Full name of the legal representative’s mother
occupation | string | Profession of the legal representative being registered
emails | Email | List of Email objects describing the legal representative’s email addresses
documents | Document | Document object representing any documents submitted during the representative’s registration
address | Address | Address object representing the legal representative’s residential address
phones | List of Phone | List of phone objects with the legal representative’s phone numbers
source | Source | Source object describing the platform used for submitting the registration
face | Face | Face object containing information on the facial validation

## *final_beneficiary* object

Request Body

```json
  {
    "id": "benef-001",
    "name": "Maria Sample",
    "document_number": "222.222.222-22",
    "declared_income": 1200000,
    "address": {
      "street": "Rua do Teste",
      "number": "111",
      "city": "São Paulo",
      "uf": "SP",
      "postal_code": "04570-140",
      "country": "BRA"
    }
  }
```

A *final_beneficiary* object represents an ultimate beneficiary of the company being registered. All fields are optional. The fields are:

name | type | description
:----: | :----: | ---------
id | string | Identifier of the ultimate beneficiary in your system
name | string | Full name of the ultimate beneficiary
document_number | string | CPF of the ultimate beneficiary, with dots and hyphen, according to the standard
declared_income | integer | Declared income of the ultimate beneficiary, in cents
address | Address | Address object describing the ultimate beneficiary's address

---

# Retrieve a Registration

URL: /en/documentation/caas/onboarding/query_registration

## Search a Specific Registration

To retrieve a specific Registration, just make a GET request. The returned result is the most up-to-date JSON of the requested Registration. If the provided identifier is not associated with any object, an HTTP 404 Status will be returned.

* **Natural Person:**

`GET https://api.caas.qitech.app/onboarding/natural_person/12345678`

* **Legal Person:**

`GET https://api.caas.qitech.app/onboarding/legal_person/12345678`

```shell
curl "https://api.caas.qitech.app/onboarding/natural_person/12345678"
  -H "Authorization: EXAMPLE-OF-API-KEY"
```

> The curl above returns the JSON that represents a Natural Person object.

```shell
curl "https://api.caas.qitech.app/onboarding/legal_person/12345678"
  -H "Authorization: EXAMPLE-OF-API-KEY"
```

> The curl above returns the JSON that represents a Legal Person object.

## Retrieve PDF

To retrieve a PDF of a registration, simply make a GET request. The returned result is the PDF file generated from the analysis. If you want the PDF to be returned in base64 format, you can add a query string named `base64` with the value `true`.

:::info **Attention**

PDF generation by the platform is asynchronous and takes a few seconds. If the GET request is made before the PDF is fully generated, a 404 error will be returned with a message explaining the situation. Simply retry after a few seconds and the PDF will be returned.
:::

* **Natural Person:**

`GET https://api.caas.qitech.app/onboarding/natural_person/12345678/pdf`

`GET https://api.caas.qitech.app/onboarding/natural_person/12345678/pdf?base64=true`

* **Legal Person:**

`GET https://api.caas.qitech.app/onboarding/legal_person/12345678/pdf`

`GET https://api.caas.qitech.app/onboarding/legal_person/12345678/pdf?base64=true`

```shell
curl "https://api.caas.qitech.app/onboarding/natural_person/12345678/pdf"
  -H "Authorization: EXAMPLE-OF-API-KEY"
```

> The curl above returns the PDF file generated by the request.

```shell
curl "https://api.caas.qitech.app/onboarding/legal_person/12345678/pdf"
  -H "Authorization: EXAMPLE-OF-API-KEY"
```

> The curl above returns the PDF file generated by the request.

---

# Integrating SDK data (face, documents and device)

URL: /en/documentation/caas/onboarding/sdk_integration

Integrating SDK data

QI Tech's SDKs (facial biometrics, document OCR and Device Scan) do not send data directly to the registration analysis. Each one returns a **key** to your backend, and it is up to you to attach those keys to the onboarding payload.

This page covers where each key goes — which differs between Natural Person and Legal Person.

| Block | What it carries | Source of the key |
| --- | --- | --- |
| `face` | Facial biometrics | Biometrics SDK |
| `documents` | Document OCR | OCR SDK/API |
| `source` | Device and session data | Device Scan SDK |

:::danger The difference that causes the most rework
In **Natural Person**, all three blocks are at the **root** of the payload.

In **Legal Person**, `face` and identity `documents` go **inside `legal_representatives[]`** — but `source` stays at the **root**. Details in [Legal Person](#legal-person).
:::

---

## Where each block goes

| Block | Natural Person | Legal Person |
| --- | --- | --- |
| `source` (`session_id`) | Root | **Root** — required for it to show on the dashboard |
| `face` | Root | Inside `legal_representatives[]` |
| `documents` (RG, CNH, passport) | Root | Inside `legal_representatives[]` |
| `documents` (`ie`, `company_statute`, `proof_of_address`) | — | Root |

---

## `source` — Device Scan and `session_id`

The `session_id` is the key returned by the Device Scan SDK. It is what connects the registration analysis to the device, geolocation and behavior data collected in the app or on the website.

session_id
string
optional — but see the warning
Session identifier generated by the Device Scan SDK. 1 to 500 characters.

channel
string
optional
Channel the registration originated from. E.g.: app , web , backoffice . 1 to 100 characters.

platform
string
optional
Platform. E.g.: android , ios , web . 1 to 100 characters.

ip
string
optional
Source IP. Accepts IPv4 and IPv6 . A malformed value returns HTTP 400.

os_version
string
optional
Operating system version. 1 to 100 characters.

gps_data
object
optional
Coordinates of the capture.

**`gps_data` fields:**

lat
number
optional
Latitude, between -90 and 90 .

lon
number
optional
Longitude, between -180 and 180 .

```json title="source — always at the root, Natural Person and Legal Person"
{
  "source": {
    "channel": "app",
    "platform": "android",
    "ip": "255.201.26.1",
    "session_id": "54b8e3cf-15de-41e5-9305-0ecf059d6e2a",
    "os_version": "14",
    "gps_data": {
      "lat": -23.5613,
      "lon": -46.6565
    }
  }
}
```

:::danger Legal Person: `session_id` must be at the root
In **Legal Person**, `source.session_id` must be in the **`source` object at the root** of the payload — not inside `legal_representatives[]`.

**If this field is not filled in at the root, the device data does not show up on the Legal Person registration analysis dashboard.** The schema accepts `source` inside `legal_representatives[]`, but that is not where the Legal Person dashboard reads the session from — putting it only there causes the data to be silently ignored in the analysis.
:::

---

## `face` — facial biometrics

type
enum
optional
How the biometrics were captured. It defines which of the fields below you must fill in.

registration_key
string
optional
Key returned by the biometrics SDK . Use it with type: "zaig_sdk" . UUID format.

image
string
optional
Base64 image. Use it with type: "base_64" when no SDK is involved.

### `type` values

| Value | Field to fill in |
| --- | --- |
| `zaig_sdk` | `registration_key` |
| `base_64` | `image` |

```json title="face via SDK"
{
  "face": {
    "type": "zaig_sdk",
    "registration_key": "46f38cf4-07b2-4de6-93e9-64b51a68378a"
  }
}
```

```json title="face via Base64"
{
  "face": {
    "type": "base_64",
    "image": "iVBORw0KGgoAAAANSUhEUg..."
  }
}
```

---

## `documents` — OCR

The OCR keys (`ocr_key`, `ocr_front_key`, `ocr_back_key`) come from the OCR SDK/API and go inside the corresponding document.

### Documents accepted in Natural Person

At the root of the Natural Person payload, `documents` accepts:

| Field | What it is | Accepted keys |
| --- | --- | --- |
| `rg` | Brazilian ID card (Registro Geral) | `ocr_front_key`, `ocr_back_key`, `ocr_key` |
| `cnh` | Brazilian driver's license | `ocr_key`, `ocr_front_key`, `ocr_back_key` |
| `passport` | Passport | `ocr_key` *(required)* |
| `ctps` | Work permit (Carteira de Trabalho) | `ocr_front_key` and `ocr_back_key` *(both required)* |
| `cin_digital` | Digital national ID card | `ocr_key` |
| `national_registry_of_foreigners` | RNE — National Registry of Foreigners | `ocr_key`, `ocr_front_key`, `ocr_back_key` |
| `national_migration_registry` | RNM — National Migration Registry | `ocr_key`, `ocr_front_key`, `ocr_back_key` |
| `class_entity_registry` | Professional association card (OAB, CRM…) | `ocr_key` *(required)* |
| `military_registry` | Military document | `ocr_key` *(required)* |
| `letter_of_emancipation` | Letter of emancipation | `ocr_key` *(required)* |
| `company_statute` | Articles of association / bylaws | `ocr_key`, `document_analysis_id` |
| `proof_of_address` | Proof of address | `document_analysis_id` |
| `others` | Other documents | `ocr_front_key` and `ocr_back_key` *(both required)* |

### Documents accepted in Legal Person

At the **root** of the Legal Person payload, `documents` accepts **only** company documents:

| Field | What it is | Accepted keys |
| --- | --- | --- |
| `ie` | State registration (inscrição estadual) | `ocr_key` — requires the `number` field |
| `company_statute` | Articles of association / bylaws | `ocr_key`, `document_analysis_id` |
| `proof_of_address` | Proof of address | `document_analysis_id` |

:::danger RG and CNH do not exist at the root of Legal Person
Identity documents (`rg`, `cnh`, `passport`…) are **not accepted** at the root of the Legal Person payload — the schema uses `additionalProperties: false` and the request returns **HTTP 400**.

They belong to the legal representative, inside `legal_representatives[]`.
:::

### OCR keys per document

The accepted keys per document are in the tables above. Worth highlighting:

- **Front and back:** for two-sided documents (`rg`, `ctps`, `others`), the standard is to send `ocr_front_key` and `ocr_back_key`. In `ctps` and `others` both are **required**.
- **Single key:** when the OCR returns a single key for the whole document, use `ocr_key`.
- **`document_analysis_id`:** used in `company_statute` and `proof_of_address`, which go through [Document Analysis](/documentation/caas/document_analysis/introduction) rather than identity OCR.

In `rg` and `cnh`, the `issuer_state` field accepts state abbreviations in uppercase or lowercase.

```json title="documents with OCR keys (Natural Person)"
{
  "documents": {
    "rg": {
      "number": "4.366.477-8",
      "issuer": "SSP",
      "issuer_state": "PR",
      "issuance_date": "2002-01-12",
      "ocr_front_key": "a5cf9c8f-2f66-4490-a7db-8a5bc70c1b76",
      "ocr_back_key": "b6df0d9e-3f77-45a1-b8ec-9b6cd81d2c87"
    },
    "cnh": {
      "register_number": "05163811694",
      "issuer_state": "PR",
      "first_issuance_date": "2011-03-21",
      "issuance_date": "2016-06-29",
      "expiration_date": "2031-06-25",
      "category": "AB",
      "ocr_key": "c7ef1eaf-4088-46b2-c9fd-ac7de92e3d98"
    }
  }
}
```

---

## Natural Person

All three blocks are at the root:

```json title="POST /onboarding/natural_person"
{
  "id": "12345678",
  "registration_date": "2026-08-07T11:37:15-03:00",
  "document_number": "111.111.111-11",
  "name": "John Sample",

  "source": {
    "channel": "app",
    "platform": "android",
    "ip": "255.201.26.1",
    "session_id": "54b8e3cf-15de-41e5-9305-0ecf059d6e2a"
  },

  "face": {
    "type": "zaig_sdk",
    "registration_key": "46f38cf4-07b2-4de6-93e9-64b51a68378a"
  },

  "documents": {
    "cnh": {
      "register_number": "05163811694",
      "issuer_state": "PR",
      "ocr_key": "c7ef1eaf-4088-46b2-c9fd-ac7de92e3d98"
    }
  }
}
```

---

## Legal Person

`source` at the root; `face` and identity documents inside `legal_representatives[]`:

```json title="POST /onboarding/legal_person"
{
  "id": "87654321",
  "registration_date": "2026-08-07T11:37:15-03:00",
  "document_number": "11.111.111/1111-11",
  "legal_name": "Empresa Exemplo LTDA",

  "source": {
    "channel": "web",
    "platform": "web",
    "ip": "255.201.26.1",
    "session_id": "54b8e3cf-15de-41e5-9305-0ecf059d6e2a"
  },

  "documents": {
    "company_statute": {
      "ocr_key": "d8fa2fb0-5199-47c3-daae-bd8ef03f4ea9"
    },
    "proof_of_address": {
      "document_analysis_id": "e9ab3ac1-62aa-48d4-ebbf-ce9fa14a5fb0"
    }
  },

  "legal_representatives": [
    {
      "id": "rep-001",
      "name": "Maria Sample",
      "document_number": "222.222.222-22",
      "birthdate": "1985-03-22",

      "face": {
        "type": "zaig_sdk",
        "registration_key": "46f38cf4-07b2-4de6-93e9-64b51a68378a"
      },

      "documents": {
        "cnh": {
          "register_number": "05163811694",
          "issuer_state": "SP",
          "ocr_key": "c7ef1eaf-4088-46b2-c9fd-ac7de92e3d98"
        }
      }
    }
  ]
}
```

:::tip Why the biometrics live in the representative
Biometrics and the identity document belong to a **person**, not to the company. In a Legal Person, the one who goes through biometric validation is the legal representative — that is why `face` and identity `documents` live inside `legal_representatives[]`.

Device data, on the other hand, belongs to the **session** in which the registration was made, which is unique for the whole request — that is why `source` stays at the root.
:::

---

## Common errors

| Symptom | Cause | Fix |
| --- | --- | --- |
| Device data does not show up on the Legal Person dashboard | `session_id` missing at the root, or sent only inside `legal_representatives[]` | Fill in `source.session_id` at the **root** of the payload |
| HTTP 400 when sending `rg`/`cnh` in Legal Person | Identity document at the root of Legal Person | Move it to `legal_representatives[].documents` |
| HTTP 400 on `passport` | Object sent without `ocr_key` | `ocr_key` is required in `passport` |
| HTTP 400 on `source.ip` | Malformed IP | Send a valid IPv4 or IPv6 |
| HTTP 400 with no obvious field | Field outside the schema (`additionalProperties: false`) | Check the `description` in the response |

---

## Checklist

- [ ] `source.session_id` at the **root**, both in Natural Person and in Legal Person.
- [ ] In Legal Person, confirmed that the device data shows up on the registration analysis dashboard.
- [ ] In Legal Person, `face` and identity documents inside `legal_representatives[]`.
- [ ] In Legal Person, only `ie`, `company_statute` and `proof_of_address` at the root of `documents`.
- [ ] `passport`, when sent, with `ocr_key`.

---

# Standards

URL: /en/documentation/caas/onboarding/standards

To simplify integration and ensure data integrity, some standards have been defined and are followed throughout the API.

## Monetary Values

> Examples:

```
10000
12345
98741
1223
1
0
```
The APIs assume that all monetary values sent are in Brazilian Reais. Values must be sent as integers in cents.

## Date and Time with Timezone

> Some examples:

```
2019-10-15T22:35:12.232-03:00
2018-05-01T13:32:11.297+00:00
2019-05-01T00:00:00.000+00:00
```

It is represented according to the ISO 8601 standard. In this case, the timezone is placed immediately after the time and should represent the timezone of the location where that data will be valid.

The validation mask is as follows:

`YYYY-MM-ddThh:mm:ss.sss±hh:mm`

## Date and Time without Timezone

> Some examples:

```
2019-10-15T22:35:12
2018-05-01T13:32:11
2019-05-01T00:00:00
```

It is represented according to the ISO 8601 standard. Data that is independent of timezones should be sent without one, always in UTC, using the letter Z to indicate that the data is in UTC. Therefore, the following format will be validated:

`YYYY-MM-ddThh:mm:ss.sssZ`

## Date
> Some examples:

``` 
2019-10-15
2019-01-01
2017-03-20
```

For fields that only receive a date—such as a birthdate—only the date, without any time, should be sent in the following format:

`YYYY-MM-dd`
 

## Documents

Since document numbers vary widely and many include non-numeric characters, all document numbers are defined as strings. Another important reason to treat them as strings is to preserve leading zeros. Documents mentioned on this page follow a strict mask and will be validated accordingly. Other documents, like RG, due to their lack of standardization, will not be validated.

## CPF

> Examples of valid CPFs based on the defined mask:

```
123.456.789-12
321.987.543-23
111.283.333-00
```

> Examples of invalid CPFs based on the defined mask:

```
8.577.477-8
08.104.627/0001-23
123.456.789-1
23.456.789-01
```

CPF is always defined as a string and will be validated against the following mask:

`###.###.###-##`

## CNPJ

> Examples of valid CNPJs based on the defined mask:

```
08.104.627/0001-02
01.079.210/0114-67
32.402.502/0001-35
```

> Examples of invalid CNPJs based on the defined mask:

```
8.577.477-8
123.456.789-12
321.987.543-23
32.402.502/0001-3
032.402.502/0001-3
```

CNPJ is always defined as a string and will be validated against the following mask:

`##.###.###/####-##`

## IP

> Examples of valid IPs based on the defined mask:

```
201.81.161.86
201.081.161.86
201.81.161.086
201.81.0.1
```

> Examples of invalid IPs:

```
201.81..86
358.81.161.86
201.81.161
```

IPs must always be sent in IPv4 format. Leading zeros are optional, as long as the following mask is respected:

`###.###.###.###`

---

# Status dynamics

URL: /en/documentation/caas/onboarding/status_dynamics

Status dynamics

The registration analysis API works with **three** independent status enumerators. Understanding the difference between them is the step that prevents the most integration errors:

| Enumerator | Who defines the value | Who returns it | What it represents |
| --- | --- | --- | --- |
| `analysis_status` | **You**, in your policy | **QI Tech** | The result of running your decision tree. |
| `risk_level` | **You**, in your policy | **QI Tech** | The risk level assigned by your decision tree. |
| `client_status` | **You** | — | The customer's situation on your platform. |

:::info `analysis_status` and `risk_level` come from your own policy
These two fields are **not** a verdict of ours about the registration. They are defined by you, in the rules engine, through the **decision** and **risk level** blocks you place along your decision tree.

On every request, QI Tech runs that tree against the analyzed data and returns the result **your** policy determined. If you want a scenario to start falling into `in_manual_analysis` instead of `automatically_reproved`, or a profile to receive `risk_level: high` instead of `medium`, the change belongs in the rules engine — there is nothing to change in the integration.
:::

:::tip The rule of thumb
`analysis_status` is the **result of your policy**, executed by us. `client_status` is **your business decision**, which you record via [PUT](/documentation/caas/onboarding/update_registration) as the customer journey evolves.
:::

### How the decision is produced

1. You design the decision tree in the rules engine, placing the **decision** (`analysis_status`) and **risk level** (`risk_level`) blocks according to your policy.
2. You send the registration to the API.
3. QI Tech runs the tree against the registration data and the available enrichments.
4. The response carries the `analysis_status` and `risk_level` that your tree determined for that case.

This is why two clients sending exactly the same registration can receive different responses: each one has its own policy configured.

---

## `analysis_status`

The result of running your decision tree. The statuses below fall into three groups according to what you should do with each one.

### Final decisions

| Status | Meaning | Action |
| --- | --- | --- |
| `automatically_approved` | Your decision tree ended in an automatic approval block. | You may approve the registration. |
| `automatically_reproved` | Your decision tree ended in an automatic rejection block. | Decline the registration. |
| `manually_approved` | Approved by an analyst. | You may approve the registration. |
| `manually_reproved` | Rejected by an analyst. | Decline the registration. |
| `approved_by_time` | Automatically approved after the analysis deadline expired. | You may approve the registration. |
| `reproved_by_time` | Automatically rejected after the analysis deadline expired. | Decline the registration. |

### Waiting — the result arrives via webhook

| Status | Meaning | Action |
| --- | --- | --- |
| `in_queue` | Asynchronous analysis queued. | Wait for the [Webhook](/documentation/caas/onboarding/webhook). |
| `pending` | The queries are taking longer than expected. | Wait for the Webhook. |
| `in_manual_analysis` | Your tree routed the case to manual analysis. | Wait for the Webhook. |
| `waiting_for_data` | Waiting for additional data to process. | Wait for the Webhook. |
| `on_hold` | Analysis paused, waiting for the customer's response. | Wait for the Webhook. |

:::danger Do not treat "waiting" as a rejection
`in_queue`, `pending`, `in_manual_analysis`, `waiting_for_data` and `on_hold` are **not negative outcomes**. Treating them as rejections is the most common integration error in this API — it declines legitimate registrations that would have been approved minutes later.
:::

### Challenges and special cases

| Status | Meaning | Action |
| --- | --- | --- |
| `automatically_challenged` | Your tree ended in a challenge block. | The registration must go through the challenge flow. |
| `manually_challenged` | Challenged by an analyst. | Same as above. |
| `manually_cancelled` | Analysis cancelled. | No decision will be issued. |
| `failed` | The analysis failed during processing. | Resubmit with a new `id` or contact support. |
| `not_analysed` | Submitted with `analyze=false`. | No recommendation will be issued; follow your own decision. |

---

## `client_status`

The customer's registration situation on **your** platform. You are responsible for keeping this status up to date via [PUT](/documentation/caas/onboarding/update_registration) — it feeds the models and improves future analyses.

| Status | Meaning |
| --- | --- |
| `registered` | Registered, with no approval decision yet. |
| `approved` | Approved on your platform. |
| `reproved` | Rejected on your platform. |
| `fraud_blocked` | Blocked due to suspected or confirmed fraud. |
| `default_blocked` | Blocked due to default. |
| `cancelled` | The customer cancelled the use of the service. |

:::info Spelling of the enumerator
The correct value is `cancelled`, with two L's. Older versions of this documentation spelled it `canceled` — that value is rejected with HTTP 400.
:::

### Which values can be sent

The method used determines the accepted values:

| Registration type | Values accepted in `PUT` |
| --- | --- |
| Natural Person | `approved`, `reproved`, `fraud_blocked`, `default_blocked`, `cancelled` |
| Legal Person | `fraud_blocked`, `default_blocked`, `cancelled` |

:::caution Legal Person accepts fewer values
In **Legal Person**, `PUT` does **not** accept `approved` or `reproved` — only the three block and cancellation values. Sending `approved` on a Legal Person registration returns **HTTP 400**.
:::

Details in [Update a registration](/documentation/caas/onboarding/update_registration).

---

## `risk_level`

The risk level assigned to the registration by the risk level block your tree went through. Present in the `GET` response and in the analysis events.

| Value | Meaning |
| --- | --- |
| `low` | Low risk. |
| `medium` | Medium risk. |
| `high` | High risk. |
| `critical` | Critical risk. |
| `undefined` | No risk assessment was performed. |

---

## Typical flow

1. You submit the registration — `POST /onboarding/natural_person`.
2. The response returns an `analysis_status`.
   - If it is a **final decision**, follow what your policy determined.
   - If it is **waiting**, wait for the webhook.
3. When you make a decision on your platform, send the `client_status` via `PUT`.

---

# Update a registration

URL: /en/documentation/caas/onboarding/update_registration

Update a registration

Use the `PUT` method to update the **status** of a registration — both the `client_status` (situation on your platform) and the `analysis_status` (manual analysis decision).

:::tip Feedback matters
Reporting the actual outcome via `PUT` is what keeps the quality of the recommendations high. Without that feedback, the models do not learn from the cases in your portfolio.
:::

The endpoint accepts the two registration types:

```text
/onboarding/natural_person/{external_id}
/onboarding/legal_person/{external_id}
```

The `{external_id}` is the `id` you sent in the `POST`.

---

## PUT — update status

ENDPOINT /onboarding/natural_person/ EXTERNAL_ID
METHOD PUT

The body accepts **two mutually exclusive shapes**: one for `client_status`, another for `analysis_status`.

**client_status**

Updates the customer's situation on your platform.

client_status
enum
required
In Natural Person accepts approved , reproved , fraud_blocked , default_blocked or cancelled . In Legal Person , only fraud_blocked , default_blocked or cancelled .

event_date
datetime
required
Date and time of the event, with time zone. Offset ending in :00 / :30 or the Z suffix.

```json title="Fraud block"
{
  "client_status": "fraud_blocked",
  "event_date": "2026-08-07T13:34:12-03:00"
}
```

```json title="Default block"
{
  "client_status": "default_blocked",
  "event_date": "2026-08-07T13:34:12-03:00"
}
```

```json title="Cancellation by the customer"
{
  "client_status": "cancelled",
  "event_date": "2026-08-07T13:34:12-03:00"
}
```

**analysis_status**

Records a manual analysis decision.

analysis_status
enum
required
Accepts manually_approved , manually_reproved , manually_challenged , manually_cancelled or on_hold .

risk_level
enum
optional
low , medium , high or critical .

observation
string
optional
Justification for the decision. Up to 3,000 characters.

user_name
string
optional
Name of the analyst responsible. Up to 50 characters.

user_email
string
optional
Email of the analyst responsible.

```json title="Manual approval"
{
  "analysis_status": "manually_approved",
  "risk_level": "low",
  "observation": "Documentation reviewed and validated.",
  "user_name": "Ana Analyst",
  "user_email": "ana@exemplo.com.br"
}
```

### Request examples

**Python**

```python
import requests

BASE_URL = "https://api.sandbox.caas.qitech.app"
API_KEY = "YOUR_API_KEY"
EXTERNAL_ID = "12345678"

response = requests.put(
    f"{BASE_URL}/onboarding/natural_person/{EXTERNAL_ID}",
    json={
        "client_status": "approved",
        "event_date": "2026-08-07T13:34:12-03:00",
    },
    headers={"Authorization": API_KEY},
    timeout=30,
)

response.raise_for_status()
```

**PHP**

```php
<?php

$baseUrl    = 'https://api.sandbox.caas.qitech.app';
$apiKey     = 'YOUR_API_KEY';
$externalId = '12345678';

$payload = [
    'client_status' => 'approved',
    'event_date'    => '2026-08-07T13:34:12-03:00',
];

$ch = curl_init("{$baseUrl}/onboarding/natural_person/{$externalId}");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Authorization: ' . $apiKey,
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("Update failed: HTTP {$status} — {$body}");
}
```

**Node.js**

```javascript
const BASE_URL = "https://api.sandbox.caas.qitech.app";
const API_KEY = "YOUR_API_KEY";
const EXTERNAL_ID = "12345678";

async function updateStatus() {
  const response = await fetch(
    `${BASE_URL}/onboarding/natural_person/${EXTERNAL_ID}`,
    {
      method: "PUT",
      headers: {
        "Content-Type": "application/json",
        Authorization: API_KEY,
      },
      body: JSON.stringify({
        client_status: "approved",
        event_date: "2026-08-07T13:34:12-03:00",
      }),
    },
  );

  if (!response.ok) {
    throw new Error(`Update failed: HTTP ${response.status}`);
  }
}

updateStatus();
```

**Java**

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class UpdateClientStatus {

    private static final String BASE_URL = "https://api.sandbox.caas.qitech.app";
    private static final String API_KEY = "YOUR_API_KEY";
    private static final String EXTERNAL_ID = "12345678";

    public static void main(String[] args) throws Exception {
        String payload = """
            {
              "client_status": "approved",
              "event_date": "2026-08-07T13:34:12-03:00"
            }
            """;

        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL + "/onboarding/natural_person/" + EXTERNAL_ID))
                .header("Content-Type", "application/json")
                .header("Authorization", API_KEY)
                .timeout(Duration.ofSeconds(30))
                .PUT(HttpRequest.BodyPublishers.ofString(payload))
                .build();

        HttpResponse<String> response =
                client.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 200) {
            throw new IllegalStateException(
                    "Update failed: HTTP " + response.statusCode());
        }
    }
}
```

**C#**

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class UpdateClientStatus
{
    private const string BaseUrl = "https://api.sandbox.caas.qitech.app";
    private const string ApiKey = "YOUR_API_KEY";
    private const string ExternalId = "12345678";

    public static async Task Main()
    {
        var payload = new
        {
            client_status = "approved",
            event_date = "2026-08-07T13:34:12-03:00"
        };

        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
        client.DefaultRequestHeaders.Add("Authorization", ApiKey);

        var content = new StringContent(
            JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

        var response = await client.PutAsync(
            $"{BaseUrl}/onboarding/natural_person/{ExternalId}", content);

        if (!response.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"Update failed: HTTP {(int)response.StatusCode}");
        }
    }
}
```

**curl**

```bash
curl -X PUT \
  'https://api.sandbox.caas.qitech.app/onboarding/natural_person/12345678' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: YOUR_API_KEY' \
  -d '{
    "client_status": "approved",
    "event_date": "2026-08-07T13:34:12-03:00"
  }'
```

## Errors

| Status | Situation | How to resolve |
| --- | --- | --- |
| 400 | Enum outside the accepted list. | Check the accepted values for `client_status` and `analysis_status`. |
| 400 | `client_status` without `event_date`. | Send both together. |
| 400 | Field not defined in the schema. | The schema uses `additionalProperties: false`. |
| 404 | Registration not found for your API Key. | Check the `external_id` in the path. |

Full list in [HTTP Status](/documentation/caas/onboarding/http_status).

---

# Webhook

URL: /en/documentation/caas/onboarding/webhook

Webhook

Fraud status updates (for registrations that are routed to manual review or responded with a Pending status) are notified via Webhook. To enable this, you must configure an endpoint URL and a *secret_token* used to sign the request through our [support team](mailto:suporte.caas@qitech.com.br).

Although not recommended, clients may alternatively use the [polling](https://en.wikipedia.org/wiki/Polling_(computer_science)) technique. In this case, simply avoid configuring a webhook endpoint and rely on the registration retrieval endpoints to perform polling.

## Webhook Signature

## Request

```bash
curl --location 'YOUR-ENDPOINT-HERE' \
--header 'Signature: CALCULATED-HASH-HMAC' \
--data '{"natural_person_id": "538509",  "analysis_status": "manually_approved", "event_date": "2024-11-13T17:52:50Z", "reason": "manually_approved"}'
```

The request follows the format above and notifies the change in fraud status. It is important to note that the request uses the HTTP POST verb and the body is sent as UTF-8 encoded string.

## Retries

A notification is considered successful when it receives an HTTP 200 status code in response. If the notifications fail, the system will attempt up to 5 retries at the following intervals, until a 200 is returned or all attempts are exhausted:

* 30 seconds  
* 60 seconds  
* 120 seconds  
* 240 seconds  
* 360 seconds