> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cadanapay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage Contracts

> Create contracts from templates or upload signed PDFs, track signatures, and manage the contract lifecycle

export const ApiExample = ({method = "GET", path, params, body, reference, tenantKey}) => {
  const baseUrl = "https://api.cadanapay.com";
  const query = params ? "?" + Object.entries(params).map(([k, v]) => `${k}=${v}`).join("&") : "";
  const url = `${baseUrl}${path}${query}`;
  const isPlatformPath = (/^\/(v1\/)?platform(\/|$)/).test(path || "");
  const includeTenantKey = tenantKey === undefined ? !isPlatformPath : tenantKey;
  let curl = `curl -X ${method} '${url}' \\\n  -H 'Authorization: Bearer YOUR_API_KEY'`;
  if (includeTenantKey) {
    curl += ` \\\n  -H 'X-MultiTenantKey: YOUR_BUSINESS_TENANT_KEY'`;
  }
  if (body) {
    const formatted = JSON.stringify(body, null, 2);
    curl += ` \\\n  -H 'Content-Type: application/json' \\\n  -d '${formatted}'`;
  }
  return <div>
      <CodeBlock language="bash" filename="bash" wrap>
        {curl}
      </CodeBlock>
      {reference && <div style={{
    marginTop: "-0.5rem",
    marginBottom: "1rem"
  }}>
          <a href={reference} style={{
    fontSize: "0.875rem"
  }}>
            Try it in the playground →
          </a>
        </div>}
    </div>;
};

Cadana supports two contract flows: **template-based** contracts that are sent for electronic signature, and **upload-based** contracts where you upload an already-signed PDF. This guide covers both.

***

## Prerequisites

<Steps>
  <Step title="Contracts enabled">
    Contracts must be enabled for your account. If you don't see the Contracts section in the Dashboard, contact your account manager to enable it.
  </Step>

  <Step title="API key from Dashboard">
    Get your API key from the [Cadana Dashboard](https://app.cadanapay.com). See [Authentication](/authentication).
  </Step>

  <Step title="Contract templates configured (template-based only)">
    If using template-based contracts, create your templates in the Dashboard under **People > Contracts > Browse Templates** before using the API. Not needed for uploaded contracts.
  </Step>

  <Step title="Onboarded workers">
    Workers must be onboarded as Persons — you'll assign contracts using their `personId`. See [Onboard Workers](/workforce/onboarding-workers).
  </Step>
</Steps>

***

## Option A: Template-Based Contracts

Use this flow when you want Cadana to handle electronic signatures. You select a contract template, Cadana generates the document, and both parties sign digitally.

### List Available Templates

Retrieve the templates available for your business. Each template is scoped to a `personType` (employee or contractor) and `country`.

<ApiExample method="GET" path="/v1/contract-templates" reference="/api-reference/workforce/contracts/list-templates" />

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "c47f3b21-9a8e-4d5c-b6f2-1e0a9d8c7b6a",
      "name": "Standard Global Contractor Agreement",
      "personType": "contractor",
      "type": "regular",
      "country": "US",
      "templateVariables": [
        {
          "name": "firstName",
          "label": "First Name",
          "ref": "person.firstName",
          "type": "string",
          "description": "The contractor's first name",
          "isRequired": false
        }
      ]
    }
  ]
}
```

Template variables (e.g., `person.firstName`) are auto-populated from the Person record when the contract is created.

### Create a Contract

Create a contract from a template and assign it to a person. The `sender` is the business representative who will sign first.

<ApiExample method="POST" path="/v1/contracts" body={{ templateId: "c47f3b21-9a8e-4d5c-b6f2-1e0a9d8c7b6a", personId: "8ef9a712-cdae-4110-b1ea-9ba95abbee6e", sender: { name: "HR Department", email: "hr@example.com" } }} reference="/api-reference/workforce/contracts/create" />

**Response:**

```json theme={null}
{
  "id": "d52e4a13-7b6f-4c8d-a9e1-2f3b5c7d8e9a",
  "name": "Jane Doe - Standard Global Contractor Agreement",
  "personId": "8ef9a712-cdae-4110-b1ea-9ba95abbee6e",
  "templateId": "c47f3b21-9a8e-4d5c-b6f2-1e0a9d8c7b6a",
  "status": "awaiting business signature",
  "signatures": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "HR Department",
      "emailAddress": "hr@example.com",
      "order": 0,
      "status": "unsigned",
      "signedAt": 0,
      "lastViewedAt": 0
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "name": "Jane Doe",
      "emailAddress": "amara@example.com",
      "order": 1,
      "status": "unsigned",
      "signedAt": 0,
      "lastViewedAt": 0
    }
  ],
  "subject": "The contract is ready for signature",
  "title": "Your Company has sent you a contract for signature"
}
```

<Tip>
  To suppress the default email notification to the signer — for example, if you handle notifications through your own system — pass `suppressNotification: true` in the request body.
</Tip>

<Warning>
  Contract creation is not idempotent — retrying the same request creates a second, identical contract, and there is no API endpoint to revoke a contract. Check for an existing contract with [`GET /v1/contracts?personId=...`](/api-reference/workforce/contracts/list) before retrying.
</Warning>

### Get the Signing URL

Retrieve a URL that the next signer can use to view and sign the contract. The URL is valid for **336 hours (14 days)**.

<ApiExample method="GET" path="/v1/contracts/d52e4a13-7b6f-4c8d-a9e1-2f3b5c7d8e9a/signingUrl" params={{ redirectUrl: "https://yourapp.com/contracts" }} reference="/api-reference/workforce/contracts/signing-url" />

**Response:**

```json theme={null}
{
  "url": "https://<app-domain>/sign-contract?token=abc123&redirectUrl=https%3A%2F%2Fyourapp.com%2Fcontracts",
  "signer": "business"
}
```

| Field    | Description                                          |
| :------- | :--------------------------------------------------- |
| `url`    | The signing URL for viewing and signing the contract |
| `signer` | Who needs to sign next: `business` or `person`       |

The domain in the URL is either `app.cadanapay.com` or your white-label domain, depending on your platform configuration.

The optional `redirectUrl` query parameter specifies where to send the signer after they complete signing.

<Tip>
  If you suppress email notifications (`suppressNotification: true`), you're responsible for delivering the signing URL to each signer. Use the signing URL endpoint to generate links on demand.
</Tip>

### Track Signature Status

List contracts to check their signature status. Optionally filter by `personId`.

<ApiExample method="GET" path="/v1/contracts" params={{ personId: "8ef9a712-cdae-4110-b1ea-9ba95abbee6e" }} reference="/api-reference/workforce/contracts/list" />

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "d52e4a13-7b6f-4c8d-a9e1-2f3b5c7d8e9a",
      "name": "Jane Doe - Standard Global Contractor Agreement",
      "personId": "8ef9a712-cdae-4110-b1ea-9ba95abbee6e",
      "templateId": "c47f3b21-9a8e-4d5c-b6f2-1e0a9d8c7b6a",
      "status": "completed",
      "signatures": [
        {
          "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "name": "HR Department",
          "emailAddress": "hr@example.com",
          "order": 0,
          "status": "signed",
          "signedAt": 1689759900,
          "lastViewedAt": 1689759850
        },
        {
          "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "name": "Jane Doe",
          "emailAddress": "amara@example.com",
          "order": 1,
          "status": "signed",
          "signedAt": 1689760200,
          "lastViewedAt": 1689760100
        }
      ],
      "createdAt": 1689759850
    }
  ]
}
```

