Skip to content
Letted DocsGo to Letted

Create agreements and forms through the API

Create a document from a saved template or supply your own text, fill in landlord details, and choose whether signatures are needed. Tenants complete their details in Letted, while your other software tracks progress.

Your other software can create a tenancy, add its tenants, invite them to Letted and prepare their agreement. The landlord and tenants can sign in either order. When signatures are required, the agreement is only complete when the landlord and every tenant have signed. Forms can instead be completed without signatures.

Prepare your template once

Open a property's Tenancy Agreements → Use template → Templates page. Choose Create template to write a document, or upload a PDF and review its converted editable text. Save it with a name so you can reuse it for other properties and tenancies. Every saved template has its own ID (UUID). Use a template saved by the same person who creates the API key. Your connection can look up its name and ID using GET /api/v1/agreement-templates.

For a one-off document, open New Agreement → Recents and choose a Letted template or Start from scratch. Both open the same writer: click the title to rename it, use the placeholder buttons, add custom placeholders and choose whether signatures are required. Letted templates start with the selected tenancy’s details filled in. Review and edit the text, then choose Save draft. These edits belong to that document; they do not change Letted’s original template.

Templates fill these placeholders with the selected tenancy's details:

For example, this small formatting example is not a complete tenancy agreement:

# Tenancy agreement

Landlord: {{landlordName}}
Property: {{propertyAddress}}
Tenants: {{tenantNames}}
Start date: {{startDate}}
Rent: {{rentAmount}}
Deposit: {{depositAmount}}

Ordinary text stays exactly as written. Letted does not rewrite names or terms that you typed directly into the template. Unknown placeholders or missing built-in tenancy details prevent creation. Declared custom tenant fields may intentionally remain blank until the tenant completes them, as described below. Updating a saved template later does not change agreements already created from it.

Forms that do not need signatures

Require signatures is on by default. Turn it off in the template writer to save a reusable form. When creating an agreement, the template supplies the default; you can change it for that individual document without changing the template. You can also change it on an unsent draft's agreement page, before it has been locked, sent or signed. Existing agreements keep their current requirements.

With signatures off, send the form to tenants without signing first. They click the blank spaces directly in the document to enter their answers, review the document and choose Complete form. Their answers are saved automatically when they confirm completion. A landlord can also complete it. All required fields must be filled. Completion freezes the entire form for everyone, including optional fields. For joint tenancies, one completion finishes the shared form; it does not collect a separate submission from each tenant. A form with no fields can still be reviewed and completed.

Completed forms show Completed, not Fully signed, and record who completed them in the audit trail. They do not record electronic signatures or satisfy a requirement for a signed tenancy agreement. Letted's signature boxes are omitted from the page and PDF; signature lines already written in your template text must be removed in the writer if you do not want them.

Template and agreement API lookups include signatureRequired. Omit it from creation to inherit the template setting, or send an explicit boolean override:

{
  "templateId": "TEMPLATE_ID",
  "signatureRequired": false,
  "fieldValues": { "tenancyReference": "CRM-TENANCY-1042" }
}

This example assumes the template declares tenancyReference. The API prepares and sends the form; completion happens in Letted. The public API does not provide a completion endpoint. On completion, GET /api/v1/agreements/{id} returns status: "completed", signatureRequired: false and completedAt. Signer records remain unsigned; use the agreement status to track form completion.

Add custom fields

In the template writer, choose Add custom placeholder beside the existing placeholders. Give it a label, choose Landlord or Tenant, and decide whether an answer is required. The modal explains how it works and creates a unique placeholder key for you. The key is generated automatically and stays the same when you edit the label. Your connected software can look it up using the template API.

Choose Add placeholder, then click the new placeholder to copy it and paste it into your document wherever the answer should appear. Use its pencil button to edit the label, who fills it in, or whether it is required. Its key stays fixed once added. If you remove a placeholder, also delete its {{key}} text from the document.

GET /api/v1/agreement-templates/{id} returns the template inside data. This fragment shows its fields array (other response properties are omitted):

{
  "data": {
    "fields": [
      { "key": "tenancyReference", "label": "Tenancy reference", "filledBy": "landlord", "required": true },
      { "key": "occupantId", "label": "Occupant ID", "filledBy": "tenant", "required": true }
    ]
  }
}

Supply custom values when creating the contract. This example explicitly enables signing:

{
  "templateId": "TEMPLATE_ID",
  "externalId": "crm-agreement-1042",
  "signatureRequired": true,
  "fieldValues": { "tenancyReference": "CRM-TENANCY-1042" }
}

