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

# White Labeling

> Configure custom branding, email domains, email templates, and embedded experiences to create a fully branded signing experience for your customers.

Firma provides comprehensive white-labeling capabilities that let you create a fully branded e-signature experience. From custom logos and color themes to embedded interfaces, you can ensure your customers interact with your brand throughout the entire signing process.

## Overview

White labeling in Firma involves several components:

1. **Custom branding** - Upload logos, set color themes, and hide Firma branding entirely
2. **Custom signer terms** - Require signers to accept your own terms of service
3. **Signing button labels** - Customize the text on signing buttons per language
4. **Custom email domains** - Send signing request emails from your own domain
5. **Custom email sender address** - Control the "from" address on all outgoing emails
6. **Custom email templates** - Personalize the content and branding of signing notification emails
7. **Disable Firma emails** - Turn off automatic emails per signing request and send your own
8. **Embedded experiences** - Embed signing and template editors directly in your app

<Note>
  All API examples use your API key directly in the `Authorization` header. Do not use the `Bearer` prefix.
</Note>

***

## Custom branding

Customize logos, colors, and branding visibility at both the company and workspace level. Workspace settings override company settings, letting you create distinct branded experiences for each of your customers.

### Company logo

Upload a logo for your entire Firma account. This logo appears in signing emails and the signing experience.

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/company/logo \
  -H "Authorization: YOUR_API_KEY" \
  -F "file=@/path/to/logo.png"
```

**Requirements:**

* Format: PNG or JPEG only
* Max size: 2 MB

To remove the company logo:

```bash theme={null}
curl -X DELETE https://api.firma.dev/functions/v1/signing-request-api/company/logo \
  -H "Authorization: YOUR_API_KEY"
```

### Workspace logo

Override the company logo for a specific workspace:

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/workspaces/{workspace_id}/logo \
  -H "Authorization: YOUR_API_KEY" \
  -F "file=@/path/to/workspace-logo.png"
```

To remove a workspace logo (falls back to the company logo):

```bash theme={null}
curl -X DELETE https://api.firma.dev/functions/v1/signing-request-api/workspaces/{workspace_id}/logo \
  -H "Authorization: YOUR_API_KEY"
```

### Color theming

Customize the color palette of the signing experience using hex color values. Colors can be set at both the company and workspace level.

| Color Setting      | Description                           | Firma Default |
| ------------------ | ------------------------------------- | ------------- |
| `color_primary`    | Primary accent color (buttons, links) | `#c285ff`     |
| `color_primary_fg` | Text color on primary elements        | `#ffffff`     |
| `color_background` | Page background                       | `#1c1c21`     |
| `color_foreground` | Primary text color                    | `#ffffff`     |
| `color_card`       | Card/panel background                 | `#22222a`     |
| `color_border`     | Border color                          | `#3b3b3b`     |
| `color_accent`     | Secondary accent color                | `#c285ff`     |
| `color_accent_fg`  | Text color on accent elements         | `#ffffff`     |
| `color_canvas`     | Document canvas/viewer background     | `#1c1c21`     |
| `color_muted`      | Muted/subtle background areas         | `#22222a`     |
| `color_muted_fg`   | Text color on muted areas             | `#a1a1aa`     |

**Set company-level colors:**

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/company/settings \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "color_primary": "#0066cc",
    "color_primary_fg": "#ffffff",
    "color_background": "#f5f5f5",
    "color_foreground": "#1a1a1a",
    "color_card": "#ffffff",
    "color_border": "#e0e0e0"
  }'
```

**Set workspace-level colors** (overrides company colors for this workspace):

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/settings \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "color_primary": "#ff6600",
    "color_primary_fg": "#ffffff"
  }'
```

Colors must be 6-digit hex values (e.g., `#0066cc`). Set a color to `null` to clear it and fall back to the next level in the hierarchy.

### Hide Firma branding