Each signature includes `signedAt` (epoch timestamp, `0` if unsigned) and `lastViewedAt` so you can track engagement.

### Contract Status Flow

```mermaid theme={null}
flowchart LR
    A[awaiting business signature] --> B[awaiting person signature]
    B --> C[completed]
    A -.-> D[revoked]
    B -.-> D
```

| Status                        | Meaning                                     |
| :---------------------------- | :------------------------------------------ |
| `awaiting business signature` | Business representative needs to sign first |
| `awaiting person signature`   | Business signed, waiting for the worker     |
| `completed`                   | Both parties have signed                    |
| `revoked`                     | Contract was cancelled before completion    |

<Note>
  Contracts can only be revoked before both parties have signed — and not via the API; there is no revocation endpoint. Once `completed`, the contract is final.
</Note>

***

## Retrieve a Contract

Fetch the full details of a single contract by its ID.

<ApiExample method="GET" path="/v1/contracts/{contractId}" reference="/api-reference/workforce/contracts/retrieve" />

To include a presigned download URL for the contract PDF, pass `includeDownloadUrl=true`:

<ApiExample method="GET" path="/v1/contracts/{contractId}" params={{ includeDownloadUrl: "true" }} reference="/api-reference/workforce/contracts/retrieve" />

**Response:**

```json theme={null}
{
  "id": "d52e4a13-7b6f-4c8d-a9e1-2f3b5c7d8e9a",
  "name": "Jane Doe - Standard Global Contractor Agreement",
  "personId": "8ef9a712-cdae-4110-b1ea-9ba95abbee6e",
  "templateId": "c47f3b21-9a8e-4d5c-b6f2-1e0a9d8c7b6a",
  "status": "completed",
  "signatures": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "HR Department",
      "emailAddress": "hr@example.com",
      "order": 0,
      "status": "signed",
      "signedAt": 1689759900,
      "lastViewedAt": 1689759850
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "name": "Jane Doe",
      "emailAddress": "amara@example.com",
      "order": 1,
      "status": "signed",
      "signedAt": 1689760200,
      "lastViewedAt": 1689760100
    }
  ],
  "createdAt": 1689759850,
  "downloadUrl": "https://storage.example.com/contracts/d52e4a13.pdf?token=abc123&expires=900",
  "downloadUrlExpiresAt": 1689760750
}
```

