Sign in to Pactly, open the account menu, and go to Account Settings.
Generate a Contract with the API
Your own system already holds the deal data — the CRM, the procurement tool, the internal request app. Rekeying it into a generation form to produce a contract is duplicate work, and it does not scale to a few hundred agreements.
The Pactly API generates a contract from one of your templates in a single call. You send the values, you get the contract back. This guide walks the whole path: key, template, values, generation, download.
Before you start
Section titled “Before you start”- A template that already produces the contract you want. The API generates from the same templates the interface uses; it does not create or edit them.
- Permission to create an API key in your account.
- Anything that can send an HTTPS request. The examples here use
curl.
Every request goes to https://clientapi.pactly.ai and carries your key in an x-api-key header. The full endpoint reference lives at api-docs.pactly.com.
Step 1: Create an API key
Section titled “Step 1: Create an API key”Select the API Keys section.
Click Create New API Key, name it after the system that will use it, and confirm. The key is shown only once — copy it and store it in your secret manager. You cannot view it again.
The key is scoped to your company. It reaches your templates and the contracts generated from them, and nothing belonging to anyone else.
Confirm it works before going further:
curl -s https://clientapi.pactly.ai/v1/test \ -H "x-api-key: YOUR_API_KEY"A valid key returns {"message":"It works!"}. A 401 means the key is wrong, revoked, or missing from the header.
Step 2: Find the template
Section titled “Step 2: Find the template”List the templates your company has:
curl -s https://clientapi.pactly.ai/v1/templates \ -H "x-api-key: YOUR_API_KEY"Each entry carries the id you generate from, along with everything that template accepts:
[ { "id": "507f1f77bcf86cd799439013", "name": "Software License Agreement", "version": "2.1.0", "description": "Standard licensing agreement", "createdAt": "2026-06-15T08:00:00Z", "variables": [ ... ], "valuemaps": [ ... ], "parties": [ ... ] }]Store the id. Template ids are stable; do not look the template up by name on every run, because renaming a template in Pactly would silently break your integration.
Step 3: See what values the template takes
Section titled “Step 3: See what values the template takes”A template accepts three kinds of input, and all three are listed on the template record. You send them in one flat values map, keyed by id.
Variables
Section titled “Variables”A variable prints a single value into the document.
{ "id": "SupplierName", "question": "Who is supplying?", "valueType": "string", "required": true, "default": null, "conditional": false}| Field | What it tells you |
|---|---|
id | The key to use in values |
valueType | string, number, or boolean. Types are checked, not coerced — sending 24 for a string is rejected |
required | Generation fails with a 400 when this is missing |
default | What the template author suggests. Not applied automatically — send it yourself or the spot renders empty |
conditional | true when the variable switches a block of text in or out rather than printing a value. Send a real boolean |
Valuemaps
Section titled “Valuemaps”A valuemap is a fixed set of choices. Send the label, and Pactly resolves it to the underlying text.
{ "id": "ContractingEntity", "question": "Which entity is contracting?", "allowMultipleSelection": false, "choices": ["Singapore - Acme Holdings", "UK - Acme Ltd"]}Send one of the strings from choices exactly. When allowMultipleSelection is true, send an array of labels instead of a single one.
Parties
Section titled “Parties”A party is a contact the document prints details for. Send an object containing the attributes that party declares.
{ "id": "party1", "label": "Supplier", "type": "entity", "attributes": ["entityName", "entityRegNo", "country", "address"]}attributes is the complete accepted list — any other key is rejected. When type is "unknown", the template has not decided whether this party is a company or a person, so send "type": "entity" or "type": "individual" alongside the attributes. Leave it out and the document’s entity-specific and individual-specific clauses have nothing to work from.
Step 4: Generate the contract
Section titled “Step 4: Generate the contract”Post the template id and the values map:
curl -s -X POST https://clientapi.pactly.ai/v1/contracts/generate \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "templateId": "507f1f77bcf86cd799439013", "contractName": "Acme - Software License 2026", "values": { "SupplierName": "Acme Pte Ltd", "Term": 24, "Agency": false, "ContractingEntity": "Singapore - Acme Holdings", "party1": { "entityName": "Acme Pte Ltd", "entityRegNo": "196800306E", "country": "Singapore", "address": "1 Raffles Place, Singapore 048616" } } }'contractName is optional and defaults to “Pactly contract”. Anything you leave out of values renders as the [●] placeholder rather than failing — the same marker you would see from a half-filled generation form.
Choosing what happens to the contract
Section titled “Choosing what happens to the contract”Three optional flags in options control the behaviour. They are per request, so the same key can persist one contract and preview the next.
| Flag | Default | What it does |
|---|---|---|
persist | true | Keeps the contract in Pactly, where it appears in the register like any other. false stores nothing — no contract, no file, no timeline entry — and returns the document in the response |
triggerWorkflows | follows persist | Runs your templateContractCreated workflows. This sends real email to real people, so set it to false for a bulk import. It cannot be true when persist is false — a workflow has no contract to act on |
reference | — | Your own identifier, printed wherever the template uses the {{__reference}} token. Accepted only with "persist": false; Pactly allocates its own reference for contracts it keeps |
The two response shapes
Section titled “The two response shapes”The response follows persist, and persisted tells you which one you got.
Persisted (the default) — a contract record. The document is fetched separately in step 5:
{ "persisted": true, "id": "507f1f77bcf86cd799439011", "name": "Acme - Software License 2026", "reference": "SLA-2026-014", "template": "507f1f77bcf86cd799439013", "company": "507f1f77bcf86cd799439010", "createdAt": "2026-08-19T10:30:00Z", "effectiveDate": null, "values": { ... }}Ephemeral ("persist": false) — the document itself, because nothing was stored and there is no id to come back for:
{ "persisted": false, "name": "Acme - Software License 2026", "template": "507f1f77bcf86cd799439013", "reference": "PO-2026-0042", "fileName": "Acme - Software License 2026.docx", "contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "document": "UEsDBBQABgAIAAAAIQ..."}document is the .docx, base64-encoded. Decode it and you have the file:
curl -s -X POST https://clientapi.pactly.ai/v1/contracts/generate \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"templateId":"507f1f77bcf86cd799439013","options":{"persist":false}}' \ | python3 -c "import json,sys,base64; r=json.load(sys.stdin); open(r['fileName'],'wb').write(base64.b64decode(r['document']))"Step 5: Download a persisted contract
Section titled “Step 5: Download a persisted contract”For a persisted contract, fetch the file with the id from step 4:
curl -s https://clientapi.pactly.ai/v1/contracts/507f1f77bcf86cd799439011/download \ -H "x-api-key: YOUR_API_KEY" \ -o contract.docxThe contract is now a normal Pactly contract. It shows up in the register, it can be sent for approval or signature, and your team can edit it in the Word add-in — nothing about it is second-class for having arrived through the API.
When something goes wrong
Section titled “When something goes wrong”Errors return a JSON body naming the problem rather than a generic failure.
| Status | Means | Do this |
|---|---|---|
400 | A value is the wrong type, a valuemap label is not in choices, a required variable is missing, or the body has a key that is not templateId, contractName, values or options | Read the message — it names the offending field. Re-check the ids against the template record |
401 | The key is missing, wrong, or revoked | Confirm the x-api-key header, then re-issue the key |
404 | The template id does not exist in your company | Check for a typo; a valid id from another tenant reads as not found |
429 | The generation budget for this key is used up | Back off and retry — see the limits below |
Rate limits
Section titled “Rate limits”Generation is bounded per API key, not per IP, so several integrations behind one outbound address do not compete:
- 300 generations per hour
- 3 running at once, with a short queue for the rest
- A minimum spacing of 200 ms between calls
Comfortable for a live integration, and enough headroom for a bulk load if you pace it. Treat a 429 as “slow down and retry”, not as a failure of the request.
The document has [●] in it
Section titled “The document has [●] in it”That placeholder marks a spot the template expected to fill and could not — almost always a key missing from your values map, or an id that does not match the template. Generation never fails on a missing value; it inserts the marker and carries on. Compare the keys you sent against variables, valuemaps and parties on the template record.
Related
Section titled “Related”Chat with us
We typically reply within a few minutes