Remove all Firma branding from the signing experience by enabling `show_custom_branding_only` at the company level:

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/company/settings \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "show_custom_branding_only": true
  }'
```

### Additional display settings

Fine-tune the signing experience with these settings, available at both company and workspace level:

| Setting                     | Description                                             | Default |
| --------------------------- | ------------------------------------------------------- | ------- |
| `show_signature_frame`      | Show the signature drawing frame/border                 | `true`  |
| `show_partial_watermark`    | Show a watermark on partially-signed documents          | `false` |
| `show_qr_code`              | Include a QR code linking to the signing page in emails | `false` |
| `require_otp_verification`  | Require email OTP verification before signing           | `false` |
| `require_terms_acceptance`  | Require signers to accept terms before signing          | `true`  |
| `disable_guided_navigation` | Disable step-by-step guided field navigation            | `false` |
| `allow_presigning_download` | Allow signers to download the document before signing   | `false` |

At the workspace level, set these to `null` to inherit the company-level setting.

### Settings hierarchy

Branding settings follow a cascading hierarchy:

1. **Workspace setting** (highest priority)
2. **Company setting**
3. **Firma default** (lowest priority)

This lets you set company-wide defaults and override them per workspace. At the workspace level, setting a value to `null` means "inherit from company."

See the [Workspace Settings API Reference](/api-reference/v01.27.00/workspace-settings/get-workspace-settings) for the full list of configurable settings.

***

## Custom signer terms

Require signers to accept your own terms of service or legal agreement before signing. Terms are configured per language and follow the same company/workspace cascade as other settings.

### How it works

When `require_terms_acceptance` is enabled (the default), signers see a terms acceptance checkbox before they can sign. You can customize the statement text and optionally link to a full terms document.

Terms are stored per language so signers see terms in their workspace's language, falling back through the cascade:

1. **Workspace terms** for the signer's language (highest priority)
2. **Company terms** for the signer's language
3. **Firma default terms** (lowest priority)

### Set company-level terms

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/company/signer-terms/en \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "statement_text": "I agree to the <a href=\"https://acme.com/terms\">Terms of Service</a> and acknowledge that my electronic signature is legally binding.",
    "terms_url": "https://acme.com/terms"
  }'
```

### Set workspace-level terms

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/signer-terms/en \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "statement_text": "I agree to the <a href=\"https://acmecorp.com/signing-terms\">Signing Terms</a>.",
    "terms_url": "https://acmecorp.com/signing-terms"
  }'
```

**Fields:**

* `statement_text` (required): HTML text shown next to the acceptance checkbox. Basic HTML tags are allowed (`<a>`, `<b>`, `<i>`, `<br>`).
* `terms_url` (optional): URL to the full terms document.

### List terms

Retrieve all configured terms for a company or workspace:

```bash theme={null}
# Company terms
curl https://api.firma.dev/functions/v1/signing-request-api/company/signer-terms \
  -H "Authorization: YOUR_API_KEY"

# Workspace terms
curl https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/signer-terms \
  -H "Authorization: YOUR_API_KEY"
```

### Delete terms

Remove custom terms for a specific language to fall back to the next level in the cascade:

```bash theme={null}
# Delete company terms for English
curl -X DELETE https://api.firma.dev/functions/v1/signing-request-api/company/signer-terms/en \
  -H "Authorization: YOUR_API_KEY"

# Delete workspace terms for English
curl -X DELETE https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/signer-terms/en \
  -H "Authorization: YOUR_API_KEY"
```

### Disable terms acceptance

To disable the terms acceptance requirement entirely:

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/company/settings \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "require_terms_acceptance": false
  }'
```

Supported languages: `en`, `es`, `it`, `pt`, `fr`, `de`, `el`, `ru`, `pl`.

See the [Signer Terms API Reference](/api-reference/v01.27.00/signer-terms/list-company-signer-terms) for complete endpoint documentation.

***

