# Best Practices

Best practices for building reliable, efficient signing workflows with the Skribble Sign API.

## API Version Selection

**Use Sign API v2** for all production integrations — it is the current stable version.

**Sign API v3 is available as a preview** and is a great opportunity to test upcoming features early and share feedback with the Skribble team before general availability:

- Draft workflow — build signature requests before inviting signers
- Multi-document support — up to 50 documents per request
- Signer groups and flexible signer management

:::warning{title="Preview — Subject to Change"}

Sign API v3 is not yet stable. Breaking changes may occur before GA. Use it in non-production environments and [share your feedback with us](mailto:info@skribble.com).

:::

## Signature Request Workflow

In v2, a signature request is created and goes directly to `OPEN` — signers are notified immediately. Include the document inline in the creation call, either as a URL reference or Base64-encoded content, or reference a previously uploaded document by ID.

```
OPEN → SIGNED/DECLINED/WITHDRAWN
```

### Option 1 — Document via URL

```json
{
  "title": "Contract Agreement",
  "message": "Please sign this contract.",
  "document": {
    "url": "https://your-app.com/documents/contract.pdf",
    "filename": "contract.pdf"
  },
  "signatures": [
    { "signer_email_address": "alice@example.com" }
  ]
}
```

### Option 2 — Document as Base64

```json
{
  "title": "Contract Agreement",
  "message": "Please sign this contract.",
  "document": {
    "content": "<base64-encoded-pdf>",
    "filename": "contract.pdf"
  },
  "signatures": [
    { "signer_email_address": "alice@example.com" }
  ]
}
```

### Option 3 — Pre-uploaded document by ID

Upload the document first, then reference it by ID:

```json
{
  "content": "<base64-encoded-pdf>",
  "filename": "contract.pdf"
}
```

Then reference the returned `id` in the signature request:

```json
{
  "title": "Contract Agreement",
  "document_id": "doc-aaa-111",
  "signatures": [
    { "signer_email_address": "alice@example.com" }
  ]
}
```

:::tip{title="v3 Draft Workflow"}

