# Request links

> Ask one customer for documents, camera captures, answers and identity checks through a single link, and get everything back in one record.

A **request link** is a custom information request for one customer. You compose what you need, Speedmap gives you a link (or sends it by email or SMS), the customer answers everything on one branded, mobile-first page, and you read it all back in one record: files, answers, capture results and verification references.

Typical use: a customer you already onboarded, where you later need more evidence. "Upload your NIN slip." "A photo of a recent utility bill." "A selfie holding your ID." "Your current address." Or a real NIN or BVN check, run through the engine like any other verification.

## Item types

Up to **15 items**, answered in the order you give them. Each item has an optional `label` and `instructions` (plain text, shown to the customer) and a `required` flag (default `true`; optional items can be skipped).

| Type | What the customer does | What you get |
| --- | --- | --- |
| `upload` | Picks a file or takes a photo: JPG, PNG or PDF, up to **10 MB** each. `accept` limits the types, `multiple` allows up to 5 files. | The file(s), encrypted at rest, downloadable through the API or the dashboard. **Collected only**: no OCR or matching. |
| `capture` | A guided camera step from the capture SDK: `document`, `selfie`, `selfie_with_id` (selfie, then the document, face-matched) or `liveness`. `document_types` limits the documents accepted. | A capture session with one verification per step (billed as [Document](https://speedmap.io/docs/products/document) and [Liveness](https://speedmap.io/docs/products/liveness)). |
| `question` | Answers in the `format` you set: `text`, `date` (YYYY-MM-DD), `select` (one of `options`) or `yes_no`. | The answer, sealed at rest and returned in clear on the request. |
| `verification` | Types the inputs the `product` needs: `nin` (NIN plus a selfie), `bvn` (BVN, name, date of birth), `account` (account number and bank) or `face` (selfie). | A normal verification linked by `reference_id`, billed at the product's price. The outcome is never shown to the customer. |

An upload labelled "NIN slip" is just a file. A `verification` item with `product: "nin"` runs the real check.

## What it costs

- Every **completed** request in live mode is charged the **request fee** (currently ₦50) to your wallet, as a ledger charge noted "Request link". The fee is held when the link is created, charged when the customer finishes, and released if the link expires or you cancel it. If the wallet cannot cover the hold, creation fails with `402 insufficient_balance`.
- Verifications and captures inside the request are charged at their normal price when they run, exactly like a direct API call.
- Sandbox requests are free, SMS is simulated, and checks answer with the usual [test values](https://speedmap.io/docs/sandbox).

## Create a request

`POST /v1/requests`

**Body parameters**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | array | Yes | 1 to 15 items (see the table above). |
| `subject` | object | No | `name`, `email`, `phone`, `external_id`. Sealed at rest; email and phone are returned masked. Required for delivery: `email` to send by email, `phone` (Nigerian mobile or E.164) to send by SMS. |
| `message` | string | No | Shown to the customer above the items. Plain text, up to 1,000 characters. |
| `delivery` | string[] | No | `["email"]`, `["sms"]` or both. Empty (default): you send the `url` yourself. |
| `expires_in_days` | integer | No | 1 to 30. Default 7. |
| `reason` | string | No | [Purpose code](https://speedmap.io/docs/compliance#purpose-codes) for the checks inside the request. Default `financialProducts`. |
| `brand` | object | No | `name`, `logo_url`, `color` (6-digit hex) for the customer page. Default: your organization name. |
| `metadata` | object | No | Up to 20 keys, returned on the request and in the webhook. |

**Request**

_cURL_

```bash
curl -X POST "https://api.speedmap.io/v1/requests" \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "type": "upload",
      "label": "NIN slip",
      "instructions": "A clear photo or scan of the slip.",
      "accept": [
        "jpg",
        "png",
        "pdf"
      ]
    },
    {
      "type": "upload",
      "label": "Utility bill",
      "instructions": "Issued in the last 3 months.",
      "accept": [
        "pdf",
        "jpg"
      ],
      "required": false
    },
    {
      "type": "capture",
      "kind": "selfie_with_id"
    },
    {
      "type": "question",
      "format": "text",
      "label": "Current residential address"
    },
    {
      "type": "question",
      "format": "select",
      "label": "Employment status",
      "options": [
        "Employed",
        "Self-employed",
        "Student",
        "Retired"
      ]
    },
    {
      "type": "verification",
      "product": "nin"
    }
  ],
  "subject": {
    "name": "Clarke Griffin",
    "email": "clarke@example.com",
    "phone": "08031234567",
    "external_id": "CUST-42"
  },
  "message": "To finish your loan application we need a few more things. It takes about five minutes.",
  "delivery": [
    "email",
    "sms"
  ],
  "expires_in_days": 7
}'
```

_Node.js_

```javascript
const res = await fetch("https://api.speedmap.io/v1/requests", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SPEEDMAP_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "items": [
      {
        "type": "upload",
        "label": "NIN slip",
        "instructions": "A clear photo or scan of the slip.",
        "accept": [
          "jpg",
          "png",
          "pdf"
        ]
      },
      {
        "type": "upload",
        "label": "Utility bill",
        "instructions": "Issued in the last 3 months.",
        "accept": [
          "pdf",
          "jpg"
        ],
        "required": false
      },
      {
        "type": "capture",
        "kind": "selfie_with_id"
      },
      {
        "type": "question",
        "format": "text",
        "label": "Current residential address"
      },
      {
        "type": "question",
        "format": "select",
        "label": "Employment status",
        "options": [
          "Employed",
          "Self-employed",
          "Student",
          "Retired"
        ]
      },
      {
        "type": "verification",
        "product": "nin"
      }
    ],
    "subject": {
      "name": "Clarke Griffin",
      "email": "clarke@example.com",
      "phone": "08031234567",
      "external_id": "CUST-42"
    },
    "message": "To finish your loan application we need a few more things. It takes about five minutes.",
    "delivery": [
      "email",
      "sms"
    ],
    "expires_in_days": 7
  }),
});

const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data);
```

_Python_

```python
import os, requests

res = requests.post(
    "https://api.speedmap.io/v1/requests",
    headers={
        "Authorization": f"Bearer {os.environ['SPEEDMAP_API_KEY']}",
    },
    json={
        "items": [
            {
                "type": "upload",
                "label": "NIN slip",
                "instructions": "A clear photo or scan of the slip.",
                "accept": [
                    "jpg",
                    "png",
                    "pdf"
                ]
            },
            {
                "type": "upload",
                "label": "Utility bill",
                "instructions": "Issued in the last 3 months.",
                "accept": [
                    "pdf",
                    "jpg"
                ],
                "required": False
            },
            {
                "type": "capture",
                "kind": "selfie_with_id"
            },
            {
                "type": "question",
                "format": "text",
                "label": "Current residential address"
            },
            {
                "type": "question",
                "format": "select",
                "label": "Employment status",
                "options": [
                    "Employed",
                    "Self-employed",
                    "Student",
                    "Retired"
                ]
            },
            {
                "type": "verification",
                "product": "nin"
            }
        ],
        "subject": {
            "name": "Clarke Griffin",
            "email": "clarke@example.com",
            "phone": "08031234567",
            "external_id": "CUST-42"
        },
        "message": "To finish your loan application we need a few more things. It takes about five minutes.",
        "delivery": [
            "email",
            "sms"
        ],
        "expires_in_days": 7
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
```

**Response 201**

```json
{
  "id": "req_7f3a1c9e2b4d6f80",
  "object": "request",
  "status": "pending",
  "mode": "live",
  "url": "https://speedmap.io/r/Qm9wZS1pcy1ub3QtYS1zdHJhdGVneS0yMDI2",
  "subject": {
    "name": "Clarke Griffin",
    "email": "c*****@example.com",
    "phone": "**********4567",
    "external_id": "CUST-42"
  },
  "message": "To finish your loan application we need a few more things. It takes about five minutes.",
  "items": [
    {
      "index": 0,
      "type": "upload",
      "label": "NIN slip",
      "required": true,
      "accept": [
        "jpg",
        "png",
        "pdf"
      ],
      "multiple": false,
      "status": "pending",
      "files": []
    },
    {
      "index": 1,
      "type": "upload",
      "label": "Utility bill",
      "required": false,
      "accept": [
        "pdf",
        "jpg"
      ],
      "multiple": false,
      "status": "pending",
      "files": []
    },
    {
      "index": 2,
      "type": "capture",
      "label": "Selfie and ID document",
      "kind": "selfie_with_id",
      "required": true,
      "status": "pending"
    },
    {
      "index": 3,
      "type": "question",
      "label": "Current residential address",
      "format": "text",
      "required": true,
      "status": "pending"
    },
    {
      "index": 4,
      "type": "question",
      "label": "Employment status",
      "format": "select",
      "options": [
        "Employed",
        "Self-employed",
        "Student",
        "Retired"
      ],
      "required": true,
      "status": "pending"
    },
    {
      "index": 5,
      "type": "verification",
      "label": "NIN check",
      "product": "nin",
      "required": true,
      "status": "pending",
      "reference_id": null
    }
  ],
  "delivery": {
    "email": {
      "requested_at": "2026-10-30T09:12:04.118Z",
      "outbox_id": "eml_2c1f0a9b8d7e6f54",
      "to": "c*****@example.com",
      "status": "queued"
    },
    "sms": {
      "requested_at": "2026-10-30T09:12:04.402Z",
      "status": "sent",
      "provider": "fintava_sms",
      "to": "**********4567"
    }
  },
  "fee_kobo": 5000,
  "fee_status": "held",
  "consent_at": null,
  "expires_at": "2026-11-06T09:12:04.000Z",
  "completed_at": null,
  "created_at": "2026-10-30T09:12:04.000Z"
}
```

> **The url is shown once:** Speedmap stores a hash of the link, not the link. Copy `url` from the create response, or let Speedmap deliver it. **Resend** issues a new link (the old one stops working) and sends it again.

## What the customer sees

The link opens `speedmap.io/r/{token}`: your name, your message, the list of items and a consent notice. The customer accepts the notice, then answers the items in order; progress is saved per item, so they can close the page and come back. Verification items collect the inputs on the page (the number, a selfie) and show only "received" or "try again", never the record. Expired, cancelled and completed links show a clear state.

## Read the record

`GET /v1/requests/{id}`

**Items on a completed request**

```json
{
  "id": "req_7f3a1c9e2b4d6f80",
  "object": "request",
  "status": "completed",
  "mode": "live",
  "subject": {
    "name": "Clarke Griffin",
    "email": "c*****@example.com",
    "phone": "**********4567",
    "external_id": "CUST-42"
  },
  "message": "To finish your loan application we need a few more things. It takes about five minutes.",
  "items": [
    {
      "index": 0,
      "type": "upload",
      "label": "NIN slip",
      "status": "done",
      "files": [
        {
          "id": "rqf_9a8b7c6d5e4f3a21",
          "filename": "nin-slip.jpg",
          "mime": "image/jpeg",
          "size_bytes": 418221,
          "sha256": "3f2c…",
          "purged": false
        }
      ]
    },
    {
      "index": 1,
      "type": "upload",
      "label": "Utility bill",
      "status": "skipped",
      "files": []
    },
    {
      "index": 2,
      "type": "capture",
      "label": "Selfie and ID document",
      "status": "done",
      "capture_session_id": "cap_1d2e3f4a5b6c7d8e",
      "result": {
        "steps": {
          "selfie": {
            "reference_id": "spm_5e6f7a8b9c0d1e2f3a4b5c6d",
            "status": "verified",
            "match": null
          },
          "document": {
            "reference_id": "spm_6f7a8b9c0d1e2f3a4b5c6d7e",
            "status": "verified",
            "match": true
          }
        }
      }
    },
    {
      "index": 3,
      "type": "question",
      "label": "Current residential address",
      "status": "done",
      "answer": "12 Adetokunbo Ademola Crescent, Wuse II, Abuja"
    },
    {
      "index": 4,
      "type": "question",
      "label": "Employment status",
      "status": "done",
      "answer": "Employed"
    },
    {
      "index": 5,
      "type": "verification",
      "label": "NIN check",
      "status": "done",
      "reference_id": "spm_3f9c1a7b2e4d6f8a0b1c2d3e",
      "result": {
        "status": "verified",
        "match": true
      },
      "verification": {
        "reference_id": "spm_3f9c1a7b2e4d6f8a0b1c2d3e",
        "product": "nin",
        "method": "in_person",
        "status": "verified",
        "match": true
      }
    }
  ],
  "delivery": {
    "email": {
      "requested_at": "2026-10-30T09:12:04.118Z",
      "outbox_id": "eml_2c1f0a9b8d7e6f54",
      "to": "c*****@example.com",
      "status": "queued"
    },
    "sms": {
      "requested_at": "2026-10-30T09:12:04.402Z",
      "status": "sent",
      "provider": "fintava_sms",
      "to": "**********4567"
    }
  },
  "fee_kobo": 5000,
  "fee_status": "charged",
  "consent_at": "2026-10-30T13:58:40.221Z",
  "expires_at": "2026-11-06T09:12:04.000Z",
  "completed_at": "2026-10-30T14:03:11.004Z",
  "created_at": "2026-10-30T09:12:04.000Z"
}
```

Each item carries its `status` (`pending`, `done`, `skipped`) and, on the single-request read, its `answer`, `files`, `verification` (the [assertion](https://speedmap.io/docs/response-shaping)) or the capture `result` with one reference per step. Download a file with `GET /v1/requests/{id}/files/{file_id}`; every download is recorded in your audit log as a reveal. Files and sealed answers are purged under your [retention policy](https://speedmap.io/docs/compliance#data-retention); the record of what was asked stays.

## Resend, cancel, list

`POST /v1/requests/{id}/resend`

`POST /v1/requests/{id}/cancel`

`GET /v1/requests`

`resend` takes an optional `channels` array (default: the channels used before) and returns the request with a fresh `url`. `cancel` closes the link and releases the fee hold. The list supports `status`, `limit` and `before` like every other [list](https://speedmap.io/docs/pagination).

## Templates

Save a list of items (and a message) you ask for often, then load it when you create a request.

`GET /v1/request-templates`

`POST /v1/request-templates`

`DELETE /v1/request-templates/{id}`

**Save a template**

_cURL_

```bash
curl -X POST "https://api.speedmap.io/v1/request-templates" \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Address proof",
  "message": "Please send a recent utility bill and confirm your address.",
  "items": [
    {
      "type": "upload",
      "label": "Utility bill",
      "accept": [
        "pdf",
        "jpg"
      ]
    },
    {
      "type": "question",
      "format": "text",
      "label": "Current address"
    }
  ]
}'
```

_Node.js_

```javascript
const res = await fetch("https://api.speedmap.io/v1/request-templates", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SPEEDMAP_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Address proof",
    "message": "Please send a recent utility bill and confirm your address.",
    "items": [
      {
        "type": "upload",
        "label": "Utility bill",
        "accept": [
          "pdf",
          "jpg"
        ]
      },
      {
        "type": "question",
        "format": "text",
        "label": "Current address"
      }
    ]
  }),
});

const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data);
```

_Python_

```python
import os, requests

res = requests.post(
    "https://api.speedmap.io/v1/request-templates",
    headers={
        "Authorization": f"Bearer {os.environ['SPEEDMAP_API_KEY']}",
    },
    json={
        "name": "Address proof",
        "message": "Please send a recent utility bill and confirm your address.",
        "items": [
            {
                "type": "upload",
                "label": "Utility bill",
                "accept": [
                    "pdf",
                    "jpg"
                ]
            },
            {
                "type": "question",
                "format": "text",
                "label": "Current address"
            }
        ]
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
```

## Webhook

`request.completed` fires when the customer answers the last item. It is PII-free: item statuses, verification reference ids, file ids, your `external_id` and `metadata`. Read answers and files with `GET /v1/requests/{id}`. The dashboard user who created the request also gets an in-app notice.

**request.completed**

```json
{
  "id": "evt_0c9d8e7f6a5b4c3d",
  "type": "request.completed",
  "mode": "live",
  "created_at": "2026-10-30T14:03:11.090Z",
  "data": {
    "id": "req_7f3a1c9e2b4d6f80",
    "object": "request",
    "status": "completed",
    "mode": "live",
    "external_id": "CUST-42",
    "fee_kobo": 5000,
    "items": [
      {
        "index": 0,
        "type": "upload",
        "label": "NIN slip",
        "status": "done",
        "reference_id": null,
        "capture_session_id": null,
        "result": null,
        "file_ids": [
          "rqf_9a8b7c6d5e4f3a21"
        ]
      },
      {
        "index": 1,
        "type": "upload",
        "label": "Utility bill",
        "status": "skipped",
        "reference_id": null,
        "capture_session_id": null,
        "result": null,
        "file_ids": []
      },
      {
        "index": 2,
        "type": "capture",
        "label": "Selfie and ID document",
        "status": "done",
        "reference_id": null,
        "capture_session_id": "cap_1d2e3f4a5b6c7d8e",
        "result": {
          "steps": {
            "selfie": {
              "reference_id": "spm_5e6f7a8b9c0d1e2f3a4b5c6d",
              "status": "verified",
              "match": null
            },
            "document": {
              "reference_id": "spm_6f7a8b9c0d1e2f3a4b5c6d7e",
              "status": "verified",
              "match": true
            }
          }
        }
      },
      {
        "index": 3,
        "type": "question",
        "label": "Current residential address",
        "status": "done",
        "reference_id": null,
        "capture_session_id": null,
        "result": null
      },
      {
        "index": 4,
        "type": "question",
        "label": "Employment status",
        "status": "done",
        "reference_id": null,
        "capture_session_id": null,
        "result": null
      },
      {
        "index": 5,
        "type": "verification",
        "label": "NIN check",
        "status": "done",
        "reference_id": "spm_3f9c1a7b2e4d6f8a0b1c2d3e",
        "capture_session_id": null,
        "result": {
          "status": "verified",
          "match": true
        }
      }
    ],
    "consent_at": "2026-10-30T13:58:40.221Z",
    "completed_at": "2026-10-30T14:03:11.004Z",
    "created_at": "2026-10-30T09:12:04.000Z"
  }
}
```

## Key scopes

A [scoped key](https://speedmap.io/docs/authentication#key-scopes) needs the scope of every check or capture inside the request (`nin`, `bvn`, `account`, `face`, `document`, `liveness`). A request that only collects files and answers works with any key.

## Dashboard

**Verification links → Requests** has the same builder: add, remove and reorder items, write the message, pick expiry and delivery, save or load a template. Each request has a page with every answer, file preview and download (audited), verification result, delivery status with resend, and cancel.

---

Source: https://speedmap.io/docs/request-links