## Custom signing button labels

Customize the text on signing buttons per workspace and per language. This lets you match the button labels to your product's terminology or localization.

### Set button label overrides

Button labels are set as a JSONB object on workspace settings, keyed by language and then by button key:

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/settings \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signing_button_label_overrides": {
      "en": {
        "sign": "Approve and Sign",
        "next": "Continue",
        "finish": "Complete Signing"
      },
      "es": {
        "sign": "Aprobar y Firmar",
        "next": "Continuar",
        "finish": "Completar Firma"
      }
    }
  }'
```

### Clear button label overrides

Set to `null` to revert to Firma's default button labels:

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/settings \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signing_button_label_overrides": null
  }'
```

**Validation:**

* Each label must be a string, max 500 characters
* Language keys must be valid language codes (max 10 characters)
* HTML tags and control characters are stripped automatically

<Note>
  Button label overrides are a workspace-level setting only. There is no company-level override.
</Note>

***

## Custom email domains

By default, signing request emails are sent from Firma's domain. With custom email domains, emails appear to come directly from your company or your customers' companies.

### Account-level (company) email domains

Set up a custom email domain for your entire Firma account. All workspaces will use this domain by default unless overridden.

#### Step 1: Add your domain

Use the API to add a custom email domain:

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/company/domains \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "acme.com"
  }'
```

**Response (201 Created)**:

```json theme={null}
{
  "domain": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "domain": "acme.com",
    "verification_status": 0,
    "domain_status": 0,
    "is_primary": false,
    "verification_token": "firma-verify=abc123xyz",
    "date_created": "2024-01-15T10:30:00Z"
  },
  "verification_instructions": {
    "record_type": "TXT",
    "record_name": "_firma-verification.acme.com",
    "record_value": "firma-verify=abc123xyz",
    "next_step": "Add this TXT record to your DNS, then call POST /company/domains/{id}/verify-ownership"
  }
}
```

#### Step 2: Add TXT verification record

Add the verification TXT record to your DNS:

| Type | Name                           | Value                    |
| ---- | ------------------------------ | ------------------------ |
| TXT  | `_firma-verification.acme.com` | `firma-verify=abc123xyz` |

<Note>
  DNS propagation typically takes a few minutes but can take up to 48 hours.
</Note>

#### Step 3: Verify domain ownership

Once the TXT record is added, verify ownership:

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/company/domains/{domain_id}/verify-ownership \
  -H "Authorization: YOUR_API_KEY"
```

**Response**:

```json theme={null}
{
  "message": "Domain ownership verified",
  "domain": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "domain": "acme.com",
    "verification_status": 1,
    "domain_status": 0
  },
  "next_step": "Call POST /company/domains/{id}/finalize to complete domain setup and receive DNS records for email sending"
}
```

#### Step 4: Finalize domain setup

After ownership is verified, finalize the domain to receive email-sending DNS records:

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/company/domains/{domain_id}/finalize \
  -H "Authorization: YOUR_API_KEY"
```

**Response**:

```json theme={null}
{
  "message": "Domain finalized. Add the following DNS records to enable email sending.",
  "domain": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "domain": "acme.com",
    "verification_status": 2,
    "domain_status": 0
  },
  "dns_records": [
    { "type": "TXT", "name": "@", "value": "v=spf1 include:amazonses.com ~all", "ttl": "Auto", "status": "pending" },
    { "type": "CNAME", "name": "resend._domainkey", "value": "resend._domainkey.amazonses.com", "ttl": "Auto", "status": "pending" },
    { "type": "TXT", "name": "_dmarc", "value": "v=DMARC1; p=none;", "ttl": "Auto", "status": "pending" }
  ],
  "next_step": "Add these DNS records, then call POST /company/domains/{id}/verify-dns to complete verification"
}
```

#### Step 5: Add DNS records

Add all three DNS records to your domain:

| Type  | Name                | Value                               |
| ----- | ------------------- | ----------------------------------- |
| TXT   | `@`                 | `v=spf1 include:amazonses.com ~all` |
| CNAME | `resend._domainkey` | `resend._domainkey.amazonses.com`   |
| TXT   | `_dmarc`            | `v=DMARC1; p=none;`                 |

#### Step 6: Verify DNS records

Once DNS records are added, verify them:

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/company/domains/{domain_id}/verify-dns \
  -H "Authorization: YOUR_API_KEY"
```

