# Countries and attestation

Add `country` to load a page the way people there get it. There are 227 countries. A screenshot from a chosen country uses 2 credits.

> **Not open to every account yet.** Screenshots from a chosen country are being opened account by account. Until yours is, a request with `country` is refused with `option_unavailable` before anything is captured. Everything below describes how it works once it is open for you.

**From Denmark**

```bash
curl "https://api.localscreenshot.com/v1/screenshot?url=https://www.netflix.com&country=DK&response=json" \
  -H "Authorization: Bearer $LOCALSCREENSHOT_API_KEY"
```

## The country you see is the one we observed

We check where the page was really loaded from, with two independent location databases. The answer reports that country, never the one you asked for.

- If we cannot confirm the country, you get the error `country_not_obtained`, and no screenshot from somewhere else. A failed screenshot is not charged.
- With the image, the header `X-Country-Observed` carries the country. With `response=json`, it is in `country.observed`.
- A country we do not have is refused with `country_not_available`.

## The list of countries

`GET /v1/countries` returns the two-letter codes you can use. No key needed. The count is what we measured, on the date given in `measured_at`.

**GET /v1/countries**

```json
{
  "count": 227,
  "measured_at": "2026-10-08",
  "countries": [
    { "code": "AD", "name": "Andorra" },
    { "code": "AE", "name": "United Arab Emirates" }
  ]
}
```

## The attestation

Every screenshot taken from a country comes with a signed record: where it was loaded from, when, what the page answered, and a fingerprint of the image. It is in the `attestation` field, and at `GET /v1/screenshots/{id}/attestation`.

| Field | Description |
| --- | --- |
| `requested_country` | The country you asked for. |
| `observed` | What we saw: country, kind of network, the network operator, a masked address, and what each location database said. |
| `response` | What the page answered: status, final address, redirects, title. |
| `screenshot_sha256` | The fingerprint of the image file. |
| `strict_pass` | `true` only when the observed country matches and stayed the same during the capture. |
| `signature` | An Ed25519 signature, with the id of the key that made it. |

The address the page was loaded from is never given in full: the attestation keeps only its masked form.

**The attestation**

```json
{
  "schema": "attestation.v1",
  "id": "att_01JABCDEF2G3H4J5K6M7N8P9QR",
  "capture_id": "01JABCDEF2G3H4J5K6M7N8P9QR",
  "captured_at": "2026-10-08T10:47:06Z",
  "requested_country": "DK",
  "observed": {
    "country": "DK",
    "network_type": "residential_or_mobile_isp",
    "ip_prefix": "203.0.113.0/24",
    "ip_hash": "5f2c9a...",
    "asn": 64500,
    "asn_org": "Example Telecom",
    "geoip": [
      { "db": "first", "version": "2026-10", "country": "DK" },
      { "db": "second", "version": "2026-10", "country": "DK" }
    ],
    "geoip_agree": true,
    "exit_stable": true
  },
  "response": { "status": 200, "final_url": "https://www.netflix.com/dk/", "redirects": [], "title": "Netflix" },
  "screenshot_sha256": "a5b9081b8430...",
  "strict": true,
  "strict_pass": true,
  "signature": { "algorithm": "Ed25519", "key_id": "k20261008", "value": "MEUC..." }
}
```

> **What it is, and is not.** A technical attestation of one capture, signed by us. It is not legal proof.

## Check the signature

1. Get our public keys at `GET /v1/attestation-keys` and pick the one whose `key_id` matches.
2. Remove the `signature` field from the attestation.
3. Write what is left as JSON with keys sorted at every level, with no escaping of slashes or accents.
4. Verify the base64 signature against that text with Ed25519.

**GET /v1/attestation-keys**

```json
{
  "keys": [
    { "key_id": "k20261008", "algorithm": "Ed25519", "public_key": "base64..." }
  ]
}
```

## What a country changes, and what it does not

- The page is loaded through a network connection in that country, so the site answers as it does for people there: prices, language, availability, legal notices.
- It does not set the browser language or clock. Add `language` and `timezone` if the site follows those: see [Devices and sizes](https://docs.localscreenshot.com/devices).
- We make no promise that a website will not notice the visit. If it shows a bot check instead of its page, the screenshot fails with `challenge_page`.