<Note>
  The `downloadUrl` and `downloadUrlExpiresAt` fields are only present when `includeDownloadUrl=true`. The download URL expires after **15 minutes**.
</Note>

***

## Option B: Upload a Signed Contract

Use this flow when you already have a signed contract PDF — for example, a contract signed offline or through a third-party e-signature tool.

### Step 1: Upload the File

Upload the PDF using the two-step file upload flow. See [Working with Files](/reference/working-with-files) for the full process.

<ApiExample method="POST" path="/v1/files/upload-url" body={{ purpose: "contract", contentType: "application/pdf" }} reference="/api-reference/workforce/files/create-upload-url" />

**Response:**

```json theme={null}
{
  "fileId": "e63f5b24-8c7d-4a9e-b0f1-3d4e6f8a9b0c",
  "putUrl": "https://s3.amazonaws.com/...",
  "expiresIn": 900
}
```

Then upload the file to the `putUrl` within the expiry window (15 minutes):

```bash Bash theme={null}
curl -X PUT '<putUrl>' \
  -H 'Content-Type: application/pdf' \
  --data-binary '@/path/to/signed-contract.pdf'
```

### Step 2: Create the Contract Record

Attach the uploaded file to the person's record using the `fileId` from step 1.

<ApiExample method="POST" path="/v1/contracts-upload" body={{ personId: "8ef9a712-cdae-4110-b1ea-9ba95abbee6e", fileId: "e63f5b24-8c7d-4a9e-b0f1-3d4e6f8a9b0c", contractName: "Contractor Agreement - Jane Doe" }} reference="/api-reference/workforce/contracts/upload-contract" />

**Response:**

```json theme={null}
{
  "id": "f74a6c35-9d8e-4b0f-c1a2-4e5f7a8b9c0d"
}
```

Uploaded contracts are created with status `completed` since they're already signed. They appear alongside template-based contracts when you list contracts for a person, and can be retrieved with [`GET /v1/contracts/{contractId}`](/api-reference/workforce/contracts/retrieve).

***

## Webhook Events

Cadana emits contract webhook events so you can track the signing lifecycle in real time. Subscribe via the [Dashboard](https://app.cadanapay.com) or see [Webhooks](/reference/webhooks) for setup.

### contract.created

Fired when a new contract is created (template-based or uploaded).

```json JSON theme={null}
{
  "id": "d52e4a13-7b6f-4c8d-a9e1-2f3b5c7d8e9a",
  "personId": "8ef9a712-cdae-4110-b1ea-9ba95abbee6e",
  "templateId": "c47f3b21-9a8e-4d5c-b6f2-1e0a9d8c7b6a",
  "tenantKey": "cad95193904"
}
```

### contract.signed

Fired each time a signature is collected. The `isComplete` field tells you whether all signatures are now in.

```json JSON theme={null}
{
  "id": "d52e4a13-7b6f-4c8d-a9e1-2f3b5c7d8e9a",
  "signatureId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "HR Department",
  "email": "hr@example.com",
  "isComplete": false,
  "tenantKey": "cad95193904"
}
```

### contract.status.updated

Fired on status transitions (e.g., revoked).

```json JSON theme={null}
{
  "id": "d52e4a13-7b6f-4c8d-a9e1-2f3b5c7d8e9a",
  "status": "revoked",
  "tenantKey": "cad95193904"
}
```

See [Events](/reference/events) for all event types and payload details.

***

## Troubleshooting

| Issue                 | Cause                                      | Solution                                                                             |
| :-------------------- | :----------------------------------------- | :----------------------------------------------------------------------------------- |
| `401 Unauthorized`    | Invalid or missing API key                 | Check your API key in the [Dashboard](https://app.cadanapay.com)                     |
| `400` on create       | Missing required fields                    | Ensure `templateId`, `personId`, and `sender` (with `name` and `email`) are provided |
| No templates returned | Templates not configured for your business | Create templates in the Dashboard under **People > Contracts > Browse Templates**    |
| Signing URL expired   | URL older than 336 hours                   | Generate a new signing URL with `GET /v1/contracts/{id}/signingUrl`                  |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Onboard Workers" icon="user-plus" href="/workforce/onboarding-workers">
    Create Person records before issuing contracts
  </Card>

  <Card title="Pay Workers via Payroll" icon="money-check-dollar" href="/workforce/pay-workers-via-payroll">
    Pay workers after contracts are in place
  </Card>

  <Card title="Working with Files" icon="file-arrow-up" href="/reference/working-with-files">
    Upload and download files (contracts, KYC documents, etc.)
  </Card>
</CardGroup>