Use the exact field key as the JSON property name, without the {{ and }} braces. The connection can supply values for either kind of field, including prefilling a Tenant field that tenants can review and edit before signing. All required Landlord fields must be supplied at creation. A Letted tenancy ID is not automatically substituted for your custom key: pass whichever internal or external reference your system needs in fieldValues.

Tenant fields can be left blank. Tenants complete their fields in the agreement page, save them, and review the updated contract before signing. For a joint tenancy, these are shared contract fields, not separate answers per signer. Give separate fields clear labels if you need details for several occupants. Tenants cannot change Landlord fields. These settings control editing, not visibility: filled values appear in the contract for every party.

Keys use letters, numbers and underscores, starting with a letter (maximum 64 characters); built-in placeholder names are reserved. Up to 30 fields are supported. Values are single-line plain text, up to 500 characters; unknown keys are rejected. Optional fields without values appear as blank lines. The template itself is unchanged by filling an individual contract.

When signatures are required, everyone must complete required fields before the first signature. Contracts with tenant fields remain editable through the fields form until the first party signs. The first signature freezes all values, including optional fields. All signatures cover the same final document; a party viewing an older copy must reload and review it before signing. Forms without signatures stay editable through their fields form until someone chooses Complete form.

The public API accepts fieldValues when creating an agreement; it does not provide a route to update them afterwards. Template definitions are managed in Letted. Agreement lookup responses do not expose the filled values or document contents.

Create a document without a saved template

Use the same POST /api/v1/tenancies/{id}/agreements endpoint, supplying document instead of templateId. Send exactly one of them. This creates an individual agreement or form; it does not save a reusable template.

For example, send an unsigned occupant-information form with a landlord reference already filled in:

curl 'https://app.letted.com/api/v1/tenancies/TENANCY_ID/agreements' \
  --request POST \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: crm-occupant-form-1042-v1' \
  --data '{
    "document": {
      "name": "Occupant information",
      "markdown": "# Occupant information\nProperty: {{propertyAddress}}\nTenants: {{tenantNames}}\nTenancy reference: {{tenancyReference}}\nOccupant ID: {{occupantId}}",
      "fields": [
        {"key":"tenancyReference","label":"Tenancy reference","filledBy":"landlord","required":true},
        {"key":"occupantId","label":"Occupant ID","filledBy":"tenant","required":true}
      ]
    },
    "signatureRequired": false,
    "externalId": "crm-occupant-form-1042",
    "fieldValues": {"tenancyReference":"CRM-TENANCY-1042"}
  }'

Replace TENANCY_ID and YOUR_API_KEY with real values. Set the top-level signatureRequired to true to require signing instead. For documents supplied directly, omitting it defaults to true. When using templateId, omitting it inherits that template’s setting. Neither choice signs or completes the document automatically.

document.name and document.markdown are required. document.fields declares the custom placeholders and can be omitted when there are none. Every declared field must appear as {{key}} in the document; every custom placeholder must have a matching definition. Built-in placeholders do not need field definitions. Use filledBy: "landlord" for references supplied by you or your software, and filledBy: "tenant" for answers tenants may supply. The interface calls these options Landlord and Tenant.

All required landlord values must be supplied in fieldValues before the API creates and sends either kind of document. Tenant values may be supplied too, or left blank for tenants. Unknown keys, missing required landlord values, or invalid definitions return HTTP 422 without creating a document or queuing an email. The response, retry rules, permissions and email operations are the same as for saved templates.

Write a one-off agreement in Letted

Choose Start from scratch on the Recents tab to write an agreement without saving a reusable template. The writer has the same built-in placeholders and Add custom placeholder button. When you save the draft, Letted fills built-in details from the selected tenancy and keeps custom fields for the landlord or tenant to complete on the agreement page. Required landlord fields must be filled before sending, and all required fields must be filled before signing or completing an unsigned form.

Give your connection permission

Create an API key with Create and send agreements and forms (agreements:write) and View tenancies and agreements (tenancies:read). Add the tenancy, tenant and property permissions needed for the rest of your workflow. Keys that already have agreements:write can use both saved templates and inline documents. If your key lacks this permission, create a replacement key with the permissions you need.

The key can only create agreements for properties it is allowed to access. Template lookup lists the key creator's saved templates. It does not return document contents.

Set up the tenancy and tenants

Finish adding people before creating their agreement. The filled document and its signer list are a snapshot of that group. A saved template is not a signature: when signing is enabled, everyone still signs the new contract themselves in Letted.

New tenants: invite them separately

Creating a tenant with portal access enabled does not send an invitation. Creating an agreement does not invite them either. To invite a new tenant to Letted, explicitly call POST /api/v1/tenants/{id}/invite.

