# Electronic Signatures

Sign leases electronically with eIDAS-compliant advanced electronic signatures.

FairePlace provides a built-in electronic signature workflow that handles OTP verification, document signing, certificate embedding, and tamper-proof proof archival — no third-party signature service needed.

## How it works

```
1. Generate PDF     ──►  Lease document ready for signing
2. Purchase credits ──►  1 credit = 1 lease signature (all signers included)
3. Initiate signing ──►  Signers receive OTP via SMS
4. Signer validates ──►  OTP code + drawn signature
5. All signed       ──►  Signed PDF with embedded certificate
6. Proof archived   ──►  eIDAS-compliant JSON proof file
```

Each signer goes through the following flow:

```
Pending → OtpSent → Signing → Completed
                        │
                        ├──► Failed (wrong OTP, technical error)
                        └──► Expired (OTP expired, signer didn't complete)
```

## Signer types

| Type | Description |
|------|-------------|
| `PROPRIETAIRE` | Property owner — signs first |
| `LOCATAIRE` | Tenant — signs after the owner |
| `AGENT_IMMOBILIER` | Real estate agent — can sign on behalf of the owner |

## Starting a signature

### Prerequisites

1. A lease with status `Draft` or `Active`
2. A generated PDF for the lease (see [Create a Lease](/signatures/lease-workflow), step 8)
3. At least 1 signature credit (see [Payments & Credits](/payments/credits))

### Initiate the signature

```bash
curl -X POST https://api.faireplace.com/api/leases/{lease_id}/signature/initiate \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pdf_id": "pdf-6f789012-abcd-ef12-3456-7890abcdef34",
    "signers": [
      {
        "signer_type": "PROPRIETAIRE",
        "first_name": "Jean",
        "last_name": "Dupont",
        "person_id": "550e8400-e29b-41d4-a716-446655440000",
        "email": "jean.dupont@sci-rivoli.com",
        "phone": "+33612345678"
      },
      {
        "signer_type": "LOCATAIRE",
        "first_name": "Marie",
        "last_name": "Martin",
        "person_id": "550e8400-e29b-41d4-a716-446655440001",
        "email": "marie.martin@email.com",
        "phone": "+33698765432"
      }
    ]
  }'
```

```json
{
  "document_signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "event_id": "signature_550e8400-e29b-41d4-a716-446655440001_1704985200",
  "pdf_url": "https://api.faireplace.com/api/media/serve/550e8400-e29b-41d4-a716-446655440002",
  "media_id": "550e8400-e29b-41d4-a716-446655440002",
  "signature_requests": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440003",
      "signer_type": "PROPRIETAIRE",
      "signer_email": "jean.dupont@sci-rivoli.com",
      "status": "PENDING",
      "expires_at": "2026-03-05T10:08:00Z"
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440004",
      "signer_type": "LOCATAIRE",
      "signer_email": "marie.martin@email.com",
      "status": "PENDING",
      "expires_at": "2026-03-05T10:08:00Z"
    }
  ],
  "status": "SIGNING"
}
```

The first signer (`Proprietaire`) immediately receives an OTP via SMS.

## Signature statuses

### Overall signature status

| Status | Description |
|--------|-------------|
| `Pending` | Signature initiated, waiting for signers |
| `InProgress` | At least one signer has started |
| `Completed` | All signers have signed successfully |
| `Failed` | A signer failed (wrong OTP, rejected) |
| `Expired` | Signature expired before completion (14 days default) |
| `Cancelled` | Signature was cancelled by the initiator |

### Individual signer status

| Status | Description |
|--------|-------------|
| `Pending` | Waiting for their turn (signers sign in order) |
| `OtpSent` | OTP sent via SMS, waiting for validation |
| `Signing` | OTP validated, signer is drawing their signature |
| `Completed` | Signature captured and embedded |
| `Failed` | OTP validation failed (3 attempts max) |
| `Expired` | OTP expired (5 minutes validity) |

## Checking status

Poll the signature status to track progress:

```bash
curl https://api.faireplace.com/api/leases/{lease_id}/signature/status \
  -H "Authorization: Bearer $API_KEY"
```

