# Response shaping

> Speedmap receives the full record and decides what leaves the platform. The same check returns different payloads depending on channel and tier.

Upstream registries return whole identity records: names, date of birth, address, phone, photo. Most integrations need far less, usually "does this person exist, and do the details match?". Speedmap keeps the full record encrypted at rest and shapes each response to the channel that asked.

That is data minimisation under the NDPA, and it is what makes the MCP server defensible to a bank's security review.

## What each channel receives

| Channel | Returns | `data` |
| --- | --- | --- |
| REST API, **full tier** | Assertion plus `provider`, `provider_reference` and `data`, the normalized record | Yes |
| REST API, **assertion tier** | `status`, `match`, `field_matches`, masked `summary` and `subject`, `reference_id` | No |
| MCP server | Assertion fields only, plus a dashboard link. No PII | No |
| Webhooks | The assertion | No |
| Lists (`GET /v1/verifications`) | The assertion | No |
| Bulk jobs | A job handle. Rows are masked assertions read from the job, never inline | No |
| Dashboard | Assertion; authorized roles can reveal the full record, and every reveal is audited | On reveal |

Speedmap sets your workspace's tier based on your licence and use case; you can see it in the dashboard. The tier applies to both sandbox and live keys, so you can build against the shape you will get in production.

## Assertion

**Assertion tier, MCP, webhooks, lists**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "nin",
  "method": "nin_raw",
  "status": "verified",
  "match": true,
  "field_matches": {
    "last_name": true
  },
  "field_confidence": {
    "last_name": 100
  },
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "*******2109",
  "summary": {
    "first_name": "A*****",
    "last_name": "O*****"
  },
  "consent": {
    "mechanism": "client_attested",
    "consent_id": null,
    "timestamp": "2026-10-19T09:41:07.512Z"
  },
  "source": "api",
  "error": null,
  "billing": {
    "amount_kobo": 0,
    "currency": "NGN"
  },
  "latency_ms": 412,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

## Full tier

**Full tier, REST API only**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "nin",
  "method": "nin_raw",
  "status": "verified",
  "match": true,
  "field_matches": {
    "last_name": true
  },
  "field_confidence": {
    "last_name": 100
  },
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "*******2109",
  "summary": {
    "first_name": "A*****",
    "last_name": "O*****"
  },
  "consent": {
    "mechanism": "client_attested",
    "consent_id": null,
    "timestamp": "2026-10-19T09:41:07.512Z"
  },
  "source": "api",
  "error": null,
  "billing": {
    "amount_kobo": 0,
    "currency": "NGN"
  },
  "latency_ms": 412,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z",
  "provider": "sandbox",
  "provider_reference": "SBX6CB753DB2C",
  "data": {
    "first_name": "CLARKE",
    "middle_name": "ELIZA",
    "last_name": "GRIFFIN",
    "date_of_birth": "1992-04-18",
    "gender": "female",
    "phone": "08031234567",
    "address": {
      "residential": "14 ADEOLA ODEKU STREET",
      "town": "VICTORIA ISLAND",
      "state": "Lagos",
      "lga": "Eti-Osa"
    },
    "origin": {
      "state": "Enugu",
      "lga": "Nsukka",
      "town": "NSUKKA"
    },
    "photo": "/9j/4AAQSkZJRgABAQEASABIAAD…"
  }
}
```

## Masking rules

- `subject` shows only the last four characters of the identifier: `*******2109`.
- Names in `summary` keep only their first letter: `A*****`. Account names are masked word by word.
- NINs and BVNs are masked in Speedmap's own logs and dashboards too. Identifiers are indexed by keyed hash, and records are encrypted at rest.

## Designing for the assertion

Build your decision logic on `status`, `match` and `field_matches`. They are identical across every channel and tier, so the same code works whether a check came from your backend, a bulk job, a webhook or an AI assistant. Treat `data` as an optional enrichment for full-tier REST calls only.

---

Source: https://speedmap-delta.vercel.app/docs/response-shaping
