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

# Human Approvals

> Request signed approvals, such as forms, documents, or JSON, from your users, delivered by link or embedded iframe, with optional identity verification.

Use `POST /request` to ask one or more people to review and approve something. They can fill a form, sign a document, or accept a block of structured data, and you receive the signed result as a W3C Verifiable Credential. This guide covers everything except agent mandates; for those, see [Agent Governance](https://docs.humanos.tech/essentials/guides/agent-governance.md).

Every approval a user makes is captured as a signed, tamper-evident credential. You don't store the signature or manage the signing UI. Humanos does, and streams you the outcome over [webhooks](https://docs.humanos.tech/essentials/webhooks-intro.md).

## What you can request

Each request carries one or more **credentials**, the things you are asking the subject to approve. Every credential has a `type`:

| Type | The subject… | `data` |
| - | - | - |
| `DOCUMENT` | Signs a PDF | A base64-encoded PDF, or a stored resource |
| `FORM` | Fills a form | Reference a stored form resource, not inline |
| `JSON` | Accepts a block of structured data | Any JSON, the general-purpose type |

The `POLICY` type is for agent mandates and is intentionally out of scope here. It's covered in [Agent Governance](https://docs.humanos.tech/essentials/guides/agent-governance.md).

You supply credentials two ways, and you can mix them in one request:

* **Inline**: put them in `credentials[]` with a `data` field. Best for one-off, dynamic content.
* **From the library**: reference resources built in the dashboard by id with `resourcesIds`, or whole **workflows** by id with `groupIds`. A workflow, also called a group, bundles several resources, so a single request can collect **multiple approvals at once**.

## Making the request

`POST /request`, signed per [Authentication](https://docs.humanos.tech/essentials/authentication.md).

```javascript theme={"dark"}
const res = await signedFetch("/request", {
  method: "POST",
  body: {
    name: "Onboarding: NDA",
    internalId: "signup-4471",
    securityLevel: "CONTACT",
    users: [{ contact: "user@example.com" }],
    credentials: [
      {
        type: "DOCUMENT",
        name: "Mutual NDA",
        data: "JVBERi0xLjQK...", // base64-encoded PDF
        placements: [
          {
            contact: "user@example.com",
            type: "SIGNATURE",
            x: 12,
            y: 80,
            width: 30,
            height: 8,
            pageIndex: 0,
          },
        ],
      },
    ],
  },
});

const request = await res.json();
```

### Request fields

**users** `object[]` *required*

The people who must approve. Up to 20 per request.

**user**

**contact** `string` *required*

Email or phone number. Determines the delivery channel: email if it contains `@`, otherwise SMS.

**secondaryContact** `string`

A second email/phone. When set, the request runs in **2FA mode**: the signing link goes to `contact` and the one-time code goes to `secondaryContact` (sent when the link is first opened).

**docId** `string`

A document ID you expect identity verification to return, used to match the person against your records.

**language** `string` (default: ENG)

OTP/notification language: `ENG`, `PRT`, `SPA`, or `FRA`. Defaults to the phone's country, else `ENG`.

**signerQuality** `string`

Free-text label for the signer's role, max 60 chars, e.g. `Administrator`.

**credentials** `object[]`

Inline credentials to collect. Up to 30. Each has `type`, a required `name`
shown to the user, optional `internalId`, `description`, `data`, and, for
`DOCUMENT`, `placements`.

**resourcesIds** `string[]`

Ids of existing dashboard resources to include as credentials.

**groupIds** `string[]`

Ids of existing workflows, also called groups. Each workflow expands to all
of its resources, so one request collects several approvals.

**securityLevel** `string` (default: CONTACT)

Whether identity verification is required. See [Identity
verification](#identity-verification).

**internalId** `string`

Your own reference for the whole request. Echoed back on the webhook so you
can correlate. Each credential also takes its own `internalId`.

**iframe** `object`

Embed the flow instead of sending a link. See [Embedding and trusted
sessions](#embedding-and-trusted-sessions).

## Signing placements

Only applicable to a `DOCUMENT` credential, `placements[]` positions signature and field boxes on the PDF pages. These placements allow users to fill information, draw, sign, select dates, auto-place identity fields, and more.

Upload your PDF, place the boxes, and copy the generated API payload in the [request builder](https://app.humanos.tech/playground/requests).

**placements\[]** `object`

**placement**

**contact** `string` *required*

Which subject this box belongs to; it must match a `users[].contact`. Inline
documents can carry placements for several contacts; a library document
with placements supports a **single** contact.

**type** `string` *required*

`SIGNATURE`, `INITIALS`, `IDENTITY_FIELD`, `OPEN_TEXT`, `NUMERIC`, `DATE`,
or `CHECKBOX`.

**identityType** `string | null`

For `IDENTITY_FIELD` boxes, which identity value to stamp: `FULL_NAME`,
`BIRTH`, `DOC_ID`, `COUNTRY_ALPHA3`, `HEALTH_NUMBER`, `TAX_NUMBER`,
`SOCIAL_SECURITY_NUMBER`, `EMAIL`, `PHONE`, or `SIGNATURE_DATE`.

**x, y, width, height** `number` *required*

Position and size as a percentage of the page, from 0 to 100.

**pageIndex** `number` *required*

Zero-based page the box appears on.

## How the user receives it

The subject gets a **one-time code** and a **signing link**. The channel is chosen per contact: email if the contact contains `@`, otherwise SMS. There are three delivery modes:

> [tab] Standard
>
> The code and the link both go to the subject's primary `contact`. They open
> the link, enter the code, and approve.

> [tab] 2FA
>
> Set `users[].secondaryContact` to split delivery: the **link** goes to
> `contact`, the **code** goes to `secondaryContact`. The subject needs both
> to proceed.

> [tab] Iframe
>
> In iframe mode the code is sent to `contact` and the link is returned in the
> API response for you to embed. See [Embedding and trusted
> sessions](#embedding-and-trusted-sessions).

Need to send a fresh code? Call `PATCH /request/resend/{requestId}` with exactly one selector query param: `contact`, `id`, or `internalId`. Resends must be at least 30 seconds apart, and are refused once the subject has entered a wrong code 10 times.

## Embedding and trusted sessions

Instead of sending a link, embed the approval flow directly in your product with `iframe.pubKey`, a P-256 public key, and receive the result over an encrypted `postMessage`. Set `iframe.cookie.allow` to opt into a **trusted session** so a returning subject can skip the OTP on later approvals; the lifetime, `iframe.cookie.duration`, is in seconds, from 3600 (1 hour) to 86400 (24 hours). Full setup, including key generation, embedding, and decryption, is in the [Iframe Integration](https://docs.humanos.tech/essentials/iframe-integration.md) guide.

## Identity verification

`securityLevel` controls whether the subject must verify their identity before approving:

| Level | Identity check |
| - | - |
| `CONTACT` *(default)* | None; the subject only proves control of the contact |
| `ORGANIZATION_KYC` | The subject must already have an identity, one your organization supplied with `POST /user` or a Humanos-verified one; otherwise they complete KYC |
| `HUMANOS_KYC` | Full Humanos identity verification |
| `HUMANOS_REVALIDATION` | Always runs a fresh KYC; the only level that allows a request with zero credentials |

When a KYC level is set and the subject doesn't already meet it, they complete verification first; if it fails, no credential is issued. The KYC outcome arrives as a separate [`identity` webhook event](https://docs.humanos.tech/essentials/webhooks-intro).

## One request, many approvals

Combine `credentials`, `resourcesIds`, and `groupIds` in a single request and each subject in `users[]` receives all of them to approve together. Workflows, referenced by `groupIds`, are the easiest way to bundle a fixed set of approvals you reuse across requests.

## Getting the result

You don't poll. When a subject decides, Humanos sends a **`credential` webhook event** with the signed credential and a `decision.action` of `accept` or `reject`; KYC results arrive as an **`identity` event**. Match them back to your system with the `internalId` you set on the request or per credential.

See the [Webhooks](https://docs.humanos.tech/essentials/webhooks-intro.md) guide for the event payloads and how to verify and decrypt them.


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