**Response (all verified)**:

```json theme={null}
{
  "verified": true,
  "message": "Domain is fully verified and ready to send emails",
  "domain": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "domain": "acme.com",
    "verification_status": 2,
    "domain_status": 1,
    "is_primary": true
  },
  "dns_records": [
    { "type": "TXT", "name": "@", "value": "v=spf1 include:amazonses.com ~all", "status": "verified" },
    { "type": "CNAME", "name": "resend._domainkey", "value": "resend._domainkey.amazonses.com", "status": "verified" },
    { "type": "TXT", "name": "_dmarc", "value": "v=DMARC1; p=none;", "status": "verified" }
  ]
}
```

#### Step 7: Set as primary domain (optional)

If you have multiple domains, set one as the default:

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/company/domains/{domain_id}/set-primary \
  -H "Authorization: YOUR_API_KEY"
```

### Workspace-level email domains

For multi-tenant SaaS applications, you can configure different email domains per workspace. Workspace domains override the company-level domain.

The workflow is identical to company domains, but uses workspace-scoped endpoints:

| Action           | Endpoint                                                       |
| ---------------- | -------------------------------------------------------------- |
| List domains     | `GET /workspace/{workspace_id}/domains`                        |
| Add domain       | `POST /workspace/{workspace_id}/domains`                       |
| Get domain       | `GET /workspace/{workspace_id}/domains/{id}`                   |
| Delete domain    | `DELETE /workspace/{workspace_id}/domains/{id}`                |
| Verify ownership | `POST /workspace/{workspace_id}/domains/{id}/verify-ownership` |
| Finalize setup   | `POST /workspace/{workspace_id}/domains/{id}/finalize`         |
| Verify DNS       | `POST /workspace/{workspace_id}/domains/{id}/verify-dns`       |
| Set primary      | `POST /workspace/{workspace_id}/domains/{id}/set-primary`      |

**Example: Add workspace domain**

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/domains \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "sign.acmecorp.com"
  }'
```

See the [Email Domains API Reference](/api-reference/v01.27.00/email-domains/list-company-domains) for complete endpoint documentation.

***

## Custom email sender address

Control the "from" address on all outgoing emails by combining a custom domain with a custom local part.

### How email addresses are resolved

When Firma sends an email, the sender address is built from three components:

```
{sender_name} <{local_part}@{domain}>
```

Each component is resolved through a fallback chain:

**Domain resolution:**

1. Workspace verified domain (primary preferred)
2. Company verified domain
3. `updates.firma.dev` (default)

**Local part resolution** (only applies when using a custom domain):

1. Workspace `email_local_part` setting
2. Company `email_local_part` setting
3. `support` (default)

**Sender name** is the name of the user who sent the signing request.

For example, if you set `email_local_part` to `noreply` and your verified domain is `sign.acmecorp.com`, emails will be sent from:

```
Jane Smith <noreply@sign.acmecorp.com>
```

### Setting the email local part

**Company level** (default for all workspaces):

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/company/settings \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email_local_part": "noreply"
  }'
```

**Workspace level** (overrides company setting):

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/settings \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email_local_part": "signing"
  }'
```

**Validation rules:**

* 1-64 characters, lowercase letters, numbers, dots, underscores, and hyphens
* Must start and end with a letter or number
* No consecutive dots
* Reserved values (`postmaster`, `abuse`, `mailer-daemon`) are not allowed

