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

# Contracts

> Have a verified counterparty sign your contracts with Aadhaar eSign before the verification completes.

A hosted workflow can make signing part of the run. Once you approve a verification on a version that
offers contracts, it waits in `pending-signature` until a contract of each one offered to it is signed,
and completes then. Each signer signs with Aadhaar eSign, on Signzy.

Your contracts are built for you, one contract workflow per contract: its template, who signs it and in
what order, how each signs, and how long its signing links last. A workflow version then offers it, and
says which of the template's values the verification fills in from what it collected and which you set
each time you send one. Talk to us to have a contract workflow built and offered.

## How a verification moves through signing

```mermaid theme={"system"}
stateDiagram-v2
    direction LR
    pending_approval --> pending_signature: approve
    pending_signature --> completed: a contract of each one offered is signed
    pending_signature --> rejected: reject
    pending_signature --> open: reset
```

| What happens | What the verification does |
| - | - |
| You approve it, or it is approved as it finishes | Reads `pending-signature`, unless it already holds a signed contract of each one offered to it, when it completes. |
| You send a contract | Stays `pending-signature`. One contract of each offer can be out at a time. |
| The last contract it waits on is signed | Reads `completed`, with `signed_at` saying when. |
| A contract expires, fails or is withdrawn | Stays `pending-signature`. Send another, or reject the verification. |
| You reject it | Reads `rejected`, and every contract still out is withdrawn. |
| You reset it | Reopens, and every contract still out is withdrawn. The new run is signed on contracts of its own. |
| It expires, or you purge it | Every contract still out is withdrawn, then everything the contracts carried is deleted with the rest. |

The person on the journey is shown it completed once they have submitted; the contracts reach each signer
by SMS or email, as the contract workflow says.

## Which contracts a verification is offered

Read the workflow, or any version of it, and `contracts` lists what its verifications can be signed on:

```json theme={"system"}
{
  "contracts": [
    {
      "contract_workflow": "dealer-agreement",
      "filled": ["dealer_name", "gstin"],
      "open": ["credit_limit", "start_date"],
      "dates": ["start_date"],
      "signers": 1,
      "condition": null
    }
  ]
}
```

`filled` are the template's values the verification fills in from what it collected, and `open` the
ones you set with each send. Every value is required. A verification is sent what the version it runs
offers.

A contract with a `condition` is offered only to the verifications that meet it, in the same terms as a
step's condition: an answer the user gave, how a check concluded, or the context you created the
verification with. A version can offer one agreement per product, each to the verifications created for
that product, or one per legal form, each to the businesses that answer with it. A verification's
`offered_contracts` names the ones offered to it, and it waits on a signed contract of each of those and
no other. Sending one not named there is refused with `409` `contract-not-sendable`.

```json theme={"system"}
{
  "status": "pending-signature",
  "context": {"products": ["gold-loan", "msme-loan"]},
  "offered_contracts": ["gold-loan-agreement", "msme-loan-agreement"]
}
```

Which contracts are offered is settled as the verification is approved, from the run as it stands. A run
reopened before any contract is sent is offered again once it is approved again, so correcting an answer
can change the contract it is signed on.

A verification can be for several things at once, such as its products, in a `choice-list` context field:
a list of one or more of its `options`. The workflow's `context` declares it:

```json theme={"system"}
{
  "context": [
    {
      "key": "products",
      "label": "Products",
      "description": null,
      "type": "choice-list",
      "options": [
        {"value": "gold-loan", "label": "Gold loan"},
        {"value": "msme-loan", "label": "MSME loan"},
        {"value": "vehicle-loan", "label": "Vehicle loan"}
      ],
      "required": true
    }
  ]
}
```

A condition tests such a list with `includes` or `excludes`: one agreement per product, each offered where
`products` includes it, or a general agreement for every verification whose products exclude `gold-loan`.

`dates` are the values in `open` that take a date. Send each as `YYYY-MM-DD`, and the contract prints it
as the day, the month's name and the year: `2026-11-01` is printed `1 November 2026`, and the contract's
`values` read it that way too.

## Previewing and sending

