Uploads a PDF and opens a draft ready for recipients and fields.
Uploads a PDF and opens a draft ready for recipients and fields.
POST /v1/esign/documents
| Address | https://api.hanzo.ai/v1/esign/documents |
| Method | POST |
| Operation | post_esign_documents |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Uploads a PDF and opens a draft ready for recipients and fields.
It answers 201 with the document in DRAFT — the state where recipients and fields may still be added, and the only state they may. The bytes go to object storage rather than into the tenant database, and the original is kept under its own key so it survives sealing untouched: a completed document can always be compared against what was uploaded. Creation is recorded on the audit trail.
This is the sender's door: a validated principal is required, and the document lands in that principal's OWN org. Isolation is physical rather than a filter — each tenant has its own store — so another org's document id is simply not there. A body over 32 MiB is refused with 413.
Request
6 fields, body application/json (required).
| Field | In | Type | Required | Description |
|---|---|---|---|---|
externalId | body | any | — | ExternalID is your own identifier for this document, stored and echoed back so a document here can be matched to a record in your system. |
message | body | any | — | Message is the covering message carried with the document. |
pdfBase64 | body | any | — | PdfBase64 is the document itself, base64-encoded; a data: URL prefix is accepted and stripped. Required. |
signingOrder | body | any | — | SigningOrder chooses PARALLEL — the default, where everyone may sign at once — or SEQUENTIAL, where each signer waits for the ones ahead of them. |
subject | body | any | — | Subject is the covering subject line carried with the document. |
title | body | any | — | Title is the document's name, shown to every recipient and used to build the download filename. |
Response
| Status | Body | Meaning |
|---|---|---|
201 | esignDocument | created |
201 body — 34 fields.
| Field | In | Type | Always | Description |
|---|---|---|---|---|
completedAt | body | integer | — | CompletedAt is when the document sealed, in unix milliseconds; null until it does. |
createdAt | body | integer | — | CreatedAt is when the document was uploaded, in unix milliseconds. |
externalId | body | string | — | ExternalID is the caller's own identifier for this document, echoed back as it was given; null when none was. |
fields | body | esignField[] | — | Fields is every field on the document, ordered by page and then by when it was placed. |
fields[].customText | body | string | — | CustomText is the value a non-signature field was filled with, empty until it is. |
fields[].fieldMeta | body | any | — | FieldMeta is the caller's own metadata for this field, stored verbatim at placement and never interpreted. |
fields[].height | body | number | — | Height is the field's height, -1 when the renderer is to choose one. |
fields[].id | body | string | — | ID is the field id. |
fields[].inserted | body | boolean | — | Inserted is whether this field has been filled in. |
fields[].page | body | number | — | Page is the 1-based page the field sits on. |
fields[].positionX | body | number | — | PositionX is the field's horizontal position on that page. |
fields[].positionY | body | number | — | PositionY is the field's vertical position on that page. |
fields[].recipientId | body | string | — | RecipientID is who must fill this field. |
fields[].type | body | string | — | Type is what the field collects — SIGNATURE, DATE, NAME, EMAIL, TEXT and the rest. |
fields[].width | body | number | — | Width is the field's width, -1 when the renderer is to choose one. |
id | body | string | — | ID is the document id. |
message | body | string | — | Message is the covering message stored with the document; null when none was given. |
recipients | body | esignRecipient[] | — | Recipients is everyone on the document, ordered by signing order and then by when they were added — which is also the order a SEQUENTIAL document enforces. |
recipients[].email | body | string | — | Email is where this recipient's signing link is meant to go, lower-cased. |
recipients[].id | body | string | — | ID is the recipient id, which is what a field is placed against. |
recipients[].name | body | string | — | Name is the recipient's display name, empty when none was given. |
recipients[].readStatus | body | string | — | ReadStatus is NOT_OPENED until they first open their link, then OPENED. |
recipients[].rejectionReason | body | string | — | RejectionReason is why they declined, null unless they did. |
recipients[].role | body | string | — | Role is SIGNER, CC, VIEWER, APPROVER or ASSISTANT. |
recipients[].sendStatus | body | string | — | SendStatus is NOT_SENT until the document goes out, then SENT. |
recipients[].signedAt | body | integer | — | SignedAt is when they finished or declined, in unix milliseconds; null while neither has happened. |
recipients[].signingOrder | body | number | — | SigningOrder is their position in a SEQUENTIAL document, null when they were added without one. |
recipients[].signingStatus | body | string | — | SigningStatus is NOT_SIGNED, SIGNED or REJECTED. |
signingOrder | body | string | — | SigningOrder is PARALLEL or SEQUENTIAL, fixed when the document was created. |
source | body | string | — | Source is how the document came to exist. |
status | body | string | — | Status is DRAFT while recipients and fields may still be added, PENDING once it has gone out, then COMPLETED when every signer has finished or REJECTED if any… |
subject | body | string | — | Subject is the covering subject line stored with the document; null when none was given. |
title | body | string | — | Title is the document's name, and the stem of the download filename. |
updatedAt | body | integer | — | UpdatedAt is when the document last changed, in unix milliseconds. |
Failure carries the platform error shape — see Errors.
Examples
hanzo esign documents createimport { Configuration, EsignApi } from 'hanzoai';
const api = new EsignApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.postEsignDocuments({ externalId: "<externalId>", message: "<message>" });from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import EsignApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = EsignApi(client).post_esign_documents(external_id="<externalId>", message="<message>")cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.EsignAPI.PostEsignDocuments(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::apis::{configuration::Configuration, esign_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = esign_api::post_esign_documents(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.EsignApi;
ApiClient client = new ApiClient();
client.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new EsignApi(client).postEsignDocuments();curl -X POST https://api.hanzo.ai/v1/esign/documents \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "<externalId>",
"message": "<message>"
}'The door reaches esign through the esign tool, which names its 9 operations with its own verbs — this one among them, under a name only the door declares. describe explains any of them:
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe",
"arguments": {
"op": "list_esign_documents"
}
}
}'How is this guide?