Set to `null` at the workspace level to inherit from the company setting.

***

## Custom email templates

Customize the notification emails Firma sends on your behalf so they match your brand voice and style. You can set templates at both the company and workspace level, with workspace templates overriding company defaults.

### Customizable email types

| Email Type                | When Sent                                                            |
| ------------------------- | -------------------------------------------------------------------- |
| `signing_invite`          | When a signing request is sent to a recipient                        |
| `next_signer`             | When it's the next signer's turn in a signing order                  |
| `resend_notification`     | When the sender manually resends a signing invitation                |
| `reminder_default`        | When an automatic reminder is sent for a pending signature           |
| `signing_expired`         | When a signing request expires                                       |
| `signing_cancelled`       | When a signing request is cancelled                                  |
| `signing_declined`        | When a signer declines (sent to other signers)                       |
| `signing_declined_admin`  | When a signer declines (sent to the sender, includes decline reason) |
| `signing_completed`       | When all signers have completed signing                              |
| `signer_identity_changed` | When a signer changes their name during signing                      |

### Available placeholders

Templates support HTML bodies with dynamic placeholders using `{{placeholder}}` syntax.

**Signer placeholders:**

| Placeholder             | Description            |
| ----------------------- | ---------------------- |
| `{{signer_first_name}}` | Signer's first name    |
| `{{signer_last_name}}`  | Signer's last name     |
| `{{signer_name}}`       | Signer's full name     |
| `{{signer_email}}`      | Signer's email address |
| `{{signer_title}}`      | Signer's job title     |
| `{{signer_company}}`    | Signer's company name  |

**Document placeholders:**

| Placeholder                | Description                               |
| -------------------------- | ----------------------------------------- |
| `{{signing_request_name}}` | Name of the signing request               |
| `{{signing_link}}`         | URL for the signer to sign                |
| `{{expiration_date}}`      | When the signing request expires          |
| `{{download_link}}`        | Link to download the signed document      |
| `{{decliner_name}}`        | Name of the signer who declined           |
| `{{decline_reason}}`       | Reason provided when declining            |
| `{{signing_qr_code}}`      | QR code image linking to the signing page |

**Team placeholders:**

| Placeholder        | Description             |
| ------------------ | ----------------------- |
| `{{team_name}}`    | Workspace/team name     |
| `{{team_email}}`   | Workspace contact email |
| `{{company_name}}` | Company name            |
| `{{company_logo}}` | Company logo image      |

You can also retrieve this list programmatically:

```bash theme={null}
curl https://api.firma.dev/functions/v1/signing-request-api/email-templates/placeholders \
  -H "Authorization: YOUR_API_KEY"
```

### View default templates

Retrieve Firma's built-in default templates for any supported language to use as a starting point:

```bash theme={null}
curl https://api.firma.dev/functions/v1/signing-request-api/email-templates/defaults/en \
  -H "Authorization: YOUR_API_KEY"
```

Supported languages: `en`, `es`, `it`, `pt`, `fr`, `de`, `el`, `ru`, `pl`.

### Set a workspace email template

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/email-templates/signing_invite \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Action required: Please sign {{signing_request_name}}",
    "body": "<p>Hi {{signer_name}},</p><p>Please review and sign {{signing_request_name}}.</p><p><a href=\"{{signing_link}}\">Sign now</a></p><p>- The {{team_name}} Team</p>"
  }'
```

**Validation:**

* `subject`: required, max 500 characters
* `body`: required, max 50,000 characters

### Set a company email template

```bash theme={null}
curl -X PUT https://api.firma.dev/functions/v1/signing-request-api/company/email-templates/signing_invite \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "{{company_name}}: Please sign {{signing_request_name}}",
    "body": "<p>Hi {{signer_name}},</p><p>You have a document to sign.</p><p><a href=\"{{signing_link}}\">Sign now</a></p>"
  }'
