> ## Documentation Index
> Fetch the complete documentation index at: https://developers.useqx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Create a contact and a collection, send an invoice and read the fields of the invoice.

This guide takes the invoice of an import from your system to the fields that the reading extracts. It uses `curl` and five endpoints, in this order:

1. `GET /packs` shows the pack and the document type of the invoice.
2. `POST /contacts` creates the contact.
3. `POST /collections` creates the collection of the contact.
4. `POST /documents` sends the invoice to the collection.
5. `GET /documents/{id}` shows the progress of the reading and, at the end, the fields of the invoice.

## Before you start

You need three things:

* the API key of the organization. See [Authentication](/en/authentication);
* an active operation pack that asks for the commercial invoice. The operator creates the pack in QX;
* the PDF of a commercial invoice, with the name `fatura.pdf`.

The examples use `qx_sua_chave`. Replace this text with the key of the organization.

Each `POST` in the guide carries the `Idempotency-Key` header. Create a new value for each write, and keep the value with the write in your system. If the response does not arrive, repeat the call with the same value. The API writes the resource only once. See [Idempotency](/en/idempotency).

## 1. Find the pack and the invoice type

```bash theme={null}
curl "https://app.useqx.com/api/v1/packs?kind=operation&active=true" \
  -H "Authorization: Bearer qx_sua_chave"
```

The response contains the active operation packs. Each requirement of the pack contains the document type that the requirement asks for:

```json theme={null}
{
  "data": [
    {
      "id": "pck_12",
      "name": "Importação marítima",
      "kind": "operation",
      "required_for": "pj",
      "active": true,
      "identification_mode": "any",
      "requirements": [
        {
          "document_type": { "id": "dtp_31", "name": "Fatura Comercial (Commercial Invoice)" },
          "required": true,
          "identifier": true,
          "accepts_many": true,
          "position": 1
        },
        {
          "document_type": { "id": "dtp_32", "name": "Bill of Lading (B/L)" },
          "required": true,
          "identifier": true,
          "accepts_many": false,
          "position": 2
        },
        {
          "document_type": { "id": "dtp_33", "name": "Romaneio de Carga (Packing List)" },
          "required": false,
          "identifier": false,
          "accepts_many": true,
          "position": 3
        }
      ]
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

Save the `id` of the pack (`pck_12`) and the `id` of the invoice type (`dtp_31`).

## 2. Create the contact

```bash theme={null}
curl https://app.useqx.com/api/v1/contacts \
  -H "Authorization: Bearer qx_sua_chave" \
  -H "Idempotency-Key: d4481be1-a6f6-4bde-bfd9-e15f83757aa1" \
  -H "Content-Type: application/json" \
  -d '{"tax_id": "12.345.678/0001-95", "email": "fiscal@importadora.com.br", "name": "Importadora Exemplo Ltda"}'
```

The `201` response contains the contact:

```json theme={null}
{
  "id": "com_12",
  "type": "company",
  "name": "Importadora Exemplo Ltda",
  "trade_name": null,
  "email": "fiscal@importadora.com.br",
  "tax_id": "12345678000195",
  "lookup_status": "pending",
  "discarded": false,
  "origin": "contact",
  "created_at": "2026-09-25T10:30:00.000-03:00"
}
```

* QX creates a company when you send a CNPJ, and a person when you send a CPF.
* QX looks up the CNPJ after the creation. The `lookup_status` field shows the progress of the lookup.
* The organization can already have a contact with this CNPJ. In that case, the response is `409` with the code `contact_exists`, and the `contact` field contains the contact. Use the `id` of that contact in the next steps.

## 3. Create the collection

```bash theme={null}
curl https://app.useqx.com/api/v1/collections \
  -H "Authorization: Bearer qx_sua_chave" \
  -H "Idempotency-Key: c3cbd9dc-1f1b-4541-857f-90bd8f3641ea" \
  -H "Content-Type: application/json" \
  -d '{"pack_id": "pck_12", "contact_id": "com_12"}'
```

The `201` response contains the collection, with an empty slot for each requirement of the pack:

```json theme={null}
{
  "id": "col_40",
  "protocol": "DOC-2026-00123",
  "state": "assembling",
  "discarded": false,
  "contact": { "id": "com_12", "type": "company", "name": "Importadora Exemplo Ltda" },
  "recipient": null,
  "packs": [{ "id": "pck_12", "name": "Importação marítima" }],
  "numbers": [],
  "created_at": "2026-09-25T10:31:12.000-03:00",
  "ready_at": null,
  "documents": [
    {
      "id": "doc_310",
      "document_type": { "id": "dtp_31", "name": "Fatura Comercial (Commercial Invoice)" },
      "status": "pending",
      "has_content": false
    },
    {
      "id": "doc_311",
      "document_type": { "id": "dtp_32", "name": "Bill of Lading (B/L)" },
      "status": "pending",
      "has_content": false
    },
    {
      "id": "doc_312",
      "document_type": { "id": "dtp_33", "name": "Romaneio de Carga (Packing List)" },
      "status": "pending",
      "has_content": false
    }
  ]
}
```

* The collection starts in the `assembling` state, in assembly.
* QX does not send an email to the contact. To send the portal invitation to the contact, also send `"send_invitation": true`.

## 4. Send the invoice to the collection

```bash theme={null}
curl https://app.useqx.com/api/v1/documents \
  -H "Authorization: Bearer qx_sua_chave" \
  -H "Idempotency-Key: eb3754f8-a422-43f5-8d77-b0aa74cef381" \
  -F file=@fatura.pdf \
  -F contact_id=com_12 \
  -F document_type_id=dtp_31 \
  -F collection_id=col_40
