# Business Contract AI

Read articles of incorporation, board minutes and other corporate documents with AI — and get back a structured, machine-readable description of who can sign on behalf of the company, what their powers are, and how votes are combined.

Stark Infra runs OCR over each PDF, calls the AI Model to extract the company data, signing rules and eligible signers, and returns the result asynchronously through a webhook.

Each analysis is composed of one Business Identity (the legal entity) and up to two Business Attachment files (the corporate documents).

NOTE: Read [Core Concepts](https://docs.starkinfra.com/get-started/core-concepts.md) before continuing this guide.

**RESOURCE SUMMARY**

**Business Identity**Register the legal entity that will have its corporate documents read by the AI Model.**Business Attachment**Upload the PDFs (articles of incorporation, amendments, board minutes) tied to the identity.

## Available Languages

Code samples on this page are in Python. The same content is available with samples in:

- [Python](https://docs.starkinfra.com/get-started/contract-ai-python.md)
- [Node.js](https://docs.starkinfra.com/get-started/contract-ai-node.md)
- [PHP](https://docs.starkinfra.com/get-started/contract-ai-php.md)
- [Java](https://docs.starkinfra.com/get-started/contract-ai-java.md)
- [Ruby](https://docs.starkinfra.com/get-started/contract-ai-ruby.md)
- [Elixir](https://docs.starkinfra.com/get-started/contract-ai-elixir.md)
- [.NET](https://docs.starkinfra.com/get-started/contract-ai-dotnet.md)
- [Go](https://docs.starkinfra.com/get-started/contract-ai-go.md)
- [Clojure](https://docs.starkinfra.com/get-started/contract-ai-clojure.md)
- [cURL](https://docs.starkinfra.com/get-started/contract-ai-curl.md)

## Setup

1. Get in touch with your account manager to enable the Business Contract AI product to your account.

2. Create a [Webhook resource](https://docs.starkinfra.com/api.md#webhook) subscribed to `business-identity` events to be notified when the AI Model finishes.

3. Have the corporate PDFs ready before calling the API — articles of incorporation and the most recent amendment are typically mandatory.

## Typical flow

**1.** Create a Business Identity with the company's CNPJ. Save the returned `id` — you'll reference it as `businessIdentityId` in every subsequent call.

**2.** Upload each corporate PDF as a Business Attachment, passing the saved `businessIdentityId`. Each call returns an attachment in `created` status.

**3.** Once the attachments are uploaded, send the identity to processing with `PATCH /business-identity/{id}` and `status=processing` — this triggers the AI Model analysis.

**4.** The identity ends in `success` or `failed`. On `success`, the webhook payload includes the validated organization name, CNPJ, document type and the list of `rules` with eligible signers and required signatures.

## Use cases

**KYB onboarding:** read the articles of incorporation submitted by the customer and pre-fill the partner list, signing rules and beneficial owners.

**Credit underwriting:** before signing a Credit Note with a legal entity, check that the candidate signer has the power to bind the company.

**Document review:** spot inconsistencies between what a customer declared and what the corporate documents actually say.

## Business Identity Overview

A Business Identity represents the legal entity whose corporate documents will be read by the AI Model. Create it first to obtain the `id` that ties together every Business Attachment and triggers the analysis.

### The Business Identity Object

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| `id` | STRING | Unique id for the Business Identity. |
| `taxId` | STRING | CNPJ of the legal entity, with or without punctuation. Example: "20.018.183/0001-80". |
| `name` | STRING | Official corporate name (razão social) fetched from the bureau at creation time. |
| `taxIdStatus` | STRING | Current status of the CNPJ at the bureau, normalized from the Receita Federal value to a stable, language-agnostic option. Mapping: `active` = "ATIVA", `blocked` = "SUSPENSA", `pending` = "INAPTA", `canceled` = "BAIXADA", `voided` = "NULA". |
| `insightTaxId` | STRING | CNPJ extracted from the corporate document by the AI Model. Empty string until the analysis succeeds. |
| `insightDocumentType` | STRING | Type of document detected by the AI Model (e.g., articles of incorporation, amendment, board minute). Empty string until the analysis succeeds. |
| `representatives` | LIST OF OBJECTS | List of representative objects (sócios) fetched from the bureau. Each entry carries at least `name`, `qualification` and `taxId`. |
| `attachments` | LIST OF STRINGS | References to the documents tied to the identity, in the format `"attachment/<id>"`. A Business Identity holds at most two attachments. |
| `rules` | LIST OF OBJECTS | List of signature rule objects extracted by the AI Model — each entry lists a valid signature combination (clause reference, literal text, required signatures and eligible signers). Empty list until `status=success`. |
| `tags` | LIST OF STRINGS | Free-form tags used to label the identity for future queries. |
| `status` | STRING | Current lifecycle status of the Business Identity. Options: `created`, `pending`, `processing`, `success`, `failed`, `canceled`. |
| `created` | STRING | Creation datetime. Example: "2026-06-12T12:00:00.000000+00:00". |
| `updated` | STRING | Last update datetime. Example: "2026-06-12T12:00:00.000000+00:00". |

### Create Business Identities

`POST /v2/business-identity`

Create one or more Business Identities. Each entry must include the CNPJ of the legal entity.

The identity is created in `pending` status. Save the returned `id` — you'll use it as `identityId` when uploading the Business Attachments and as the path parameter when sending the identity to processing.

**Validations:** the CNPJ must be valid, must be active in the official bureau and must return non-empty `representatives` (sócios).

**Parameters**

| Name | Required | Description |
| --- | --- | --- |
| `taxId` | REQUIRED | CNPJ of the legal entity, with or without punctuation. Example: "20.018.183/0001-80". |
| `tags` | OPTIONAL | List of free-form tags to label the identity for future queries. |

**Request**

```python
import starkinfra

identities = starkinfra.businessidentity.create([
    starkinfra.BusinessIdentity(
        tax_id="20.018.183/0001-80",
        tags=["onboarding-123"]
    )
])

for identity in identities:
    print(identity)
```

**Response**

```python
[
    BusinessIdentity(
        id="5740539971076096",
        tax_id="20.018.183/0001-80",
        name="STARK BANK S.A.",
        tax_id_status="active",
        insight_tax_id="",
        insight_document_type="",
        representatives=[{"name": "Daenerys Targaryen Stormborn", "qualification": "Diretor", "taxId": "012.345.678-90"}],
        attachments=[],
        rules=[],
        tags=["onboarding-123"],
        status="pending",
        created="2026-06-12T12:00:00+00:00",
        updated="2026-06-12T12:00:00+00:00"
    )
]
```

### Send Business Identity to processing

`PATCH /v2/business-identity/:id`

Patch a Business Identity to trigger the AI Model analysis. The identity must be in `pending` status and must already have at least one Business Attachment associated with it.

Once `status=processing` is set, Stark Infra runs OCR and AI extraction. The `business-identity` webhook fires when the identity reaches `success` or `failed`.

**Parameters**

| Name | Required | Description |
| --- | --- | --- |
| `id` | REQUIRED | Id returned by `POST /v2/business-identity`. Sent as the path parameter. |
| `status` | OPTIONAL | Only `processing` is accepted. Triggers the AI Model analysis. |
| `tags` | OPTIONAL | Replace the current list of tags. |

**Request**

```python
import starkinfra

identity = starkinfra.businessidentity.update(
    id="5740539971076096",
    status="processing"
)

print(identity)
```

**Response**

```python
BusinessIdentity(
    id="5740539971076096",
    tax_id="20.018.183/0001-80",
    name="STARK BANK S.A.",
    tax_id_status="active",
    insight_tax_id="",
    insight_document_type="",
    representatives=[{"name": "Daenerys Targaryen Stormborn", "qualification": "Diretor", "taxId": "012.345.678-90"}],
    attachments=["attachment/5104320788332544"],
    rules=[],
    tags=["onboarding-123"],
    status="processing",
    created="2026-06-12T12:00:00+00:00",
    updated="2026-06-12T12:05:00+00:00"
)
```

### Get a Business Identity

`GET /v2/business-identity/:id`

Retrieve a Business Identity by its `id`. Use this to inspect the current `status` of the analysis and, when `status=success`, read the structured `rules` extracted by the AI Model.

Each `rules` entry describes a valid signature combination — the clause reference in the original document, the literal text, the number of required signatures and the eligible signers. Every signer carries `isSignatureRequired` (whether they must sign in that specific combination) and `isRepresentative` (whether they appear in the company's representative board).

**Parameters**

| Name | Required | Description |
| --- | --- | --- |
| `id` | REQUIRED | Id returned by `POST /v2/business-identity`. Sent as the path parameter. |
| `fields` | OPTIONAL | Comma-separated list of response keys to return. |

**Request**

```python
import starkinfra

identity = starkinfra.businessidentity.get("5740539971076096")
print(identity)
```

**Response**

```python
BusinessIdentity(
    id="5740539971076096",
    tax_id="20.018.183/0001-80",
    name="STARK BANK S.A.",
    tax_id_status="active",
    insight_tax_id="20.018.183/0001-80",
    insight_document_type="articles-of-incorporation",
    representatives=[{"name": "Daenerys Targaryen Stormborn", "qualification": "Diretor", "taxId": "012.345.678-90"}],
    attachments=["attachment/5104320788332544"],
    rules=[{"reference": "Artigo 16, item (a)", "content": "pela assinatura isolada do Diretor Presidente", "signaturesRequired": 1, "eligibles": [{"name": "Daenerys Targaryen Stormborn", "taxId": "012.345.678-90", "role": "Diretor Presidente", "isSignatureRequired": True, "isRepresentative": True}]}, {"reference": "Artigo 16, item (b)", "content": "pela assinatura de quaisquer 2 (dois) diretores em conjunto", "signaturesRequired": 2, "eligibles": [{"name": "Daenerys Targaryen Stormborn", "taxId": "012.345.678-90", "role": "Diretor Presidente", "isSignatureRequired": False, "isRepresentative": True}, {"name": "Jon Snow", "taxId": "098.765.432-10", "role": "Diretor de Riscos e Compliance", "isSignatureRequired": False, "isRepresentative": True}, {"name": "Arya Stark", "taxId": "111.222.333-44", "role": "Diretor Financeiro", "isSignatureRequired": False, "isRepresentative": True}]}, {"reference": "Artigo 16, item (c)", "content": "pela assinatura de 1 (um) diretor em conjunto com 1 (um) procurador", "signaturesRequired": 2, "eligibles": [{"name": "Procurador", "taxId": "", "role": "Procurador", "isSignatureRequired": True, "isRepresentative": False}, {"name": "Daenerys Targaryen Stormborn", "taxId": "012.345.678-90", "role": "Diretor Presidente", "isSignatureRequired": False, "isRepresentative": True}, {"name": "Jon Snow", "taxId": "098.765.432-10", "role": "Diretor de Riscos e Compliance", "isSignatureRequired": False, "isRepresentative": True}]}],
    tags=["onboarding-123"],
    status="success",
    created="2026-06-12T12:00:00+00:00",
    updated="2026-06-12T12:10:00+00:00"
)
```

### Deleting a Business Identity

`DELETE /v2/business-identity/:id`

Cancel a Business Identity before it has been sent to processing. The identity transitions to `canceled` and is no longer eligible to receive new attachments or be patched to `processing`.

**Validations:** the identity must exist and belong to your workspace; its current `status` must be `created` or `pending` — identities already in `processing`, `success`, `failed` or `canceled` cannot be canceled again.

Canceling an identity does **not** cancel the Business Attachments tied to it. If you also need to discard the uploaded documents, call `DELETE /v2/business-attachment/:id` on each one before they leave `created` status.

**Parameters**

| Name | Required | Description |
| --- | --- | --- |
| `id` | REQUIRED | Id returned by `POST /v2/business-identity`. Sent as the path parameter. |

**Request**

```python
import starkinfra

identity = starkinfra.businessidentity.cancel("5740539971076096")
print(identity)
```

**Response**

```python
BusinessIdentity(
    id="5740539971076096",
    tax_id="20.018.183/0001-80",
    name="STARK BANK S.A.",
    tax_id_status="active",
    insight_tax_id="",
    insight_document_type="",
    representatives=[{"name": "Daenerys Targaryen Stormborn", "qualification": "Diretor", "taxId": "012.345.678-90"}],
    attachments=[],
    rules=[],
    tags=["onboarding-123"],
    status="canceled",
    created="2026-06-12T12:00:00+00:00",
    updated="2026-06-12T12:03:00+00:00"
)
```

## Business Attachment Overview

A Business Attachment is a single corporate document (articles of incorporation, most recent amendment, board minute) tied to a Business Identity.

Up to two attachments per identity. Each attachment is registered with `status=created` and must be in place before the identity can be sent to processing.

### The Business Attachment Object

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| `id` | STRING | Unique id for the Business Attachment. |
| `name` | STRING | File name. Unique among the identity's other `created` attachments. |
| `businessIdentityId` | STRING | Id of the Business Identity that owns this attachment. |
| `attachmentId` | STRING | Id of the underlying document on Stark Infra's Attachment microservice — used internally to fetch and validate the file. |
| `content` | STRING | Base64-encoded file payload. Returned only when the attachment is fetched with `expand=content`. |
| `tags` | LIST OF STRINGS | Free-form tags used to label the attachment for future queries. |
| `status` | STRING | Current lifecycle status of the Business Attachment. Options: `created`, `approved`, `denied`, `canceled`. |
| `created` | STRING | Creation datetime. Example: "2026-06-12T12:02:00.000000+00:00". |
| `updated` | STRING | Last update datetime. Example: "2026-06-12T12:02:00.000000+00:00". |

### Upload a Business Attachment

`POST /v2/business-attachment`

Send a single corporate document tied to a previously created Business Identity. The `identityId` must match the `id` returned by `POST /v2/business-identity`.

**Validations:** only PDF, JPG and PNG are accepted, ≤ 8 MB per attachment; the `name` must be unique among the identity's other `created` attachments; the identity must be in `pending` status and have fewer than 2 attachments.

**Parameters**

| Name | Required | Description |
| --- | --- | --- |
| `name` | REQUIRED | File name. Must be unique among the identity's other `created` attachments. |
| `content` | REQUIRED | File payload encoded as a base64 string (e.g., `data:application/pdf;base64,...`). |
| `identityId` | REQUIRED | Id of the Business Identity that owns the attachment. Use the `id` returned by `POST /v2/business-identity`. |
| `tags` | OPTIONAL | List of free-form tags. |

**Request**

```python
import starkinfra

attachments = starkinfra.businessattachment.create([
    starkinfra.BusinessAttachment(
        name="contrato-social.pdf",
        content="data:application/pdf;base64,JVBERi0xLjQK...",
        business_identity_id="5740539971076096",
        tags=["doc-principal"]
    )
])

for attachment in attachments:
    print(attachment)
```

**Response**

```python
[
    BusinessAttachment(
        id="6710539971076096",
        name="contrato-social.pdf",
        business_identity_id="5740539971076096",
        attachment_id="5104320788332544",
        tags=["doc-principal"],
        status="created",
        created="2026-06-12T12:02:00+00:00",
        updated="2026-06-12T12:02:00+00:00"
    )
]
```

### Deleting a Business Attachment

`DELETE /v2/business-attachment/:id`

Cancel a Business Attachment that was uploaded by mistake or that you no longer want to send to the AI Model. The attachment transitions to `canceled` and stops counting toward the identity's two-attachment limit, so you can upload a replacement in its place.

**Validations:** the attachment must exist and belong to your workspace; its current `status` must be `created` — attachments already `approved`, `denied` or `canceled` cannot be canceled again.

Canceling an attachment does **not** cancel the parent Business Identity. If the identity has not yet been sent to processing and you want to discard it as well, call `DELETE /v2/business-identity/:id` separately.

**Parameters**

| Name | Required | Description |
| --- | --- | --- |
| `id` | REQUIRED | Id returned by `POST /v2/business-attachment`. Sent as the path parameter. |
| `fields` | OPTIONAL | Comma-separated list of response keys to return. |

**Request**

```python
import starkinfra

attachment = starkinfra.businessattachment.cancel("6710539971076096")
print(attachment)
```

**Response**

```python
BusinessAttachment(
    id="6710539971076096",
    name="contrato-social.pdf",
    business_identity_id="5740539971076096",
    attachment_id="5104320788332544",
    tags=["doc-principal"],
    status="canceled",
    created="2026-06-12T12:02:00+00:00",
    updated="2026-06-12T12:03:30+00:00"
)
```
