# Field families & MMDB distribution API

> How API key licensedFamilies project lookup responses, plus the complete /v1/mmdb/* endpoints for custom quotes, release manifests, SHA256SUMS, and signed artifact downloads.

## 1. Licensed field families (`licensedFamilies`)

In `@ipscope/api`, every API key carries an explicit `licensedFamilies` entitlement list configured by your plan or custom database profile. When you call `GET /v1/ip/:ip` or `POST /v1/ip/batch`, `filterByLicensedFamilies()` automatically projects the `IpLookupResponse` to the families licensed on your key:

| Family ID (`licensedFamilies`) | Projected JSON fields in `IpLookupResponse` | Description |
| --- | --- | --- |
| `network` | `matched_network`, `network.asn`, `network.organization` | Matched CIDR prefix and BGP origin ASN / organization. |
| `geo_country` | `geo.country_code`, `geo.declared_country_code`, `geo.physical_country_code`, `geo.is_virtual_location`, `geo.location_type`, `geo.confidence` | Country-level routing reality and declared vs. physical separation. |
| `geo_city` | Adds `geo.subdivision`, `geo.city`, `geo.accuracy_radius_km`, `geo.timezone` | City, first-level subdivision, accuracy radius, and IANA timezone. |
| `connection` | `connection.connection_type`, `is_residential`, `is_mobile`, `is_business`, `is_hosting` | Access network classification (`residential`, `mobile`, `business`, `hosting`). |
| `hosting` | `security.is_hosting`, `connection.is_hosting` | Datacenter, cloud, and colocation hosting flags. |
| `anonymity` | `security.is_vpn`, `is_tor_exit`, `is_proxy`, `is_hosting`, `anonymizer_type`, `confidence`, `reason_codes` | VPN, Tor exit, proxy, and relay detection with explainable `reason_codes`. |
| `security_extensions` | `security_extensions.abuse_velocity_score`, `is_known_scanner`, `observation_recency_hours` | Honeypot abuse velocity score (`0–100`), scanner flag, and observation recency. |
| `meta` | `dataset_version`, `updated_at` | Snapshot version provenance and publication timestamp. |

> **Tip:** If an API key is created with an empty `licensedFamilies` list (`[]`), the lookup engine defaults to returning all enabled families in the snapshot.

## 2. MMDB & artifact catalog (`GET /v1/mmdb/products` & `POST /v1/mmdb/quote`)

```http
GET https://api.ipscope.io/v1/mmdb/products
```

Authentication: API key (`X-API-Key` header or `Authorization: Bearer`). Plan: scope: lookup

```http
POST https://api.ipscope.io/v1/mmdb/quote
```

Authentication: API key (`X-API-Key` header or `Authorization: Bearer`). Plan: scope: lookup

`GET /v1/mmdb/products` returns the complete catalog of selectable artifact formats (`mmdb`, `csv`, `parquet`), field families, update cadences (`daily`, `weekly`, `every_30_days`), dynamic pricing rules, and preset SKUs. `POST /v1/mmdb/quote` computes a deterministic integer-minor-unit price quote and `profileSchemaHash` for any custom combination.

**Quote request body (POST /v1/mmdb/quote)** (MmdbQuoteRequestDto)

- `preset_id` (string, optional): Optional preset SKU identifier (`"lite"`, `"country"`, `"asn"`, `"city"`, `"privacy"`, `"mega"`).
- `formats` (string[], required): One or more artifact formats to compile.
- `field_families` (string[], required): Array of field family IDs to include in the compiled database.
- `cadence` (string, required): Snapshot update cadence.
- `seats` (integer, optional): Number of licensed internal servers (`1` included by default; `+$149/mo` per additional server). Databases are strictly for internal server use and cannot be shared or sold.

```json Example
{
  "formats": [
    "mmdb",
    "parquet"
  ],
  "field_families": [
    "network",
    "geo_country",
    "geo_city",
    "anonymity"
  ],
  "cadence": "daily",
  "seats": 1
}
```

```bash curl
curl -sS -X POST "https://api.ipscope.io/v1/mmdb/quote" \
  -H "Authorization: Bearer $IPSCOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"formats":["mmdb","parquet"],"field_families":["network","geo_country","geo_city","anonymity"],"cadence":"daily"}'
```

```js Node.js
const res = await fetch('https://api.ipscope.io/v1/mmdb/quote', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.IPSCOPE_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "formats": [
      "mmdb",
      "parquet"
    ],
    "field_families": [
      "network",
      "geo_country",
      "geo_city",
      "anonymity"
    ],
    "cadence": "daily"
  }),
});
const data = await res.json();
```

```python Python
import os, httpx

async with httpx.AsyncClient(timeout=5.0) as client:
    res = await client.post(
        "https://api.ipscope.io/v1/mmdb/quote",
        headers={"Authorization": f"Bearer {os.environ['IPSCOPE_KEY']}"},
        json={
          "formats": [
            "mmdb",
            "parquet"
          ],
          "field_families": [
            "network",
            "geo_country",
            "geo_city",
            "anonymity"
          ],
          "cadence": "daily"
        },
    )
    data = res.json()
```

```go Go
req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.ipscope.io/v1/mmdb/quote", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("IPSCOPE_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer res.Body.Close()
```

### 200 `MmdbQuoteResponse`: Deterministic integer-minor-unit quote and profileSchemaHash.

- `currency` (string, required): ISO 4217 currency code (`"USD"`).
- `amountMinor` (integer, required): Price in integer minor units (cents).
- `profileSchemaHash` (string, required): Deterministic hash identifying the exact format + field family + cadence selection.
- `formats` (string[], required): Selected artifact formats.
- `field_families` (string[], required): Selected intelligence field families.
- `cadence` (string, required): Selected update cadence.

```json 200 MmdbQuoteResponse
{
  "currency": "USD",
  "amountMinor": 19900,
  "profileSchemaHash": "psh_8a91c4e20b1f",
  "formats": [
    "mmdb",
    "parquet"
  ],
  "field_families": [
    "network",
    "geo_country",
    "geo_city",
    "anonymity"
  ],
  "cadence": "daily"
}
```

### 400 `ApiError`: Unsupported format, unknown field_family, or invalid cadence.

- `statusCode` (integer, required): HTTP status code returned by the API gateway.
- `message` (string, required): Human-readable diagnostic message describing the error condition.
- `error` (string, required): Canonical HTTP error classification.

```json 400 ApiError
{
  "statusCode": 400,
  "message": "Unsupported format: xml",
  "error": "Bad Request"
}
```

## 3. Releases, tokens, checksums & downloads (`/v1/mmdb/releases/*`)

```http
GET https://api.ipscope.io/v1/mmdb/releases
```

Authentication: API key (`X-API-Key` header or `Authorization: Bearer`). Plan: scope: download

```http
POST https://api.ipscope.io/v1/mmdb/releases/{id}/download-token
```

Authentication: API key (`X-API-Key` header or `Authorization: Bearer`). Plan: scope: download · 201

```http
GET https://api.ipscope.io/v1/mmdb/releases/{id}/checksums
```

Authentication: API key (`X-API-Key` header or `Authorization: Bearer`). Plan: scope: download

```http
GET https://api.ipscope.io/v1/mmdb/releases/{id}/download
```

Authentication: API key (`X-API-Key` header or `Authorization: Bearer`). Plan: scope: download

```http
GET https://api.ipscope.io/v1/mmdb/snapshots/diff
```

Authentication: API key (`X-API-Key` header or `Authorization: Bearer`). Plan: scope: download

All release and artifact delivery endpoints require the `download` scope. Pass either a specific release ID (`rel_...`) or `latest` as `{id}`.

```bash curl
curl -sS "https://api.ipscope.io/v1/mmdb/releases" \
  -H "Authorization: Bearer $IPSCOPE_KEY"
```

```js Node.js
const res = await fetch('https://api.ipscope.io/v1/mmdb/releases', {
  headers: { 'Authorization': `Bearer ${process.env.IPSCOPE_KEY}` },
});
const data = await res.json();
```

```python Python
import os, httpx

async with httpx.AsyncClient(timeout=5.0) as client:
    res = await client.get(
        "https://api.ipscope.io/v1/mmdb/releases",
        headers={"Authorization": f"Bearer {os.environ['IPSCOPE_KEY']}"},
    )
    data = res.json()
```

```go Go
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.ipscope.io/v1/mmdb/releases", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("IPSCOPE_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer res.Body.Close()
```

### 200 `MmdbReleaseListResponse`: Published MMDB, CSV, and Parquet release manifests and SHA-256 digests.

- `count` (integer, required): Number of published releases returned.
- `releases` (MmdbReleaseManifest[], required): Ordered list of compiled release manifests.
  - `releaseId` (string, required): Unique release identifier (`"rel_..."`).
  - `snapshotVersion` (string, required): Source dataset snapshot version.
  - `createdAt` (string, required): ISO 8601 UTC compilation timestamp.
  - `status` (string, required): Release lifecycle status.
  - `artifacts` (MmdbArtifactItem[], required): Compiled files available in this release.
    - `format` (string, required): Artifact format (`"mmdb"`, `"csv"`, `"parquet"`).
    - `filename` (string, required): Canonical filename.
    - `sizeBytes` (integer, required): Compressed artifact size in bytes.
    - `sha256` (string, required): Hex-encoded SHA-256 checksum verified prior to atomic swap.

```json 200 MmdbReleaseListResponse
{
  "count": 1,
  "releases": [
    {
      "releaseId": "rel_20261027_0000",
      "snapshotVersion": "2026-10-27T00:00:00Z.crawl-1793088000000",
      "createdAt": "2026-10-27T00:00:00Z",
      "status": "published",
      "artifacts": [
        {
          "format": "mmdb",
          "filename": "ipscope-complete.mmdb",
          "sizeBytes": 148291584,
          "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
        },
        {
          "format": "parquet",
          "filename": "ipscope-complete.parquet",
          "sizeBytes": 112408192,
          "sha256": "4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a"
        }
      ]
    }
  ]
}
```

### 201 `MmdbDownloadTokenResponse`: Short-lived HMAC download token from POST /v1/mmdb/releases/{id}/download-token.

- `token` (string, required): Signed short-lived download token.
- `releaseId` (string, required): Resolved release ID.
- `format` (string, required): Authorized artifact format.
- `expiresAt` (string, required): ISO 8601 UTC token expiration timestamp.

```json 201 MmdbDownloadTokenResponse
{
  "token": "dl_tok_9f86d081884c7d659a2f",
  "releaseId": "rel_20261027_0000",
  "format": "mmdb",
  "expiresAt": "2026-10-27T02:45:00.000Z"
}
```

### 200 `SHA256SUMS`: Plain-text SHA256SUMS manifest from GET /v1/mmdb/releases/{id}/checksums?format=text.

```text 200 SHA256SUMS
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08  ipscope-complete.mmdb
4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a  ipscope-complete.parquet
```

### 302 `GcsSignedUrlRedirect`: Redirect to a 15-minute Google Cloud Storage V4 Signed URL when ?redirect=true is passed.

- `Location` (string, required): HTTPS GCS V4 signed URL (`X-Goog-Algorithm=GOOG4-RSA-SHA256`, `X-Goog-Expires=900`).
- `ETag` (string, required): Quoted SHA-256 checksum of the artifact (`"<sha256>"`).
- `X-MMDB-SHA256` (string, required): Hex-encoded SHA-256 digest of the artifact.

```json 302 GcsSignedUrlRedirect
{
  "statusCode": 302,
  "headers": {
    "Location": "https://storage.googleapis.com/ipscope-releases/rel_20261027_0000/ipscope-complete.mmdb?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Expires=900",
    "ETag": "\"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\"",
    "X-MMDB-SHA256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
  }
}
```

### 304 `NotModified`: Returned when If-None-Match matches the release ETag ("<sha256>").

- `ETag` (string, required): Unchanged artifact SHA-256 ETag.

### 403 `ApiError`: API key does not have the required download scope or invalid download token.

- `statusCode` (integer, required): HTTP status code returned by the API gateway.
- `message` (string, required): Human-readable diagnostic message describing the error condition.
- `error` (string, required): Canonical HTTP error classification.

```json 403 ApiError
{
  "statusCode": 403,
  "message": "API key does not have required scope: download",
  "error": "Forbidden"
}
```