`GET /verifications/{verification_id}/contracts/preview?contract_workflow=dealer-agreement` answers what a
send would carry now: the values the verification fills in, the ones left to you, everyone it would go
to, the contracts of that contract workflow already sent on the run, and every fault that would refuse
a send whatever you set. Its `open` and `dates` read as they do on the workflow. It sends, spends and records nothing.

Send one with every value in `open`, and a note of your own if you want one:

```bash theme={"system"}
curl -X POST https://api.verify.privue.ai/verifications/3f2504e0-4f89-41d3-9a0c-0305e82c3301/contracts \
  -H "Authorization: Bearer $PRIVUE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contract_workflow": "dealer-agreement",
    "values": {"credit_limit": "5,00,000", "start_date": "2026-11-01"},
    "note": "Credit limit as agreed with the regional head"
  }'
```

A send is refused with `422` `contract-refused`, naming every fault, where a value you set is missing,
empty or not yours to set, where a date is not a calendar date given as `YYYY-MM-DD`
(`value 'start_date': a date, as YYYY-MM-DD`), where the verification does not hold a value the offer fills in, or where a
value would print the contract wrongly, such as a name so long that it pushes the signing line onto other
text. Nothing goes to the signers and no credit is used.

Each send uses one of the workflow's contract credits, whether or not it is signed. Read
`contract_credits` on the workflow to see where they stand. A workflow with none left refuses a send with
`402` `contract-credits-exhausted`.

A send whose answer you did not receive can be asked again, and nothing goes out twice. Where the contract
is already out carrying the same values and note, it is answered with `200` rather than `201`, and nothing
is sent, spent or recorded; a send carrying anything else while one is out is refused with `409`
`contract-not-sendable`.

## Following a contract

`GET /verifications/{verification_id}/contracts/{contract_id}` reads one contract: its status, each
signer's progress and attempts, every message each signer was sent and whether it was delivered, the values
it carried and your note. A callback tells you when it reaches
its end, so you rarely need to poll.

| Status | Meaning |
| - | - |
| `pending` | With its signers. |
| `withdrawing` | You asked for it back, by withdrawing it or by rejecting or resetting its verification. It is taken back once no signer is part way through signing, which takes up to about ten minutes after their last act, and can still be signed meanwhile. |
| `signed` | Every signer signed, and the signed document and its audit certificate are held for you. |
| `expired` | Its signing links ran out first. |
| `failed` | A signer ran out of attempts at the Aadhaar checks. |
| `withdrawn` | Taken back, or deleted at Signzy, which reads the same: it is no longer with its signers and was never signed. |

Every status but `pending` and `withdrawing` is final. `POST .../remind` reads the contract afresh and
sends the link again to every signer still to sign, or to the one whose turn it is where they sign in
order; only a `pending` contract is reminded. `POST .../withdraw` takes a contract back. `GET /contracts` lists every contract across your verifications, filtered by
status, contract workflow, workflow, your reference, or verification.

## Downloading the contract

`POST /verifications/{verification_id}/contracts/{contract_id}/download` answers short-lived links to the
contract as it went to its signers, held from the moment it goes, and once it is signed, to the signed
document and Signzy's audit certificate. Each link carries the SHA-256 of its file, and `sent_document` and
`signed_files` on the contract carry the same hashes, so you can check what you downloaded:

```bash theme={"system"}
shasum -a 256 dealer-agreement.pdf
```

Every file is held until the verification expires, and goes with it, so keep what you need before then.

## Callbacks

Each contract reaching its end is posted to the workflow's callback endpoint as `contract.signed`,
`contract.expired`, `contract.failed` or `contract.withdrawn`, and the signature that completes the
verification is followed by `verification.completed`. See [Callbacks](https://docs.privue.ai/verification/callbacks.md).

## Retention

A contract lives and goes with its verification. Sending a contract, withdrawing one and one reaching
its end each restart the verification's retention window; reminding signers and reading do not. Once
the verification expires, or you purge it, its contracts keep what they were and how they ended, and
their values, your notes, their signers and every file of theirs are deleted. A contract still out is
withdrawn first, and a purge while a signer is part way through signing is refused with `409`
`contract-withdrawal-pending`, to be asked again in a few minutes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.