```json
{
  "document_signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "event_id": "signature_550e8400-e29b-41d4-a716-446655440001_1704985200",
  "pdf_url": "https://api.faireplace.com/api/media/serve/550e8400-e29b-41d4-a716-446655440002",
  "media_id": "550e8400-e29b-41d4-a716-446655440002",
  "signature_requests": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440003",
      "signer_type": "PROPRIETAIRE",
      "signer_email": "jean.dupont@sci-rivoli.com",
      "status": "SIGNED",
      "expires_at": "2026-03-05T10:08:00Z"
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440004",
      "signer_type": "LOCATAIRE",
      "signer_email": "marie.martin@email.com",
      "status": "OTP_SENT",
      "expires_at": "2026-03-05T10:08:00Z"
    }
  ],
  "status": "SIGNING"
}
```

> **Tip:** Use webhooks to get real-time notifications instead of polling. See [Payments & Credits](/payments/credits) for webhook configuration.

## Downloading the signed document

Once status is `Completed`:

```bash
# Download signed PDF
curl https://api.faireplace.com/api/leases/{lease_id}/signature/document/{document_signature_id}/status \
  -H "Authorization: Bearer $API_KEY" \
  -o signed-lease.pdf
```

The signed PDF includes:
- Each signer's drawn signature on the signature pages
- An embedded digital certificate (PAdES-B format)
- A tamper-proof seal — any modification invalidates the document

## Retrieving the proof certificate

The proof file provides a legally admissible record of the signing process:

```bash
curl https://api.faireplace.com/api/leases/{lease_id}/signature/proof \
  -H "Authorization: Bearer $API_KEY"
```

```json
{
  "status": "AVAILABLE",
  "proof": {
    "version": "1.0",
    "proof_id": "proof-550e8400-e29b-41d4-a716-446655440000",
    "generated_at": "2026-02-19T16:45:01Z",
    "document": {
      "document_signature_id": "550e8400-e29b-41d4-a716-446655440000",
      "lease_id": "550e8400-e29b-41d4-a716-446655440001",
      "original_document_hash": "a1b2c3d4e5f6...",
      "signed_document_hash": "f0e1d2c3b4a5...",
      "page_count": 12,
      "created_at": "2026-02-19T10:08:00Z",
      "completed_at": "2026-02-19T16:45:00Z"
    },
    "signers": [
      {
        "signer_type": "PROPRIETAIRE",
        "identity": {
          "email": "jean.dupont@sci-rivoli.com",
          "phone": "+33612345678",
          "first_name": "Jean",
          "last_name": "Dupont"
        },
        "process": {
          "otp_validated_at": "2026-02-19T14:29:50Z",
          "signature_completed_at": "2026-02-19T14:30:00Z",
          "ip_address": "92.184.xxx.xxx",
          "user_agent": "Mozilla/5.0..."
        }
      },
      {
        "signer_type": "LOCATAIRE",
        "identity": {
          "email": "marie.martin@email.com",
          "phone": "+33698765432",
          "first_name": "Marie",
          "last_name": "Martin"
        },
        "process": {
          "otp_validated_at": "2026-02-19T16:44:50Z",
          "signature_completed_at": "2026-02-19T16:45:00Z",
          "ip_address": "86.247.xxx.xxx",
          "user_agent": "Mozilla/5.0..."
        }
      }
    ],
    "compliance": {
      "eidas_level": "Advanced Electronic Signature",
      "signature_format": "PAdES-B",
      "hash_algorithm": "SHA-256",
      "retention_years": 10
    }
  },
  "pdf_url": "https://api.faireplace.com/api/media/serve/proof-pdf-id",
  "json_url": "https://api.faireplace.com/api/media/serve/proof-json-id"
}
```

## Cancelling a signature

Cancel a pending or in-progress signature:

```bash
curl -X POST https://api.faireplace.com/api/leases/{lease_id}/signature/cancel \
  -H "Authorization: Bearer $API_KEY"
```

> Cancelling does not refund the signature credit.

## eIDAS compliance

FairePlace electronic signatures meet the **Advanced Electronic Signature (AdES)** level under the EU eIDAS regulation:

| Requirement | Implementation |
|-------------|---------------|
| Uniquely linked to signer | OTP sent to signer's personal mobile phone |
| Capable of identifying signer | Identity verified via phone number + email |
| Under sole control of signer | OTP valid for 5 minutes, 3 attempts max |
| Linked to data integrity | PAdES-B embedded signature with SHA-256 hash |
| Certificate Authority | Certinomis (French qualified trust service provider) |
| Timestamp | Qualified timestamp from Certinomis TSA |
| Proof archive | JSON proof file with all verification details |

## When things go wrong

Real integrations hit edge cases. Here's how to handle each one.

### OTP not received

The signer didn't get the SMS within 60 seconds.

1. Verify the phone number is in international format (`+33612345678`, not `0612345678`)
2. Confirm it's a mobile number (landlines can't receive SMS)
3. Resend the OTP:

```bash
curl -X POST https://api.faireplace.com/api/leases/{lease_id}/signature/resend/LOCATAIRE \
  -H "Authorization: Bearer $API_KEY"
```

The previous OTP is immediately invalidated.

### Wrong OTP entered (3 attempts max)

After 3 failed attempts, the OTP is locked for 5 minutes:

```json
{
  "error": {
    "code": 429,
    "type": "OTP_LOCKED",
    "message": "Maximum OTP attempts reached. Request a new OTP.",
    "details": {"locked_until": "2026-03-17T15:40:00Z"}
  }
}
```

**What to do:** Wait for the lockout to expire, then resend a new OTP. Don't retry the same code.

### Signature expired (14-day default)

If signers don't complete within 14 days, the entire signature process expires.

**What to do:**
1. The signature credit is consumed (not refunded)
2. Regenerate the PDF if lease data changed: `POST /leases/{id}/pdf`
3. Initiate a new signature: `POST /leases/{id}/signature/initiate`
4. This consumes another credit

### Insufficient credits

```json
{
  "error": {
    "code": 402,
    "type": "PAYMENT_REQUIRED",
    "message": "Insufficient signature credits",
    "details": {"credits_required": 1, "credits_available": 0}
  }
}
```

**What to do:** Purchase credits via `POST /credits/checkout`. See [Payments & Credits](/payments/credits).

### Signer order matters

Signers sign in order. The owner (`PROPRIETAIRE`) must sign before the tenant (`LOCATAIRE`). If you list the tenant first, the owner still signs first — the API enforces the legal signing order.

### Concurrent signature attempts

You cannot initiate a new signature while one is already in progress for the same lease. Cancel the existing one first:

```bash
curl -X POST https://api.faireplace.com/api/leases/{lease_id}/signature/cancel \
  -H "Authorization: Bearer $API_KEY"
```

## Error handling

### Insufficient credits

```json
{
  "error": {
    "code": 402,
    "type": "PAYMENT_REQUIRED",
    "message": "Insufficient signature credits",
    "details": {
      "credits_required": 1,
      "credits_available": 0
    }
  }
}
```

**Fix:** Purchase credits via [Payments & Credits](/payments/credits).

### OTP expired

If a signer's OTP expires, you can resend it:

```bash
curl -X POST https://api.faireplace.com/api/leases/{lease_id}/signature/resend/{signer_type} \
  -H "Authorization: Bearer $API_KEY"
```

### Invalid phone number

```json
{
  "error": {
    "code": 422,
    "type": "VALIDATION_ERROR",
    "message": "Invalid phone number for signer",
    "details": {
      "signer": "Marie Martin",
      "phone": "+336987",
      "message": "Phone number must be a valid international format"
    }
  }
}
```

---

## Known limitations

- **No bulk signing** — Each lease requires a separate signature initiation. No batch API.
- **SMS only** — OTP delivery is SMS-only. No email, WhatsApp, or authenticator app support.
- **French mobile numbers preferred** — International numbers work but delivery is not guaranteed for all carriers.
- **14-day expiration** — Not configurable. After 14 days, the signature expires and a new one must be initiated.
- **No partial signatures** — If 1 of 2 signers completes but the other doesn't, the entire process expires. The completed signer must re-sign.
- **Credit consumed on initiation** — Credits are consumed when the signature is initiated, not when it's completed. Expired or cancelled signatures are not refunded.

## Related

- **[Create a Lease End-to-End](/signatures/lease-workflow)** — Full workflow including signature
- **[Payments & Credits](/payments/credits)** — Purchase signature credits
- **[Leases API](/leases/leases)** — PDF generation and signature endpoints
