04 · SENDING

Send a document from your own file

Upload a PDF, place signature fields by coordinate, add recipients, and send. The path that depends on nothing but a key.

All documentation

Use this path when the document is generated per customer - an invoice, a quote, a contract your own system renders. It depends on nothing but a valid key, so it works on a brand new workspace. If the paperwork is the same every time, the template path is less work.

Upload the file

POST /files is multipart, not JSON. The declared content type is ignored - the file is identified from its actual bytes, so renaming a .txt to .pdf will not get it past the door.

bash
curl https://api.documentesign.com/v1/files \
  -H "Authorization: Bearer $ESIGN_API_KEY" \
  -F "file=@contract.pdf"
Accepted files
FieldTypeNotes
TypesPDF, DOCX, DOC, PNG, JPEG. Anything else returns 415 unsupported_file_type.
Size25 MBLarger uploads return 413 file_too_large.
Per document10 filesThey are merged in the order you list them.

PDFs are parsed on upload, so a password-protected file fails fast with 400 password_protected_pdf and an unreadable one with 400 corrupt_pdf. Remove the password before uploading - there is no way to supply one.

Create the document

Now build a document around the file id. This single call carries recipients, fields and routing, and can send in the same request.

bash
curl https://api.documentesign.com/v1/documents \
  -H "Authorization: Bearer $ESIGN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Services agreement - Acme",
    "files": ["file_9f2c..."],
    "routing": "sequential",
    "recipients": [
      { "name": "Sam Rivera", "email": "sam@example.com", "type": "signer" },
      { "name": "Dana Poole", "email": "dana@example.com", "type": "cc" }
    ],
    "fields": [
      { "type": "SIGNATURE", "pageNumber": 3,
        "xNorm": 0.1, "yNorm": 0.72, "widthNorm": 0.28, "heightNorm": 0.06,
        "recipientId": null }
    ],
    "send": true
  }'
Body
FieldTypeNotes
filesstring[]required1 to 10 file ids, merged in this order.
recipientsobject[]required1 to 10, signers and CCs combined.
titlestringUp to 160 chars. Defaults to "Untitled document".
routing"parallel" | "sequential"Defaults to parallel - everyone is asked at once. Sequential asks them in list order.
fieldsobject[]Up to 500. See field placement below.
sendbooleanDefaults to false here, so the document stays a draft unless you ask. Note this is the opposite default to the template endpoint.

Recipients

Each needs a name and email. Set type to cc for someone who should receive the finished document without signing it. A CC cannot be given fields.

Optional per recipient: phone, and pin for an access code the signer must enter before they can open the document.

Placing fields

Coordinates are normalised to the page, not measured in points, so they survive a page-size change. 0,0 is the top left corner and 1,1 the bottom right.

Field
FieldTypeNotes
typeenumrequiredSIGNATURE, INITIALS, DATE, TEXT, TEXTAREA, NUMBER, EMAIL, PHONE, NAME, TITLE, COMPANY, CHECKBOX, RADIO_GROUP, DROPDOWN, STAMP.
pageNumberintegerrequired1-based.
xNorm, yNormnumberrequiredTop-left corner, 0 to 1.
widthNorm, heightNormnumberrequired0 to 1. The field must fit on the page - otherwise 400 field_out_of_bounds.
recipientIdstring | nullWho fills it. Omit on create and assign with PUT /fields once recipient ids exist.
requiredbooleanDefaults to true.
fieldKeystringLowercase snake_case handle so you can prefill or read it back by name.
fillMode"SIGNER_FILLED" | "PREFILLED"PREFILLED renders your value as fixed content the signer cannot edit.
Every signer needs a signature field
A send with a signer who has nothing to sign is rejected with 400 signer_missing_signature_field, and the code carries the offending recipient ids after a colon. This is the most common first failure on this path.

Two-step, if you prefer

Creating with send: false gives you a draft whose recipient ids you can read, assign fields against, and then send. Both PUT calls are full replacements rather than patches - send the complete list every time, because anything you leave out is deleted.

bash
# 1. create as a draft, read back recipient ids
# 2. attach fields to those recipients
curl -X PUT https://api.documentesign.com/v1/documents/doc_7a1b.../fields \
  -H "Authorization: Bearer $ESIGN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fields": [ { "type": "SIGNATURE", "pageNumber": 1,
        "xNorm": 0.1, "yNorm": 0.8, "widthNorm": 0.25, "heightNorm": 0.06,
        "recipientId": "rcp_3d9e..." } ] }'

# 3. send it
curl -X POST https://api.documentesign.com/v1/documents/doc_7a1b.../send \
  -H "Authorization: Bearer $ESIGN_API_KEY"

Field ids are not stable across a PUT. The rows are replaced, so do not store them.

After it is out

POST /documents/{id}/remind nudges everyone still outstanding, or one person if you pass recipient_id. It returns how many were reminded, and 409 if that number would be zero. POST /documents/{id}/cancel withdraws a document that is still in progress.

Do not poll for completion in production. Subscribe to webhooks and fetch the signed PDF when document.completed arrives.