Templates API reference
Reusable documents with fields and roles, used to create signature requests.
- get/templatesList templates
- post/templatesCreate a template from documents
- get/templates/{id}Get a template
- patch/templates/{id}Update a template
- post/templates/{id}/duplicateDuplicate a template
- post/templates/{id}/archiveArchive a template
List templates
/api/v1/templates- Read-only keys can call this
Lists the templates the key's user can access, newest first. Use it to let users pick a
template in your app, or to look up a template by your own external_id or by folder.
Archived templates are excluded unless archived=true. List items omit fields and
documents; fetch a single template to get them.
Query parameters
limitintegerNumber of items per page, 1–100. Values outside that range fall back to 20 (below 1) or 100 (above 100).
Default201 to 100cursorintegerThe
next_cursorfrom the previous page. Omit it for the first page.qstringCase-insensitive search on the template name.
archivedbooleantrueto list only archived templates instead of active ones.Defaultfalseexternal_idstringOnly templates with this
external_id.folderstringOnly templates in the folder with this exact name.
Request sample
curl https://app.signwith.co/api/v1/templates \
-H "Authorization: Bearer $SIGNWITH_API_KEY"const response = await fetch('https://app.signwith.co/api/v1/templates', {
headers: {
Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
},
})
console.log(response.status, await response.json())import os
import requests
response = requests.get(
"https://app.signwith.co/api/v1/templates",
headers={
"Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
},
)
print(response.status_code, response.json())Response 200
A page of templates. Each item in data is a template object (list version).
{
"object": "list",
"data": [
{
"object": "template",
"id": 311,
"name": "Residential lease",
"external_id": "lease-v3",
"folder": "Leasing",
"roles": [
"Tenant",
"Landlord"
],
"edit_url": "https://app.signwith.co/templates/311/edit",
"created_by": {
"id": 42,
"email": "[email protected]",
"name": "Ada Lovelace"
},
"archived_at": null,
"created_at": "2026-09-20T09:12:44Z",
"updated_at": "2026-09-21T16:03:10Z"
}
],
"has_more": true,
"next_cursor": 311
}Errors
| 401 | The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired) |
| 403 | The key is read-only, or its user can't access this resource. (read_only_api_key, forbidden) |
| 429 | Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback
messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for
POST /signature_requests/{id}/remind — signers were reminded less than an hour ago
(remind_too_soon, with Retry-After). (rate_limited, remind_too_soon) |
| 500 | Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error) |
Codes listed are the examples the spec gives; see all error codes.
Create a template from documents
/api/v1/templates- Needs a full-access key
- Accepts Idempotency-Key
Uploads one or more documents (PDF or image, up to 10 per template, 25 MB each) and creates a template from them. Fillable PDF form fields are converted into template fields automatically.
Text tags. Write fields into the document itself and they're placed where the tag is, with
the tag text hidden: {{Client signature;type=signature;role=Client}}. The first part is the
field name, followed by key=value settings: type (any field type; default text), role
(created on the template if new), required (default true), options (comma-separated, for
select) and width/height in points. {{signature;role=Client}} on its own is a signature
field named "Signature". The same tag on several pages becomes one field (e.g. initials on every
page). When every tag names a role, the template gets exactly those roles. Tags work in PDFs from
any editor; keep each one on a line of its own text, or colour it white, for the cleanest result.
To place signature and other fields yourself, pass fields (JSON requests): each field has a
type, a role and one or more areas giving its page and position as fractions of the page
size. Roles named in fields are created on the template. Otherwise, open the returned
edit_url and place fields in the SignWith editor — a template needs at least one field before
it can be sent.
Use it to sync documents generated by your system into SignWith. Two request formats are supported:
application/json—documentsis an array of{ name, file }wherefileis base64 content (adata:URI prefix is allowed) or anhttps://URL SignWith downloads.multipart/form-data— upload files directly asdocuments[].
If name is omitted, the first document's filename is used. If folder doesn't exist it is
created. Invalid fields return 422 invalid_field. Triggers the template.created webhook.
Headers
Idempotency-KeystringA unique value (e.g. a UUID) that makes retries of this request safe. A successful response is stored for 24 hours and replayed for retries with the same key.
at most 255 characters
Request bodyJSON or multipart/form-data
namestringTemplate name. Defaults to the first document's name.
external_idstringYour own ID for the template.
folderstringFolder name. Created if it doesn't exist.
passwordstringPassword for encrypted PDFs.
documentsarray of objectsRequired1 to 10 items2 child fields
namestringDocument name. Defaults to
Document <n>.filestringRequiredBase64-encoded PDF or image (a
data:URI prefix is allowed), or anhttps://URL to download it from. Max 25 MB.
fieldsarray of objectsFields to place on the documents, in addition to any fillable PDF form fields. Roles named here are added to the template (at most 10 roles).
6 child fields
namestringField name, used as the key in
prefill. Defaults to e.g.Signature 2.typestringRequiredOne oftext,signature,initials,date,checkbox,number,phone,select,radio,multiple,image,file,stamprolestringRole that fills this field. Created on the template if it doesn't exist. Defaults to the template's first role.
requiredbooleanDefaulttrueoptionsarray of stringsChoices for
select,radioandmultiplefields (required for those types).areasarray of objectsRequiredWhere the field goes. A field can appear in several places.
at least 1 item6 child fields
pageintegerRequired1-based page number; must exist in the document.
at least 1xnumberRequired0 to 1ynumberRequired0 to 1wnumberRequiredat most 1, greater than 0hnumberRequiredat most 1, greater than 0documentinteger0-based index of the document within the template.
Default0at least 0
As multipart/form-data, send the same text fields plus the files:
documents[]filesRequiredPDF or image files, 25 MB max each.
1 to 10 items
Request sample
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"
}
]
}'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())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())Example: Base64 document with password
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": "NDA",
"documents": [
{
"name": "nda.pdf",
"file": "JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoK..."
}
],
"password": "s3cret"
}'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: 'NDA',
documents: [
{
name: 'nda.pdf',
file: 'JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoK...',
},
],
password: 's3cret',
}),
})
console.log(response.status, await response.json())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": "NDA",
"documents": [
{
"name": "nda.pdf",
"file": "JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoK...",
},
],
"password": "s3cret",
},
)
print(response.status_code, response.json())Example: Document with signature fields placed by the API
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": "Consulting agreement",
"documents": [
{
"name": "Consulting agreement",
"file": "https://files.acme.co/contracts/consulting.pdf"
}
],
"fields": [
{
"name": "Client name",
"type": "text",
"role": "Client",
"areas": [
{
"page": 1,
"x": 0.12,
"y": 0.18,
"w": 0.35,
"h": 0.03
}
]
},
{
"name": "Client signature",
"type": "signature",
"role": "Client",
"areas": [
{
"page": 3,
"x": 0.1,
"y": 0.78,
"w": 0.3,
"h": 0.06
}
]
},
{
"name": "Consultant signature",
"type": "signature",
"role": "Consultant",
"areas": [
{
"page": 3,
"x": 0.55,
"y": 0.78,
"w": 0.3,
"h": 0.06
}
]
},
{
"name": "Payment terms",
"type": "select",
"role": "Client",
"required": false,
"options": [
"Net 15",
"Net 30"
],
"areas": [
{
"page": 2,
"x": 0.12,
"y": 0.42,
"w": 0.2,
"h": 0.03
}
]
}
]
}'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: 'Consulting agreement',
documents: [
{
name: 'Consulting agreement',
file: 'https://files.acme.co/contracts/consulting.pdf',
},
],
fields: [
{
name: 'Client name',
type: 'text',
role: 'Client',
areas: [
{
page: 1,
x: 0.12,
y: 0.18,
w: 0.35,
h: 0.03,
},
],
},
{
name: 'Client signature',
type: 'signature',
role: 'Client',
areas: [
{
page: 3,
x: 0.1,
y: 0.78,
w: 0.3,
h: 0.06,
},
],
},
{
name: 'Consultant signature',
type: 'signature',
role: 'Consultant',
areas: [
{
page: 3,
x: 0.55,
y: 0.78,
w: 0.3,
h: 0.06,
},
],
},
{
name: 'Payment terms',
type: 'select',
role: 'Client',
required: false,
options: ['Net 15', 'Net 30'],
areas: [
{
page: 2,
x: 0.12,
y: 0.42,
w: 0.2,
h: 0.03,
},
],
},
],
}),
})
console.log(response.status, await response.json())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": "Consulting agreement",
"documents": [
{
"name": "Consulting agreement",
"file": "https://files.acme.co/contracts/consulting.pdf",
},
],
"fields": [
{
"name": "Client name",
"type": "text",
"role": "Client",
"areas": [
{
"page": 1,
"x": 0.12,
"y": 0.18,
"w": 0.35,
"h": 0.03,
},
],
},
{
"name": "Client signature",
"type": "signature",
"role": "Client",
"areas": [
{
"page": 3,
"x": 0.1,
"y": 0.78,
"w": 0.3,
"h": 0.06,
},
],
},
{
"name": "Consultant signature",
"type": "signature",
"role": "Consultant",
"areas": [
{
"page": 3,
"x": 0.55,
"y": 0.78,
"w": 0.3,
"h": 0.06,
},
],
},
{
"name": "Payment terms",
"type": "select",
"role": "Client",
"required": False,
"options": ["Net 15", "Net 30"],
"areas": [
{
"page": 2,
"x": 0.12,
"y": 0.42,
"w": 0.2,
"h": 0.03,
},
],
},
],
},
)
print(response.status_code, response.json())Example: upload files with multipart/form-data
curl -X POST https://app.signwith.co/api/v1/templates \
-H "Authorization: Bearer $SIGNWITH_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F 'name=Residential lease' \
-F 'folder=Leasing' \
-F 'documents[][email protected]' \
-F 'documents[][email protected]'import { openAsBlob } from 'node:fs'
const form = new FormData()
form.append('name', 'Residential lease')
form.append('folder', 'Leasing')
form.append('documents[]', await openAsBlob('lease.pdf'), 'lease.pdf')
form.append('documents[]', await openAsBlob('addendum.pdf'), 'addendum.pdf')
const response = await fetch('https://app.signwith.co/api/v1/templates', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
body: form,
})
console.log(response.status, await response.json())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()),
},
data={
"name": "Residential lease",
"folder": "Leasing",
},
files=[
("documents[]", open("lease.pdf", "rb")),
("documents[]", open("addendum.pdf", "rb")),
],
)
print(response.status_code, response.json())Response 201
The template was created. Returns a template object.
Example response: see the example object below.
Errors
| 400 | The request body is not valid JSON. (invalid_json) |
| 401 | The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired) |
| 403 | The key is read-only, or its user can't access this resource. (read_only_api_key, forbidden) |
| 409 | A request with the same Idempotency-Key is still being processed. Retry shortly. (idempotency_key_in_use) |
| 422 | The request is valid JSON but can't be processed. See error.code; the codes each endpoint
can return are described in the endpoint's description and in the error table above. (missing_parameter, invalid_parameter, invalid_request, missing_documents, too_many_documents, document_too_large, invalid_document, invalid_file_type, pdf_encrypted, missing_signers, invalid_signer, already_completed, not_open, nothing_to_remind, invalid_field, template_has_no_fields, template_archived, signer_finished, idempotency_key_reused, invalid_discount_code, already_purchased, billing_details_required) |
| 429 | Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback
messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for
POST /signature_requests/{id}/remind — signers were reminded less than an hour ago
(remind_too_soon, with Retry-After). (rate_limited, remind_too_soon) |
| 500 | Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error) |
Codes listed are the examples the spec gives; see all error codes.
Get a template
/api/v1/templates/{id}- Read-only keys can call this
Returns a template with its roles, fields and documents. Use it to find out which roles to assign signers to and which field names you can prefill before creating a signature request.
Path parameters
idintegerRequiredTemplate ID.
Request sample
curl https://app.signwith.co/api/v1/templates/311 \
-H "Authorization: Bearer $SIGNWITH_API_KEY"const response = await fetch('https://app.signwith.co/api/v1/templates/311', {
headers: {
Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
},
})
console.log(response.status, await response.json())import os
import requests
response = requests.get(
"https://app.signwith.co/api/v1/templates/311",
headers={
"Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
},
)
print(response.status_code, response.json())Errors
| 401 | The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired) |
| 403 | The key is read-only, or its user can't access this resource. (read_only_api_key, forbidden) |
| 404 | The resource doesn't exist or belongs to another account. (not_found) |
| 429 | Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback
messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for
POST /signature_requests/{id}/remind — signers were reminded less than an hour ago
(remind_too_soon, with Retry-After). (rate_limited, remind_too_soon) |
| 500 | Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error) |
Codes listed are the examples the spec gives; see all error codes.
Update a template
/api/v1/templates/{id}- Needs a full-access key
Renames a template, changes its external_id or folder, renames its roles, or replaces its
fields. Use it to keep template metadata in sync with your system, or to place fields on a
template created without them. Only the parameters you send are changed.
roles is matched by position: the first name renames the first role, and so on. Extra names
add new roles (without fields). Roles can't be removed through the API.
fields, when sent, replaces all of the template's fields (send [] to remove them all).
Roles named in fields that don't exist yet are added. Invalid fields return
422 invalid_field.
Changes only affect signature requests sent afterwards: requests already sent keep the fields
and documents they were sent with. PUT is accepted as an alias. Triggers the
template.updated webhook.
Path parameters
idintegerRequiredTemplate ID.
Request body
namestringexternal_idstring or nullYour own ID for the template. Send
nullto clear it.folderstringFolder name. Created if it doesn't exist.
rolesarray of stringsNew role names, matched by position. Extra names add roles.
fieldsarray of objectsReplaces all of the template's fields. Roles named here are added if missing.
6 child fields
namestringField name, used as the key in
prefill. Defaults to e.g.Signature 2.typestringRequiredOne oftext,signature,initials,date,checkbox,number,phone,select,radio,multiple,image,file,stamprolestringRole that fills this field. Created on the template if it doesn't exist. Defaults to the template's first role.
requiredbooleanDefaulttrueoptionsarray of stringsChoices for
select,radioandmultiplefields (required for those types).areasarray of objectsRequiredWhere the field goes. A field can appear in several places.
at least 1 item6 child fields
pageintegerRequired1-based page number; must exist in the document.
at least 1xnumberRequired0 to 1ynumberRequired0 to 1wnumberRequiredat most 1, greater than 0hnumberRequiredat most 1, greater than 0documentinteger0-based index of the document within the template.
Default0at least 0
Request sample
curl -X PATCH https://app.signwith.co/api/v1/templates/311 \
-H "Authorization: Bearer $SIGNWITH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Residential lease (2027)",
"external_id": "lease-v4",
"folder": "Leasing",
"roles": [
"Tenant",
"Landlord"
]
}'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({
name: 'Residential lease (2027)',
external_id: 'lease-v4',
folder: 'Leasing',
roles: ['Tenant', 'Landlord'],
}),
})
console.log(response.status, await response.json())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={
"name": "Residential lease (2027)",
"external_id": "lease-v4",
"folder": "Leasing",
"roles": ["Tenant", "Landlord"],
},
)
print(response.status_code, response.json())Example: Replace all fields
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
}
]
}
]
}'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())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())Response 200
The updated template. Returns a template object.
Example response: see the example object below.
Errors
| 400 | The request body is not valid JSON. (invalid_json) |
| 401 | The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired) |
| 403 | The key is read-only, or its user can't access this resource. (read_only_api_key, forbidden) |
| 404 | The resource doesn't exist or belongs to another account. (not_found) |
| 422 | The request is valid JSON but can't be processed. See error.code; the codes each endpoint
can return are described in the endpoint's description and in the error table above. (missing_parameter, invalid_parameter, invalid_request, missing_documents, too_many_documents, document_too_large, invalid_document, invalid_file_type, pdf_encrypted, missing_signers, invalid_signer, already_completed, not_open, nothing_to_remind, invalid_field, template_has_no_fields, template_archived, signer_finished, idempotency_key_reused, invalid_discount_code, already_purchased, billing_details_required) |
| 429 | Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback
messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for
POST /signature_requests/{id}/remind — signers were reminded less than an hour ago
(remind_too_soon, with Retry-After). (rate_limited, remind_too_soon) |
| 500 | Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error) |
Codes listed are the examples the spec gives; see all error codes.
Duplicate a template
/api/v1/templates/{id}/duplicate- Needs a full-access key
- Accepts Idempotency-Key
Creates a copy of a template, including its documents, fields and roles. Use it to create a
variant (for example a per-customer version) without re-uploading and re-placing fields.
Optionally give the copy a new name, external_id or folder. Triggers the
template.created webhook.
Path parameters
idintegerRequiredTemplate ID.
Headers
Idempotency-KeystringA unique value (e.g. a UUID) that makes retries of this request safe. A successful response is stored for 24 hours and replayed for retries with the same key.
at most 255 characters
Request body (optional)
namestringName of the copy. Defaults to the original name followed by "(Clone)".
external_idstringfolderstringFolder for the copy. Created if it doesn't exist.
Request sample
curl -X POST https://app.signwith.co/api/v1/templates/311/duplicate \
-H "Authorization: Bearer $SIGNWITH_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Residential lease — Riverside Apartments",
"external_id": "lease-v3-riverside",
"folder": "Leasing"
}'const response = await fetch('https://app.signwith.co/api/v1/templates/311/duplicate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
name: 'Residential lease — Riverside Apartments',
external_id: 'lease-v3-riverside',
folder: 'Leasing',
}),
})
console.log(response.status, await response.json())import os
import uuid
import requests
response = requests.post(
"https://app.signwith.co/api/v1/templates/311/duplicate",
headers={
"Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Residential lease — Riverside Apartments",
"external_id": "lease-v3-riverside",
"folder": "Leasing",
},
)
print(response.status_code, response.json())Response 201
The new template. Returns a template object.
Example response: see the example object below.
Errors
| 400 | The request body is not valid JSON. (invalid_json) |
| 401 | The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired) |
| 403 | The key is read-only, or its user can't access this resource. (read_only_api_key, forbidden) |
| 404 | The resource doesn't exist or belongs to another account. (not_found) |
| 409 | A request with the same Idempotency-Key is still being processed. Retry shortly. (idempotency_key_in_use) |
| 422 | The request is valid JSON but can't be processed. See error.code; the codes each endpoint
can return are described in the endpoint's description and in the error table above. (missing_parameter, invalid_parameter, invalid_request, missing_documents, too_many_documents, document_too_large, invalid_document, invalid_file_type, pdf_encrypted, missing_signers, invalid_signer, already_completed, not_open, nothing_to_remind, invalid_field, template_has_no_fields, template_archived, signer_finished, idempotency_key_reused, invalid_discount_code, already_purchased, billing_details_required) |
| 429 | Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback
messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for
POST /signature_requests/{id}/remind — signers were reminded less than an hour ago
(remind_too_soon, with Retry-After). (rate_limited, remind_too_soon) |
| 500 | Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error) |
Codes listed are the examples the spec gives; see all error codes.
Archive a template
/api/v1/templates/{id}/archive- Needs a full-access key
- Accepts Idempotency-Key
Archives a template so it no longer shows among active templates. Use it when a document
version is retired. Existing signature requests are not affected. Archiving an already
archived template succeeds and returns it unchanged. The template.archived webhook fires only
the first time. Archived templates can't be used for new signature requests
(422 template_archived), and signing links of unfinished requests from them stop working.
Path parameters
idintegerRequiredTemplate ID.
Headers
Idempotency-KeystringA unique value (e.g. a UUID) that makes retries of this request safe. A successful response is stored for 24 hours and replayed for retries with the same key.
at most 255 characters
Request sample
curl -X POST https://app.signwith.co/api/v1/templates/311/archive \
-H "Authorization: Bearer $SIGNWITH_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"const response = await fetch('https://app.signwith.co/api/v1/templates/311/archive', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
})
console.log(response.status, await response.json())import os
import uuid
import requests
response = requests.post(
"https://app.signwith.co/api/v1/templates/311/archive",
headers={
"Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
print(response.status_code, response.json())Response 200
The archived template. Returns a template object.
Example response: see the example object below.
Errors
| 401 | The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired) |
| 403 | The key is read-only, or its user can't access this resource. (read_only_api_key, forbidden) |
| 404 | The resource doesn't exist or belongs to another account. (not_found) |
| 409 | A request with the same Idempotency-Key is still being processed. Retry shortly. (idempotency_key_in_use) |
| 429 | Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback
messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for
POST /signature_requests/{id}/remind — signers were reminded less than an hour ago
(remind_too_soon, with Retry-After). (rate_limited, remind_too_soon) |
| 500 | Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error) |
Codes listed are the examples the spec gives; see all error codes.
The template object
A template, including its fields and documents.
objectstringRequiredAlwaystemplateidintegerRequirednamestringRequiredexternal_idstring or nullRequiredYour own ID for the template.
folderstring or nullRequiredName of the folder the template is in.
rolesarray of stringsRequiredRole names in signing order.
edit_urlstringRequiredOpens the template in the SignWith editor, where the user can place or adjust fields.
URLcreated_byobject or nullRequiredA SignWith user (a member of your team).
3 child fields
idintegerRequiredemailstringRequiredemail addressnamestring or nullRequiredFirst and last name, or
nullif not set.
archived_atstring (date-time) or nullRequiredcreated_atstring (date-time)Requiredupdated_atstring (date-time)Requiredfieldsarray of objectsRequiredA field placed on a template's documents. Keys with no value are omitted.
6 child fields
idstring (uuid)RequiredStable field ID.
namestringField name. Use it as the key in
prefill. Omitted if the field has no name.typestringRequiredField type, e.g.
text,signature,initials,date,number,checkbox,radio,select,multiple,image,file,phone,stamp,cells.rolestringName of the role that fills this field.
requiredbooleanRequiredWhether the signer must fill the field.
optionsarray of stringsAllowed values for
select,radioandmultiplefields.
documentsarray of objectsRequiredA document (one uploaded file) of a template.
4 child fields
idintegerRequirednamestringRequiredFile name without extension.
urlstringRequiredDownload link for the original file.
URLpreview_image_urlstring or nullImage of the first page, if available.
URL
{
"object": "template",
"id": 311,
"name": "Residential lease",
"external_id": "lease-v3",
"folder": "Leasing",
"roles": [
"Tenant",
"Landlord"
],
"edit_url": "https://app.signwith.co/templates/311/edit",
"created_by": {
"id": 42,
"email": "[email protected]",
"name": "Ada Lovelace"
},
"archived_at": null,
"created_at": "2026-09-20T09:12:44Z",
"updated_at": "2026-09-21T16:03:10Z",
"fields": [
{
"id": "0f9c2b7e-5d1a-4e8b-9a61-3c2e7d4f1a90",
"name": "Tenant name",
"type": "text",
"role": "Tenant",
"required": true
},
{
"id": "6a3e1d42-8b7c-4f19-a0d5-2e9b4c7f8a13",
"name": "Monthly rent",
"type": "number",
"role": "Tenant",
"required": true
},
{
"id": "b2d8f4a1-3c6e-4a7b-8d92-5f1e0c3a9b76",
"name": "Start date",
"type": "date",
"role": "Tenant",
"required": true
},
{
"id": "91c7e3b5-2a4d-4f8e-b610-7d3a5c9e2f48",
"name": "Pets",
"type": "select",
"role": "Tenant",
"required": false,
"options": [
"None",
"Cat",
"Dog"
]
},
{
"id": "4e8a2c6f-9b1d-4a3e-8c57-1f6d0b9e3a24",
"name": "Tenant signature",
"type": "signature",
"role": "Tenant",
"required": true
},
{
"id": "c5f1a9d3-7e2b-4c8a-9f60-3b8e4d2a7c15",
"name": "Landlord signature",
"type": "signature",
"role": "Landlord",
"required": true
}
],
"documents": [
{
"id": 7730,
"name": "Lease agreement",
"url": "https://app.signwith.co/file/eyJfcmFpbHMiOnsiZGF0YSI6MTIzfX0/lease-agreement.pdf",
"preview_image_url": "https://app.signwith.co/file/eyJfcmFpbHMiOnsiZGF0YSI6MTI0fX0/0.jpg"
}
]
}Base URL, pagination and idempotency work the same on every endpoint: see making requests. Every error code is on errors.
Start building
The API and MCP server come with every account. 3 free documents a month, then pay per document.