```

### Delete a custom template

Remove a custom template to revert to the next level in the hierarchy:

```bash theme={null}
curl -X DELETE https://api.firma.dev/functions/v1/signing-request-api/workspace/{workspace_id}/email-templates/signing_invite \
  -H "Authorization: YOUR_API_KEY"
```

### Template hierarchy

Email templates follow a cascading hierarchy:

1. **Workspace template** (highest priority)
2. **Company template**
3. **Built-in default** for the workspace's language (lowest priority)

This lets you set a company-wide branded template and override it for specific workspaces when needed. Deleting a template at any level causes it to fall back to the next level.

<Warning>
  A warning is returned if a template body does not contain the `{{signing_link}}` placeholder, since recipients need a link to access the signing flow.
</Warning>

See the [Email Templates API Reference](/api-reference/v01.27.00/email-templates/list-workspace-email-templates) for complete endpoint documentation.

***

## Disabling Firma emails

For complete control over customer communications, you can disable Firma's automatic emails per signing request. This allows you to:

* Send signing request links through your own email system
* Integrate with your existing notification workflows
* Customize email timing and follow-up sequences
* Use your own email delivery infrastructure

### Email settings on signing requests

Each signing request has settings that control which emails are sent:

| Setting                   | Description                                              | Default |
| ------------------------- | -------------------------------------------------------- | ------- |
| `send_signing_email`      | Send signing request notification emails to signers      | `true`  |
| `send_finish_email`       | Send completion email when all signers finish            | `true`  |
| `send_expiration_email`   | Send expiration notification when request expires        | `true`  |
| `send_cancellation_email` | Send cancellation notification when request is cancelled | `true`  |

### Create signing request with emails disabled

```bash theme={null}
curl -X POST https://api.firma.dev/functions/v1/signing-request-api/signing-requests \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "workspace_123",
    "template_id": "template_456",
    "settings": {
      "send_signing_email": false,
      "send_finish_email": false,
      "send_expiration_email": false,
      "send_cancellation_email": false
    },
    "recipients": [
      {
        "first_name": "Jane",
        "last_name": "Smith",
        "email": "jane@example.com",
        "designation": "Signer",
        "order": 1
      }
    ]
  }'
```

### Get signing URLs for manual distribution

When emails are disabled, retrieve the signing URLs from the API and send them through your own channels:

```js theme={null}
// Create signing request with emails disabled
const response = await fetch(
  'https://api.firma.dev/functions/v1/signing-request-api/signing-requests',
  {
    method: 'POST',
    headers: {
      'Authorization': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      workspace_id: workspaceId,
      template_id: templateId,
      settings: {
        send_signing_email: false,
        send_finish_email: false
      },
      recipients: [
        { first_name: 'Jane', last_name: 'Smith', email: 'jane@example.com', designation: 'Signer', order: 1 }
      ]
    })
  }
)
const signingRequest = await response.json()

// Get signing URLs for each recipient
const usersResponse = await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/signing-requests/${signingRequest.id}/users`,
  {
    headers: { 'Authorization': API_KEY }
  }
)
const { results: users } = await usersResponse.json()

// Send emails through your own system
for (const user of users) {
  const signingUrl = `https://app.firma.dev/signing/${user.id}`
  await yourEmailService.send({
    to: user.email,
    subject: 'Please sign your document',
    body: `Click here to sign: ${signingUrl}`
  })
}
```

***

## Embedded experiences

The most powerful white-labeling feature is embedding Firma's interfaces directly in your application. This removes all Firma branding and creates a seamless experience within your product.

### Embeddable signing

Embed the signing flow so recipients sign documents without leaving your app:

```html theme={null}
<iframe 
  src="https://app.firma.dev/signing/{signing_request_user_id}" 
  style="width:100%;height:900px;border:0;" 
  allow="camera;microphone;clipboard-write"
  title="Sign Document"