```

The `201` response contains the new document. The reading already started, and the fields have no answers yet:

```json theme={null}
{
  "id": "doc_318",
  "document_type": { "id": "dtp_31", "name": "Fatura Comercial (Commercial Invoice)" },
  "status": "uploaded",
  "reading_status": "extracting",
  "contact": { "id": "com_12", "type": "company", "name": "Importadora Exemplo Ltda" },
  "collection_id": "col_40",
  "in_triage": false,
  "discarded": false,
  "discard_reason": null,
  "number": null,
  "arrival_channel": "api",
  "arrived_at": "2026-09-25T10:32:05.000-03:00",
  "files": [{ "name": "fatura.pdf", "content_type": "application/pdf", "size": 182044 }],
  "created_at": "2026-09-25T10:32:05.000-03:00",
  "fields": [
    { "key": "invoice_number", "label": "Número da Fatura", "value": null },
    { "key": "invoice_date", "label": "Data da Fatura", "value": null },
    { "key": "invoice_amount", "label": "Valor da Fatura", "value": null },
    { "key": "currency", "label": "Moeda", "value": null }
  ]
}
```

* The new document takes the empty slot of the invoice, and QX removes the slot `doc_310` from the collection. Use the `id` of the response (`doc_318`) in the next steps.
* When every required document of the collection arrives, the collection moves to `reviewing`, and QX notifies the operators.

## 5. Wait for the reading

Call `GET /documents/{id}` every 10 seconds or more. Stop when the `reading_status` reaches one of these values:

| `reading_status` | What it means |
| - | - |
| `complete` | The reading ended, and the fields have the answers that the reading extracted. |
| `failed` | The reading failed. |
| `no_reader` | The document type has no reading. |

The values `not_started`, `extracting`, `enriching` and `validating` tell that the reading did not end yet.

The loop below uses [jq](https://jqlang.org) to read the field:

```bash theme={null}
while true; do
  status=$(curl -s https://app.useqx.com/api/v1/documents/doc_318 \
    -H "Authorization: Bearer qx_sua_chave" | jq -r .reading_status)
  echo "$status"
  case "$status" in
    complete|failed|no_reader) break ;;
  esac
  sleep 10
done
```

## 6. Read the fields

```bash theme={null}
curl https://app.useqx.com/api/v1/documents/doc_318 \
  -H "Authorization: Bearer qx_sua_chave"
```

The response contains all the fields of the type, in the catalog order. The example shows only a part of the fields:

```json theme={null}
{
  "id": "doc_318",
  "document_type": { "id": "dtp_31", "name": "Fatura Comercial (Commercial Invoice)" },
  "status": "uploaded",
  "reading_status": "complete",
  "contact": { "id": "com_12", "type": "company", "name": "Importadora Exemplo Ltda" },
  "collection_id": "col_40",
  "in_triage": false,
  "discarded": false,
  "discard_reason": null,
  "number": "INV-81244/2026",
  "arrival_channel": "api",
  "arrived_at": "2026-09-25T10:32:05.000-03:00",
  "files": [{ "name": "fatura.pdf", "content_type": "application/pdf", "size": 182044 }],
  "created_at": "2026-09-25T10:32:05.000-03:00",
  "fields": [
    { "key": "invoice_number", "label": "Número da Fatura", "value": "INV-81244/2026" },
    { "key": "invoice_date", "label": "Data da Fatura", "value": "2026-09-18" },
    { "key": "invoice_amount", "label": "Valor da Fatura", "value": "48,250.00" },
    { "key": "currency", "label": "Moeda", "value": "USD" }
  ]
}
```

* Each item of `fields` contains the key, the label and the answer of the field.
* A field without an answer has `value` equal to `null`.
* The `number` field contains the printed number that identifies the document. On the invoice, it is the invoice number.

To correct an answer, call `PATCH /documents/{id}` with `fields`. The value that your system writes wins over the value that the reading extracted.

## Usage rules

Follow these four rules in every integration:

1. Send the documents of the same operation with `collection_id`. Each upload through the API arrives alone, and the document that it creates has no siblings in Triage. Without a collection, QX links the document to a collection of the operation only by the numbers that the document prints or cites. A document without these numbers can wait in Triage for an operator.
2. Send a registration document, from a `kyc` pack, with `collection_id`. Without a collection, the sweep discards this document when the organization has an active operation pack. The response shows `discarded` equal to `true` and `discard_reason` equal to `outside_pack`.
3. Check the `reading_status` every 10 seconds or more. Each check counts toward the limit of 300 calls per minute of the key, together with the other calls. See [Authentication](/en/authentication).
4. Send one document per file. The API does not split the file. A file that joins the invoice, the packing list and the B/L becomes one document, of the type that the call gave.
