Create a contract
Create a draft contract from a template: copies its title, content, field placements and prepared PDF, and creates one recipient per template role. Nothing is emailed until you send it.
POST
https://api.centerfy.ai/webhooks/inbound/contracts Headers
| Header | Required | Value | Description |
|---|---|---|---|
x-api-key | Yes | your sub-account API key (cfy_…) | Authenticates the request; or use Authorization: Bearer <key>. |
Body parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
template_id | string (uuid) | Yes | — | Template to create the contract from. |
title | string | No | — | Contract title. Defaults to the template name. |
contact_id | string (uuid) | null | No | — | CRM contact in this sub-account. Also fills the first signer role (name and email) unless that role is given in recipients. |
recipients | { role_key, name, email }[] | No | — | Fill template roles (max 50). role_key must match a recipient_roles[].key; name and email are required. Roles you don’t fill keep the role label as name and an empty email. |
Request body
{
"template_id": "2e4f6a8b-1c3d-4e5f-9a7b-8c9d0e1f2a3b",
"title": "Service agreement — Jamie Rivera",
"contact_id": "3f1c2a9e-7b4d-4c8a-9e2f-1a2b3c4d5e6f",
"recipients": [
{
"role_key": "client",
"name": "Jamie Rivera",
"email": "[email protected]"
}
]
}Example
curl -X POST "https://api.centerfy.ai/webhooks/inbound/contracts" \
-H "x-api-key: $CENTERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "2e4f6a8b-1c3d-4e5f-9a7b-8c9d0e1f2a3b",
"title": "Service agreement — Jamie Rivera",
"contact_id": "3f1c2a9e-7b4d-4c8a-9e2f-1a2b3c4d5e6f",
"recipients": [
{
"role_key": "client",
"name": "Jamie Rivera",
"email": "[email protected]"
}
]
}' import { CenterfyClient } from "@centerfy/sdk";
const centerfy = new CenterfyClient({ apiKey: process.env.CENTERFY_API_KEY! });
const result = await centerfy.contracts.create({
"template_id": "2e4f6a8b-1c3d-4e5f-9a7b-8c9d0e1f2a3b",
"title": "Service agreement — Jamie Rivera",
"contact_id": "3f1c2a9e-7b4d-4c8a-9e2f-1a2b3c4d5e6f",
"recipients": [
{
"role_key": "client",
"name": "Jamie Rivera",
"email": "[email protected]"
}
]
});
console.log(result); centerfy contracts create '{"template_id":"2e4f6a8b-1c3d-4e5f-9a7b-8c9d0e1f2a3b","title":"Service agreement — Jamie Rivera","contact_id":"3f1c2a9e-7b4d-4c8a-9e2f-1a2b3c4d5e6f","recipients":[{"role_key":"client","name":"Jamie Rivera","email":"[email protected]"}]}' MCP tool: centerfy_contracts_create (see MCP server)
Response
{
"status": "success",
"contract": {
"id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"title": "Service agreement — Jamie Rivera",
"contract_number": "CTR-0042",
"status": "draft",
"source_type": "pdf",
"contact_id": "3f1c2a9e-7b4d-4c8a-9e2f-1a2b3c4d5e6f",
"template_id": "2e4f6a8b-1c3d-4e5f-9a7b-8c9d0e1f2a3b",
"sequential_signing": false,
"has_prepared_pdf": true,
"has_signed_pdf": false,
"verification_code": null,
"expires_at": null,
"sent_at": null,
"completed_at": null,
"declined_at": null,
"voided_at": null,
"content": [],
"fields": [
{
"id": "f0e1d2c3-b4a5-4968-8776-655443322110",
"type": "signature",
"page": 1,
"x": 12.5,
"y": 80,
"w": 25,
"h": 6,
"recipientId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"required": true
}
],
"recipients": [
{
"id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"name": "Jamie Rivera",
"email": "[email protected]",
"role": "signer",
"signing_order": 1,
"status": "pending",
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "8b9c0d1e-2f3a-4b4c-9d5e-6f7a8b9c0d1e",
"name": "Account manager",
"email": "",
"role": "cc",
"signing_order": 2,
"status": "pending",
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"created_at": "2026-10-06T10:00:00.000Z",
"updated_at": "2026-10-06T10:00:00.000Z"
},
"ready_to_send": true,
"blockers": []
}Errors
400— if template_id or contact_id is not a UUID, a role_key isn’t one of the template’s roles, or a recipient’s name or email is missing/invalid.404— if the template or contact is not in this sub-account.
Notes
Returns 201. blockers lists what still prevents sending (no prepared PDF, no signers, a signer without an email or without a signature field) and ready_to_send is true when it is empty. Builder templates without a prepared PDF can only be sent from the app.