You can create their agreement before they accept the invitation or finish signing up. It will be waiting when they first sign in with their linked tenant account. Agreement notification emails are only eligible once the tenant has a linked account, subject to portal access and email preferences. Enabling access or sending an invitation alone does not meet that requirement.

Example: prepare an agreement for a new tenant

POST /api/v1/tenants/TENANT_ID/invite

Do not treat an agreement email as a substitute for the invitation, or recreate the agreement because its notification did not send. Check the existing operation if email delivery matters; do not assume an old notification will automatically send after signup, because delivery retries are limited. See troubleshooting.

Create from a saved template

First look up GET /api/v1/agreement-templates/{id} to read its fields and signatureRequired. Use the exact field keys returned; the app generates them, so do not guess them from their labels. Send the tenancy ID in the address and the template ID in the body. The tenancy determines the property, so you cannot accidentally supply a different property here.

curl 'https://app.letted.com/api/v1/tenancies/TENANCY_ID/agreements' \
  --request POST \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: crm-agreement-1042-v1' \
  --data '{"templateId":"TEMPLATE_ID","externalId":"crm-agreement-1042","signatureRequired":true,"fieldValues":{"tenancyReference":"CRM-TENANCY-1042"}}'

Replace the uppercase placeholders with real IDs. externalId is optional: it is your own reference for looking up this agreement, and is not automatically printed in the contract. The example assumes the template declares tenancyReference; fieldValues places that value into {{tenancyReference}} in the document. Omit fieldValues if the template has no custom values to supply.

A successful request returns HTTP 201, the agreement in data, its Letted url, the signer list and an operationId. Creation saves the agreement and makes it available through the tenant portal, including for a new tenant when they first sign in. It also queues a separate notification operation, but agreement emails are only eligible once the tenant has a linked account, subject to portal access and email preferences. This operation does not send a platform invitation. Contracts with tenant fields collect those details before signing; signature-required contracts without tenant fields are locked immediately; unsigned forms stay editable until completion. It does not sign for anyone. The API key creator is the landlord signer and can open the returned link; tenants use their portal.

Emails run after the agreement is saved. Read GET /api/v1/operations/{operationId} to check delivery. A failed email does not delete the agreement or mean you should create another one. If your request times out, retry the exact same request with the same Idempotency-Key.

For documents requiring signatures, both parties can sign through Letted once all required fields are complete, even while the notification is waiting. The agreement's sent_to_tenant status means tenants can open it to complete details and sign once all required fields are filled; sentAt records acceptance of the queued email notifications. Email acceptance is not proof of inbox delivery.

Track signatures

Use webhooks to receive:

The signature event includes the agreement ID as data.resourceId, plus tenancyId, propertyId, signerId, signedAt and version. Match signerId against GET /api/v1/agreements/{id} to identify which signer completed. That lookup also shows each signer's status and the overall agreement status. It does not share the document file.

If all tenants sign first, the landlord still needs to sign before completion. If the landlord signs first, Letted waits for every tenant. Duplicate or out-of-order webhooks are possible; use the event ID to avoid handling a message twice, and fetch current agreement status when needed.

A request you can give your provider or ChatGPT

Help me connect [my other system] to Letted using these guides:
https://app.letted.com/docs/agreement-templates
https://app.letted.com/docs/creating-tenancies
https://app.letted.com/docs/tenant-access
https://app.letted.com/docs/receiving-events
https://app.letted.com/docs/api-reference

When I approve a tenancy, find the correct existing property in Letted,
create the tenancy, add everyone in the tenant group, enable their portal
access and explicitly invite them with POST /api/v1/tenants/{id}/invite.
Check invitation delivery separately from agreement availability. Do not
use agreement creation as a substitute for inviting a new tenant. Their
agreement can be created before signup and will be waiting at first login;
agreement notification emails require a linked tenant account.
Then create their agreement using my
saved template ID, or supply document.name, document.markdown and
document.fields for a one-off document. Never send both sources. Set the
top-level signatureRequired boolean according to whether signing is needed.
For a saved template, read its fields definitions and map my tenancy reference
to the correct fieldValues key, without placeholder braces. Supply all
required Landlord fields; leave Tenant fields for tenants unless my
system already knows their values. externalId alone does not print a
reference in the contract. Letted should fill in the details and let tenants
complete any missing required fields before either party signs. The first
signature freezes all field values.

Update my tracker when each tenant signs. Only mark the agreement complete
when agreement.completed arrives and a lookup confirms the expected outcome:
fully_signed for a signed contract, or completed with signatureRequired false
for an unsigned form.
Use stable idempotency keys, check email operations, and avoid duplicate
agreements when retrying. Ask me for missing details instead of guessing.
Do not ask me to paste my API key into this conversation.