></iframe>
```

[Embeddable Signing Guide](/guides/embeddable-signing) - Full implementation details

### Embeddable template editor

Let users create and edit templates within your app using JWT authentication:

```js theme={null}
// Generate JWT token from your backend
const token = await generateTemplateToken(templateId)

// Embed the template editor
const editorUrl = `https://app.firma.dev/template-editor?token=${token}`
```

```html theme={null}
<iframe 
  src="https://app.firma.dev/template-editor?token={jwt_token}"
  style="width:100%;height:900px;border:0;"
  title="Edit Template"
></iframe>
```

[Embeddable Template Editor Guide](/guides/embeddable-template-editor) - JWT authentication and full implementation

### Embeddable signing request editor

Provide a UI for configuring signing request recipients and options:

```html theme={null}
<iframe 
  src="https://app.firma.dev/signing-request-editor?token={jwt_token}"
  style="width:100%;height:700px;border:0;"
  title="Configure Signing Request"
></iframe>
```

[Embeddable Signing Request Editor Guide](/guides/embeddable-signing-request-editor) - Full implementation guide

***

## Complete white-label setup

Here's a complete example of setting up a fully white-labeled workspace for a customer:

### 1. Create the workspace

```js theme={null}
const workspace = await fetch('https://api.firma.dev/functions/v1/signing-request-api/workspaces', {
  method: 'POST',
  headers: {
    'Authorization': API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Acme Corporation'
  })
}).then(r => r.json())

console.log('Created workspace:', workspace.id)
```

### 2. Upload a logo

```js theme={null}
const logoForm = new FormData()
logoForm.append('file', logoFile)

await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/workspaces/${workspace.id}/logo`,
  {
    method: 'POST',
    headers: { 'Authorization': API_KEY },
    body: logoForm
  }
)
```

### 3. Configure branding

```js theme={null}
await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/workspace/${workspace.id}/settings`,
  {
    method: 'PUT',
    headers: {
      'Authorization': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      color_primary: '#0066cc',
      color_primary_fg: '#ffffff',
      color_background: '#f8f9fa',
      color_foreground: '#212529',
      color_card: '#ffffff',
      color_border: '#dee2e6',
      email_local_part: 'noreply'
    })
  }
)
```

### 4. Hide Firma branding (company-level)

```js theme={null}
await fetch(
  'https://api.firma.dev/functions/v1/signing-request-api/company/settings',
  {
    method: 'PUT',
    headers: {
      'Authorization': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      show_custom_branding_only: true
    })
  }
)
```

### 5. Set up custom email domain (optional)

```js theme={null}
// Add domain
const domainResponse = await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/workspace/${workspace.id}/domains`,
  {
    method: 'POST',
    headers: {
      'Authorization': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ domain: 'sign.acmecorp.com' })
  }
).then(r => r.json())

const domainId = domainResponse.domain.id

// User adds TXT record to DNS...
// Verify ownership
await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/workspace/${workspace.id}/domains/${domainId}/verify-ownership`,
  { method: 'POST', headers: { 'Authorization': API_KEY } }
)

// Finalize (returns SPF, DKIM, DMARC records)
const finalizeResponse = await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/workspace/${workspace.id}/domains/${domainId}/finalize`,
  { method: 'POST', headers: { 'Authorization': API_KEY } }
).then(r => r.json())

console.log('Add these DNS records:', finalizeResponse.dns_records)

// User adds DNS records...
// Verify DNS
await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/workspace/${workspace.id}/domains/${domainId}/verify-dns`,
  { method: 'POST', headers: { 'Authorization': API_KEY } }
)

// Set as primary
await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/workspace/${workspace.id}/domains/${domainId}/set-primary`,
  { method: 'POST', headers: { 'Authorization': API_KEY } }
)
```

### 6. Customize email templates (optional)