Sign API v3 (preview) introduces a document-first, explicit-initiation model with multi-document support and signer groups. See [Migrating from v2 → Draft Workflow](/sign-api-v3-migrate-from-v2#the-v3-draft-workflow) for the full flow.

:::

## Document Handling

### Upload Best Practices

1. **Validate before upload**: Ensure documents are valid PDFs
2. **Use meaningful filenames**: They appear in the signing interface
3. **Optimize file size**: Large files slow down the signing experience
4. **Base64 encode properly**: Documents must be Base64-encoded

```javascript
// Upload a document
const response = await api.post('/documents', {
  title: 'Contract.pdf',
  content_type: 'application/pdf',
  content: base64EncodedPdf
});
```

### Supported Attachment Types

- PDF documents
- Microsoft Office formats (Word, Excel, PowerPoint)
- Images (JPEG, PNG)
- Plain text files

## Signer Management

### Adding Signers

You can add signers in several ways:

**1. By Email (Skribble account required):**

```json
{
  "signer_email_address": "signer@example.com"
}
```

**2. With Identity Data (no account required):**

```json
{
  "signer_identity_data": {
    "email_address": "signer@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "mobile_number": "+41791234567",
    "language": "en"
  }
}
```

**3. With both (no account required, unless existing account present):**

```json
{
  "signer_email_address": "signer@example.com",
  "signer_identity_data": {
    "email_address": "signer@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "mobile_number": "+41791234567",
    "language": "en"
  }
}
```

### Signing Sequence

Control the order in which signers are invited by setting `sequence` on each signature:

- Values start at **1** and must be sequential with no gaps (1, 2, 3…)
- Signers with the **same sequence number** sign in parallel
- Range: **1–999**
- Omit `sequence` to allow all signers to sign in parallel

### Disabling Notifications for SES

For Simple Electronic Signature (SES) quality, you can disable TAN-based notifications for no-account signers by setting `notify: false` on the signature. Use `identity_reference` in `signer_identity_data` to correlate signers with your internal system.

### Signer Modification Rules

- In v2, signers are part of the creation call — once the request is `OPEN`, you cannot add or remove signers
- Once any signer has signed, you cannot modify the request
- Use `DELETE /signature-requests/{id}` to withdraw an open request and recreate it if changes are needed

## Signature Qualities

Choose the appropriate signature quality based on your requirements:

| Quality | Description | Use Case |
|---------|-------------|----------|
| `SES` | Simple Electronic Signature | Low-risk documents, internal approvals |
| `AES` | Advanced Electronic Signature | Standard contracts, agreements |
| `QES` | Qualified Electronic Signature | High-value contracts, regulatory requirements |
| `DEMO` | Test signature | Development and testing |

### Legislation Compliance

| Legislation | Region | Standard |
|-------------|--------|----------|
| `ZERTES` | Switzerland | Swiss Federal Act on Electronic Signatures |
| `EIDAS` | European Union | EU Regulation on electronic identification |

## Customizing the Signing URL

When a signature request is created, each signer receives a `signing_url`. This URL forwards the signer to a preview page of the PDF document where they can start the signature process as usual.

You can append query parameters to the `signing_url` to control the signer's experience after completing or declining the signature process.

| Parameter | Type | Description |
| --- | --- | --- |
| `exitURL` | string (URL-encoded) | Optional URL to redirect after the signature process finished or declined. Example: `https%3A%2F%2Fgoogle.com` |
| `errorURL` | string (URL-encoded) | Optional URL to redirect in case of an error. Defaults to `exitURL`. Ignored if `exitURL` is not set. |
| `declineURL` | string (URL-encoded) | Optional URL to redirect when the user declines. Defaults to `exitURL`. Ignored if `exitURL` is not set. |
| `hidedownload` | boolean | Optional flag to hide and disable the download document option. Example: `true` |
| `redirectTimeout` | integer | Seconds before auto-redirect after signing. Min 0 (instant), max 90. Default: 45. |
| `lang` | string | Language for the signing page: `en` (default), `de`, `fr`. |

```javascript
const signingUrlWithParams = `${signer.signing_url}?exitURL=${encodeURIComponent('https://your-app.com/done')}&lang=de&hidedownload=true`;
```

:::note

Missing signer fields like first name and last name can be adjusted by the signer directly through the Skribble signing interface — they are not required to be set upfront via the API.

:::

## Reminders

### Sending Manual Reminders

```bash
POST /signature-requests/{id}/remind
```

### Reminder Limitations

- **Rate limit**: One reminder per signer per hour
- Only works for signers who haven't completed signing
- Consider using automatic reminders for better UX

## Request Ownership

By default, the API user creating a signature request is the owner. To assign ownership to a different business member, set the `creator` field:

```json
{
  "title": "Contract Agreement",
  "creator": "colleague@company.com",
  "document": { "url": "https://..." },
  "signatures": [...]
}
```

## Deleting and Cleaning Up Data

### Deletion Cascades

Deleting cascades to related resources and cleans up orphan leaves:

- **`DELETE /signature-requests/{id}`** also deletes the request's documents that are **not referenced by another signature request**.
- **`DELETE /documents/{id}`** also deletes the parent document when **no other reference remains**.

This means a document can be removed as a side effect of deleting its signature request. If you then try to delete that document directly, the API returns **`404`** (already gone) or **`403`** (cascade still in-flight — the document is about to be removed). Neither is an error to worry about.

### Bulk Cleanup

When removing many objects at once (e.g. tidying up a test account):

1. **Delete signature requests first.** Their orphan documents go with them via the cascade above.
2. **Then list remaining documents** (`GET /documents`) and delete those — these are the orphans that were never attached to a signature request.
3. **Treat `404` and `403` on document deletes as success** — `404` means already gone, `403` means the SR cascade is still in-flight (retry after a short delay). Only `401` or `5xx` indicate a real problem.

```javascript
// 1) Delete every signature request (cascades to its orphan documents)
const srs = await api.get('/signature-requests'); // returns the full set
for (const sr of srs) {
  await api.delete(`/signature-requests/${sr.id}`); // 404 ⇒ already gone
}

// 2) Mop up any remaining standalone documents
const docs = await api.get('/documents');
for (const doc of docs) {
  await api.delete(`/documents/${doc.id}`); // 404 ⇒ already gone, 403 ⇒ cascade in-flight (retry)
}
```

:::note{title="Listing is 0-indexed"}

`GET /signature-requests` paginates with `page_number` **0-indexed** — the first page is `page_number=0`. Omit `page_number` entirely to retrieve the full set in one call (pagination is disabled by default).

:::

## Common Pitfalls

### Avoid These Mistakes

1. **Caching `document_id`**: The `document_id` changes with each revision and activity on a Signature Request. Always store only the Signature Request `id` and fetch the current `document_id` and status from the API when needed
2. **Missing required fields**: Ensure all signers and document details are complete in the creation call, since v2 requests go directly to `OPEN`
3. **Ignoring status**: Always check `status_overall` before performing actions
4. **Not handling callbacks**: Rely on callbacks rather than polling for status updates
5. **Hardcoding URLs**: Use environment-specific base URLs
6. **Wrong type for `page` in visual signatures**: The `page` field in visual signature positions must be a **string** (e.g. `"page": "0"`), not an integer

### Status Checking

Always verify the current status before operations:

```javascript
const request = await api.get(`/signature-requests/${id}`);

if (request.status_overall === 'OPEN') {
  // Waiting for signatures — can send reminders
} else if (request.status_overall === 'SIGNED') {
  // All signers signed — download the signed document
} else if (request.status_overall === 'DECLINED') {
  // A signer declined — handle accordingly
} else if (request.status_overall === 'WITHDRAWN') {
  // Request was withdrawn
}
```
