# Quick start: send a document for signature

> Five requests take you from an API key to a document waiting for signatures. Every example below is copied from the API spec, so you can run them as they are and then swap in your own file and people.

Source: https://signwith.co/docs/api/quickstart · Updated 2026-10-02

You need a SignWith account and a terminal. The examples use cURL, Node 20 or later (save the code as a `.mjs` file), and Python with the `requests` package. Pick a language once and every example on the page switches.

## 1. Get an API key

[Get your API key](https://app.signwith.co/settings/api)

In SignWith, open [Settings → Developers](https://app.signwith.co/settings/api) and create a key. Keys starting with `sw_test_` act on your test account, and keys starting with `sw_live_` act on your live account, where signers get real emails. Then put the key in an environment variable, so it stays out of your code:

```bash
export SIGNWITH_API_KEY="sw_live_..."
```

## 2. Check the key works

[`GET /me`](https://signwith.co/docs/api/account#get-the-current-api-keys-user-and-account) returns the user, account and environment the key belongs to. It changes nothing, so it's a safe first call.

cURL:

```bash
curl https://app.signwith.co/api/v1/me \
  -H "Authorization: Bearer $SIGNWITH_API_KEY"
```

Node:

```javascript
const response = await fetch('https://app.signwith.co/api/v1/me', {
  headers: {
    Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
  },
})

console.log(response.status, await response.json())
```

Python:

```python
import os

import requests

response = requests.get(
    "https://app.signwith.co/api/v1/me",
    headers={
        "Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
    },
)

print(response.status_code, response.json())
```

A `200` response with your email in `user.email` means you're ready. A `401` with code `unauthenticated` means the key is missing or wrong; see [authentication](https://signwith.co/docs/api/authentication).

## 3. Upload a document as a template

A template is a reusable document with roles (who signs) and fields (what they fill in). Create one from a PDF that SignWith can download from an `https://` URL. Replace the `file` URL with one of yours, or send the file itself as base64 or a multipart upload (see [create a template](https://signwith.co/docs/api/templates#create-a-template-from-documents)).

cURL:

```bash
curl -X POST https://app.signwith.co/api/v1/templates \
  -H "Authorization: Bearer $SIGNWITH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Residential lease",
  "external_id": "lease-v3",
  "folder": "Leasing",
  "documents": [
    {
      "name": "Lease agreement",
      "file": "https://files.acme.co/templates/lease-v3.pdf"
    }
  ]
}'
```

Node:

```javascript
const response = await fetch('https://app.signwith.co/api/v1/templates', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    name: 'Residential lease',
    external_id: 'lease-v3',
    folder: 'Leasing',
    documents: [
      {
        name: 'Lease agreement',
        file: 'https://files.acme.co/templates/lease-v3.pdf',
      },
    ],
  }),
})

console.log(response.status, await response.json())
```

Python:

```python
import os
import uuid

import requests

response = requests.post(
    "https://app.signwith.co/api/v1/templates",
    headers={
        "Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "name": "Residential lease",
        "external_id": "lease-v3",
        "folder": "Leasing",
        "documents": [
            {
                "name": "Lease agreement",
                "file": "https://files.acme.co/templates/lease-v3.pdf",
            },
        ],
    },
)

print(response.status_code, response.json())
```

The response is the new template. Note its `id`; the examples below use `311`. If the PDF has fillable form fields, they become template fields automatically. Otherwise the template has no fields yet, and it needs at least one before it can be sent.

## 4. Place the signature fields

Send `fields` to [`PATCH /templates/{id}`](https://signwith.co/docs/api/templates#update-a-template) to place a signature box for each role. Each area gives the page and the position as fractions of the page size, measured from the top-left corner. This example puts a Tenant signature and a Landlord signature on page 4, so change `page` if your document is shorter (`422 invalid_field` tells you when a page doesn't exist).

cURL:

```bash
curl -X PATCH https://app.signwith.co/api/v1/templates/311 \
  -H "Authorization: Bearer $SIGNWITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "fields": [
    {
      "name": "Tenant signature",
      "type": "signature",
      "role": "Tenant",
      "areas": [
        {
          "page": 4,
          "x": 0.1,
          "y": 0.8,
          "w": 0.3,
          "h": 0.06
        }
      ]
    },
    {
      "name": "Landlord signature",
      "type": "signature",
      "role": "Landlord",
      "areas": [
        {
          "page": 4,
          "x": 0.55,
          "y": 0.8,
          "w": 0.3,
          "h": 0.06
        }
      ]
    }
  ]
}'
```

Node:

```javascript
const response = await fetch('https://app.signwith.co/api/v1/templates/311', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    fields: [
      {
        name: 'Tenant signature',
        type: 'signature',
        role: 'Tenant',
        areas: [
          {
            page: 4,
            x: 0.1,
            y: 0.8,
            w: 0.3,
            h: 0.06,
          },
        ],
      },
      {
        name: 'Landlord signature',
        type: 'signature',
        role: 'Landlord',
        areas: [
          {
            page: 4,
            x: 0.55,
            y: 0.8,
            w: 0.3,
            h: 0.06,
          },
        ],
      },
    ],
  }),
})

console.log(response.status, await response.json())
```

Python:

```python
import os

import requests

response = requests.patch(
    "https://app.signwith.co/api/v1/templates/311",
    headers={
        "Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
    },
    json={
        "fields": [
            {
                "name": "Tenant signature",
                "type": "signature",
                "role": "Tenant",
                "areas": [
                    {
                        "page": 4,
                        "x": 0.1,
                        "y": 0.8,
                        "w": 0.3,
                        "h": 0.06,
                    },
                ],
            },
            {
                "name": "Landlord signature",
                "type": "signature",
                "role": "Landlord",
                "areas": [
                    {
                        "page": 4,
                        "x": 0.55,
                        "y": 0.8,
                        "w": 0.3,
                        "h": 0.06,
                    },
                ],
            },
        ],
    },
)

print(response.status_code, response.json())
```

Roles named in `fields` are added to the template, so it now has the roles Tenant and Landlord. You can also open the template's `edit_url` and place fields in the SignWith editor instead.

> **Or write the fields into the document:** If you generate the PDF yourself, put text tags where the fields go, such as `{{Tenant signature;type=signature;role=Tenant}}`. SignWith places a field at each tag and hides the tag text when you create the template, so you can skip this step. See [text tags](https://signwith.co/docs/api/text-tags) for the full syntax.

## 5. Send it for signature

[`POST /signature_requests`](https://signwith.co/docs/api/signature-requests#send-a-signature-request) sends the template to one signer per role. This example uses `send_email: false`, so nobody is emailed while you test: the response includes each signer's private `signing_url` for you to open yourself.

cURL:

```bash
curl -X POST https://app.signwith.co/api/v1/signature_requests \
  -H "Authorization: Bearer $SIGNWITH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "template_id": 311,
  "signing_order": "parallel",
  "send_email": false,
  "signers": [
    {
      "role": "Tenant",
      "name": "Grace Hopper",
      "email": "grace@example.com"
    },
    {
      "role": "Landlord",
      "name": "Ada Lovelace",
      "email": "ada@acme.co"
    }
  ]
}'
```

Node:

```javascript
const response = await fetch('https://app.signwith.co/api/v1/signature_requests', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    template_id: 311,
    signing_order: 'parallel',
    send_email: false,
    signers: [
      {
        role: 'Tenant',
        name: 'Grace Hopper',
        email: 'grace@example.com',
      },
      {
        role: 'Landlord',
        name: 'Ada Lovelace',
        email: 'ada@acme.co',
      },
    ],
  }),
})

console.log(response.status, await response.json())
```

Python:

```python
import os
import uuid

import requests

response = requests.post(
    "https://app.signwith.co/api/v1/signature_requests",
    headers={
        "Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "template_id": 311,
        "signing_order": "parallel",
        "send_email": False,
        "signers": [
            {
                "role": "Tenant",
                "name": "Grace Hopper",
                "email": "grace@example.com",
            },
            {
                "role": "Landlord",
                "name": "Ada Lovelace",
                "email": "ada@acme.co",
            },
        ],
    },
)

print(response.status_code, response.json())
```

To have SignWith email the signers instead, leave out `send_email`. Add `signing_order: "sequential"` to invite them one at a time, `message` for your own email text, and `prefill` to fill fields in advance. The [signature requests reference](https://signwith.co/docs/api/signature-requests#send-a-signature-request) has every option.

The request includes an `Idempotency-Key` header, so if the network fails you can retry the same request without sending the document twice. See [idempotency](https://signwith.co/docs/api/requests#idempotency).

## 6. Check the status

Open each `signing_url` and sign. Then fetch the signature request with [`GET /signature_requests/{id}`](https://signwith.co/docs/api/signature-requests#get-a-signature-request):

cURL:

```bash
curl https://app.signwith.co/api/v1/signature_requests/4812 \
  -H "Authorization: Bearer $SIGNWITH_API_KEY"
```

Node:

```javascript
const response = await fetch('https://app.signwith.co/api/v1/signature_requests/4812', {
  headers: {
    Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
  },
})

console.log(response.status, await response.json())
```

Python:

```python
import os

import requests

response = requests.get(
    "https://app.signwith.co/api/v1/signature_requests/4812",
    headers={
        "Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
    },
)

print(response.status_code, response.json())
```

When `status` is `completed`, the response has download links for the signed `documents` and the `audit_trail_url`. One credit covers one signed document, and sending the same template again doesn't use another; see [credits and billing](https://signwith.co/docs/api/credits).

## Next steps

- Get told when people sign instead of polling: [set up webhooks](https://signwith.co/docs/api/webhooks).
- Handle failures by their stable code: [errors](https://signwith.co/docs/api/errors).
- Let an AI assistant send documents for you: [connect Claude, ChatGPT or Cursor](https://signwith.co/docs/mcp).