```js theme={null}
await fetch(
  `https://api.firma.dev/functions/v1/signing-request-api/workspace/${workspace.id}/email-templates/signing_invite`,
  {
    method: 'PUT',
    headers: {
      'Authorization': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      subject: '{{company_name}}: Please sign {{signing_request_name}}',
      body: '<p>Hi {{signer_name}},</p><p>You have a document to review and sign.</p><p><a href="{{signing_link}}">Sign now</a></p><p>- The {{team_name}} Team</p>'
    })
  }
)
```

### 7. Embed the experiences

```jsx theme={null}
function AcmeSigningApp({ signingRequestUserId }) {
  return (
    <div className="acme-signing-portal">
      {/* Your branded header */}
      <header className="acme-header">
        <img src="/acme-logo.png" alt="Acme Corp" />
      </header>
      
      {/* Embedded Firma signing */}
      <iframe
        src={`https://app.firma.dev/signing/${signingRequestUserId}`}
        style={{ width: '100%', height: '800px', border: 'none' }}
        allow="camera;microphone;clipboard-write"
      />
      
      {/* Your branded footer */}
      <footer className="acme-footer">
        &copy; 2026 Acme Corporation
      </footer>
    </div>
  )
}
```

***

## Best practices

### Branding

* Use a consistent color palette across logos, email templates, and embedded experiences
* Upload logos in PNG format with a transparent background for best results
* Enable `show_custom_branding_only` to fully remove Firma's branding
* Set company-level branding as a baseline, then override per workspace where needed
* Test the full signing flow from your customers' perspective

### Email domain configuration

* Use a subdomain (e.g., `sign.yourcompany.com`) rather than your main domain
* Set up all DNS records (SPF, DKIM, DMARC) for optimal deliverability
* Monitor email bounce rates and adjust as needed
* Test email delivery before going live with customers

### Email templates

* Use the [defaults endpoint](#view-default-templates) to see Firma's built-in templates as a starting point
* Always include `{{signing_link}}` in invite and reminder templates
* Set a company-level template as a branded baseline, then override per workspace when needed
* Use `{{company_logo}}` in templates to include the workspace or company logo

### Security

* Always generate JWT tokens on your backend
* Never expose API keys in client-side code
* Use short token expiration times (recommended: 1-4 hours)
* Validate postMessage origins when handling iframe events

***

## Troubleshooting

### Domain ownership verification failed

**Possible causes**:

* DNS records not propagated (wait up to 48 hours)
* Incorrect TXT record name or value
* TXT record added to wrong zone

**Solution**: Check your DNS configuration matches the `verification_instructions` returned when adding the domain

### DNS records not verifying

**Possible causes**:

* Records not propagated yet
* Incorrect record values
* Missing records

**Solution**: Call `GET /company/domains/{id}` or `verify-dns` to see which specific records are pending

### Signing iframe not loading

**Possible causes**:

* Invalid signing request user ID
* Expired or cancelled signing request
* Content Security Policy blocking iframe

**Solution**: Verify signing request status and check browser console for CSP errors

### JWT token expired

**Possible causes**:

* Token TTL too short for use case
* Clock skew between servers

**Solution**: Generate tokens with appropriate expiration, consider refreshing tokens proactively

### Colors not applying

**Possible causes**:

* Invalid hex format (must be 6-digit, e.g., `#0066cc`, not 3-digit `#06c`)
* Setting applied at company level but workspace has its own override

**Solution**: Check workspace settings first, then company settings. Use `null` at the workspace level to clear an override.

***

## Related guides

* [Embeddable Signing](/guides/embeddable-signing) - Embed the signing experience in your app
* [Embeddable Template Editor](/guides/embeddable-template-editor) - Let users edit templates with JWT auth
* [Embeddable Signing Request Editor](/guides/embeddable-signing-request-editor) - Configure signing requests in your UI
* [Creating Workspaces](/guides/creating-workspaces) - Set up isolated environments for customers
* [Webhooks](/guides/webhooks) - Track signing events in real-time
