# Task: Enviar un documento para firmar

I want my application to do what Lacuna's "Enviar un documento para firmar" demo does: Envíe un PDF y recorra el flujo como lo haría su firmante: la invitación llega por correo electrónico y un código enviado por SMS confirma la firma.

- Docs: https://docs.lacunasoftware.com/en-us/articles/signer/get-started
- Live demo: https://try.lacunasoftware.com/es/signer/sign-a-document
- This demo for AI agents: https://try.lacunasoftware.com/es/signer/sign-a-document.md

## Samples

- .NET (console): [Sign a document: create it with one signer](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/CreateDocumentWithOneSignerScenario.cs)
- Java (console): [Sign a document: create it with one signer](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/CreateDocumentWithOneSignerScenario.java)
- Node.js (console): [Sign a document: create it with one signer](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/createDocumentWithOneSignerScenario.ts)
- PHP (console): [Sign a document: create it with one signer](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/CreateDocumentWithOneSignerScenario.php)

## Call sequence

Recorded on 2026-10-14 against the real products with Lacuna's public test certificates and data. Keys, tokens, sessions, ids, file contents and personal data are masked.

### 1. Dropsigner takes the PDF, creates the document for the signer to sign electronically with an SMS code, and emails them the invitation; it is then read back for the Status panel.

On the server: `SignerClient.UploadFileAsync, then SignerClient.CreateDocumentAsync, then SignerClient.GetDocumentDetailsAsync`

`POST https://www.dropsigner.com/api/uploads` → 200

Request:

```json
"<multipart/form-data, 2138 bytes>"
```

Response:

```json
{
  "location": "/api/uploads/00000000-0000-0000-0000-000000000000?access_ticket={value}&culture=en&contentType={value}",
  "id": "00000000-0000-0000-0000-000000000000",
  "name": "Lacuna-Test-Contract.pdf",
  "contentType": "application/pdf"
}
```

`POST https://www.dropsigner.com/api/documents` → 200

Request:

```json
{
  "AreFilesScanned": false,
  "Files": [
    {
      "DisplayName": "Lacuna-Test-Contract",
      "Id": "00000000-0000-0000-0000-000000000000",
      "Name": "Lacuna-Test-Contract.pdf",
      "ContentType": "application/pdf"
    }
  ],
  "IsEnvelope": false,
  "FlowActions": [
    {
      "User": {
        "Name": "Alan Mathison Turing",
        "Identifier": "***.***.***-**",
        "Email": "someone@example.com",
        "Phone": "+55 ** *****-****"
      },
      "Type": "Signer",
      "Step": 1,
      "AllowElectronicSignature": true,
      "RequireSmsAuthenticationToSignElectronically": true,
      "RequireSelfieAuthenticationToSignElectronically": false,
      "RequireDatavalidAuthenticationToSignElectronically": false,
      "RequireLivenessAuthenticationToSignElectronically": false
    }
  ],
  "DisablePendingActionNotifications": false,
  "DisableNotifications": false,
  "ForceCadesSignature": false,
  "SignatureType": 0,
  "ExpirationDate": "2026-10-20T00:00:00"
}
```

Response:

```json
[
  {
    "uploadId": "00000000-0000-0000-0000-000000000000",
    "documentId": "00000000-0000-0000-0000-000000000000"
  }
]
```

`GET https://www.dropsigner.com/api/documents/00000000-0000-0000-0000-000000000000` → 200

Response:

```json
{
  "checksumMd5": "<hex, 16 bytes>",
  "isDeleted": false,
  "flowActions": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "shouldNotarize": false,
      "creationDate": "2026-10-14T02:59:48.8337097+00:00",
      "pendingDate": "2026-10-14T02:59:48.8340471+00:00",
      "updateDate": "2026-10-14T02:59:48.8340464+00:00",
      "user": {
        "id": "00000000-0000-0000-0000-000000000000",
        "name": "Alan Mathison Turing",
        "identifierType": "Cpf",
        "identifier": "***.***.***-**",
        "identifiers": [
          {
            "id": "00000000-0000-0000-0000-000000000000",
            "isGrant": true,
            "type": "Cpf",
            "value": "***.***.***-**"
          }
        ],
        "email": "someone@example.com",
        "phone": "+55 ** *****-****",
        "createdBySubscriptionId": "00000000-0000-0000-0000-000000000000"
      },
      "numberRequiredSignatures": 0,
      "numberRequiredActions": 0,
      "allowElectronicSignature": true,
      "requireEmailAuthenticationToSignElectronically": true,
      "requireSmsAuthenticationToSignElectronically": true,
      "requireWhatsappAuthenticationToSignElectronically": false,
      "requireAuthenticatorAppToSignElectronically": false,
      "requireSelfieAuthenticationToSignElectronically": false,
      "requireDatavalidAuthenticationToSignElectronically": false,
      "requirePixAuthenticationToSignElectronically": false,
      "requireLivenessAuthenticationToSignElectronically": false,
      "requireIdScanAuthenticationToSignElectronically": false,
      "requireCompanyCertificate": false,
      "signatureInitialsMode": "None",
      "isElectronic": false,
      "allowRuleFlowToContinueIfRefused": false,
      "allowIdrcSignature": false,
      "notificationLanguage": "Pt",
      "type": "Signer",
      "status": "Pending",
      "step": 1
    }
  ],
  "observers": [],
  "permissions": {
    "move": true,
    "editFlow": true,
    "viewSignedDocumentWithExternalFlowBeforeCompleted": true,
    "submitExternalFlowFiles": true,
    "updateDocumentVersion": true,
    "createAttachments": true,
    "deleteAttachments": true,
    "viewPrivateAttachments": true,
    "editAttachments": true,
    "createMeeting": true,
    "deleteMeeting": true,
    "refuse": true,
    "editInformation": true,
    "cancel": true,
    "editParticipantInformation": false
  },
  "pending": {},
  "hideDownloadOptionForPendingDocuments": false,
  "id": "00000000-0000-0000-0000-000000000000",
  "qrCode": "***",
  "name": "Lacuna-Test-Contract",
  "key": "***",
  "filename": "Lacuna-Test-Contract.pdf",
  "fileSize": 5425,
  "mimeType": "application/pdf",
  "hasSignature": false,
  "status": "Pending",
  "isConcluded": false,
  "organization": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "Lacuna Demo Site"
  },
  "isOwner": false,
  "creationDate": "2026-10-14T02:59:48.8336845+00:00",
  "updateDate": "2026-10-14T02:59:48.8336856+00:00",
  "expirationDate": "2026-10-21T02:59:59.9999999+00:00",
  "expirationDateWithoutTime": "2026-10-20",
  "createdBy": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "Lacuna Demo Site"
  },
  "hasExternalFlow": false,
  "forceCadesSignature": false,
  "isScanned": false,
  "isEnvelope": false,
  "signatureType": "None"
}
```

### 2. The Status panel polls the document for its status and its signer as Dropsigner knows them.

On the server: `SignerClient.GetDocumentDetailsAsync`

`GET https://www.dropsigner.com/api/documents/00000000-0000-0000-0000-000000000000` → 200

Response:

```json
{
  "checksumMd5": "<hex, 16 bytes>",
  "isDeleted": false,
  "flowActions": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "shouldNotarize": false,
      "creationDate": "2026-10-14T02:59:48.8337097+00:00",
      "pendingDate": "2026-10-14T02:59:48.8340471+00:00",
      "updateDate": "2026-10-14T02:59:48.8340464+00:00",
      "user": {
        "id": "00000000-0000-0000-0000-000000000000",
        "name": "Alan Mathison Turing",
        "identifierType": "Cpf",
        "identifier": "***.***.***-**",
        "identifiers": [
          {
            "id": "00000000-0000-0000-0000-000000000000",
            "isGrant": true,
            "type": "Cpf",
            "value": "***.***.***-**"
          }
        ],
        "email": "someone@example.com",
        "phone": "+55 ** *****-****",
        "createdBySubscriptionId": "00000000-0000-0000-0000-000000000000"
      },
      "numberRequiredSignatures": 0,
      "numberRequiredActions": 0,
      "allowElectronicSignature": true,
      "requireEmailAuthenticationToSignElectronically": true,
      "requireSmsAuthenticationToSignElectronically": true,
      "requireWhatsappAuthenticationToSignElectronically": false,
      "requireAuthenticatorAppToSignElectronically": false,
      "requireSelfieAuthenticationToSignElectronically": false,
      "requireDatavalidAuthenticationToSignElectronically": false,
      "requirePixAuthenticationToSignElectronically": false,
      "requireLivenessAuthenticationToSignElectronically": false,
      "requireIdScanAuthenticationToSignElectronically": false,
      "requireCompanyCertificate": false,
      "signatureInitialsMode": "None",
      "isElectronic": false,
      "allowRuleFlowToContinueIfRefused": false,
      "allowIdrcSignature": false,
      "notificationLanguage": "Pt",
      "type": "Signer",
      "status": "Pending",
      "step": 1
    }
  ],
  "observers": [],
  "permissions": {
    "move": true,
    "editFlow": true,
    "viewSignedDocumentWithExternalFlowBeforeCompleted": true,
    "submitExternalFlowFiles": true,
    "updateDocumentVersion": true,
    "createAttachments": true,
    "deleteAttachments": true,
    "viewPrivateAttachments": true,
    "editAttachments": true,
    "createMeeting": true,
    "deleteMeeting": true,
    "refuse": true,
    "editInformation": true,
    "cancel": true,
    "editParticipantInformation": false
  },
  "pending": {},
  "hideDownloadOptionForPendingDocuments": false,
  "id": "00000000-0000-0000-0000-000000000000",
  "qrCode": "***",
  "name": "Lacuna-Test-Contract",
  "key": "***",
  "filename": "Lacuna-Test-Contract.pdf",
  "fileSize": 5425,
  "mimeType": "application/pdf",
  "hasSignature": false,
  "status": "Pending",
  "isConcluded": false,
  "organization": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "Lacuna Demo Site"
  },
  "isOwner": false,
  "creationDate": "2026-10-14T02:59:48.8336845+00:00",
  "updateDate": "2026-10-14T02:59:48.8336856+00:00",
  "expirationDate": "2026-10-21T02:59:59.9999999+00:00",
  "expirationDateWithoutTime": "2026-10-20",
  "createdBy": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "Lacuna Demo Site"
  },
  "hasExternalFlow": false,
  "forceCadesSignature": false,
  "isScanned": false,
  "isEnvelope": false,
  "signatureType": "None"
}
```

### 3. The Status panel looks among the document's events for the signer opening it, which the typed client has no call for.

On the server: `GET api/documents/{id}/events`

`GET https://www.dropsigner.com/api/documents/00000000-0000-0000-0000-000000000000/events` → 200

Response:

```json
[
  {
    "id": 0,
    "agent": {
      "id": "00000000-0000-0000-0000-000000000000",
      "type": "Application",
      "name": "Lacuna Demo Site"
    },
    "type": "DocumentCreated",
    "date": "2026-10-14T02:59:48.8340627+00:00",
    "hasDetails": false
  }
]
```

### 4. Start over cancels the document with a reason saying the visitor started over, and Dropsigner emails the signer that it was canceled.

On the server: `SignerClient.CancelDocumentAsync`

`POST https://www.dropsigner.com/api/documents/00000000-0000-0000-0000-000000000000/cancellation` → 200

Request:

```json
{
  "Reason": "The visitor started over in the Lacuna Demo Site."
}
```

## Patterns to copy

- Upload the PDF first, then create the document with the upload's id as its file, and read the new document's id from the single item the create call returns. A `SignerApiException` with code `FileHasPassword` or `CpfAlreadyBeingUsed` is a problem with the user's input, not an outage.
- Read the document back and treat its first flow action's user as the truth rather than what you sent: Dropsigner looks signers up by email and keeps a known user's stored name, CPF and phone.
- Follow progress by polling the document (`GetDocumentDetailsAsync`): Concluded, Canceled and Expired are final, Refused is not. The Demo polls every 5 s for the first 10 minutes, then every 30 s.
- Seeing that the signer opened the document takes `GET /api/documents/{id}/events`, which the typed client lacks: read it into your own type and count only a `DocumentViewed` whose agent email is the signer's.
- Ask for a fresh download ticket (`GetDocumentDownloadTicketAsync`, `Signatures` or `PrinterFriendlyVersion`) on each click: the link needs no authentication, lasts an hour and carries a one-time token, so never log it.

## What to do

Implement this in my project with its existing stack, language, framework and conventions, following the call sequence and the patterns above, and adapting the samples rather than copying them whole. Ask me before adding any dependency or package. Keep API keys and private keys out of the browser and out of the repository.
