Signed proof documents for official exchange rates

Keep source attribution, selected date, document id, payload hash, and public verification together.

Developer Reference

FXProof API Documentation

Official API reference for exchange-rate retrieval, proof bundle access, PDF verification, and account usage. The docs below stay aligned with the same product surface, navigation, and visual system as the rest of FXProof.

Dataset-first API reference

FXProof API clients first discover a dataset through GET /api/v1/rates/catalog. A dataset is the stable public token source|product|periodicity used by rates and proof bundle endpoints.

Proof bundles are opened or reused for one dataset and one date. Downloadable documents are exposed only after proof bundle readiness checks pass.

Public verification uses document id + payload_hash. It verifies stored proof payload metadata, not the byte hash of the signed PDF file.

Introduction

FXProof API helps teams automate proof work: discover valid datasets, retrieve reference rates when needed, open or reuse proof bundles, download ready files, and verify FXProof PDFs. After authentication, rates and proof bundle routes expose quota state with X-Quota-Limit, X-Quota-Remaining, and X-Quota-Reset.

Quick Start
  • Call POST /api/v1/tokens over HTTPS to get a Bearer token.
  • Call GET /api/v1/rates/catalog and choose a dataset ID such as source|product|periodicity.
  • Call GET /api/v1/rates with that dataset. Add date for a specific day, or omit it to use the latest date allowed by your plan. Add base when a dataset has more than one base currency; if date is omitted, base also narrows the latest-date lookup.
  • Open or reuse proof files with POST /api/v1/proofs using the same dataset and date. Reusing a bundle and downloading its files again do not spend proof quota.
  • After authentication, rates and proof bundle routes include X-Quota-Limit, X-Quota-Remaining, and X-Quota-Reset on quota-aware responses. The endpoint examples below show these headers on success and documented errors; 401 responses do not include them.
  • Handle expected errors by code. Refresh the catalog for invalid_filter, choose a returned date for no_data, retry later for not-ready proof files, and treat system_error as temporary.
  • Verify a PDF with GET /api/v1/check-pdf?id=...&hash=... using the values printed in the document.

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_AUTH_KEY}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Call POST /api/v1/tokens over HTTPS to get a short-lived Bearer token. Use it for protected endpoints.

Account

Get the authenticated user's account and quota summary.

requires authentication

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/user';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/user'
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/user"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
curl --request GET \
    --get "https://fxproof.org/api/v1/user" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"

Example response (200, Profile payload):

            {
    "data": {
        "id": 1,
        "name": "API User",
        "email": "api-user@example.com",
        "email_verified_at": "2026-03-30T12:00:00Z",
        "subscriptions": [
            {
                "name": "default",
                "provider": "paddle",
                "status": "active",
                "ends_at": null,
                "current_period_start": null,
                "current_period_end": null
            }
        ],
        "entitlements": [
            {
                "product": "Basic",
                "granted_at": "2026-03-01T00:00:00Z",
                "expires_at": null
            }
        ],
        "current_period": {
            "start": "2026-03-01",
            "end": "2026-03-31",
            "reset_at": "2026-04-01T00:00:00Z"
        },
        "usage": {
            "api_calls": 1,
            "proofs": 1
        },
        "limits": {
            "api_calls": 1500,
            "proofs": 5,
            "rpm": 10,
            "parallel_streams": 1,
            "history_years": 1,
            "history_days": 366,
            "history_earliest_date": "2025-03-30"
        },
        "remaining": {
            "api_calls": 1499,
            "proofs": 4
        },
        "plan": {
            "level": "basic",
            "name": "Basic"
        }
    }
}
     

Example response (401, Unauthenticated):

            {
    "message": "Unauthenticated."
}
     

Example response (429, RPM throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "details": {
        "scope": "rpm",
        "limit": 10
    }
}
     

Example response (500, System error):

            {
    "error": "system_error",
    "message": "Unexpected system error.",
    "details": {
        "scope": "user_show"
    }
}
     

Request

GET api/v1/user

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object   
limits   object   

Effective plan limits currently applied to the account.

history_years   integer|null     

Archive depth in calendar years, or null for the full available archive. Example: 1

api_calls   integer     

Monthly successful Rate API lookup limit from the effective plan.

proofs   integer     

Monthly proof bundle limit from the effective plan.

rpm   integer     

Per-minute request limit from the effective plan.

parallel_streams   integer     

Maximum concurrent rate lookups and proof PDF downloads.

history_days   integer     

Deprecated compatibility field. Inclusive day count derived from history_earliest_date.

history_earliest_date   string     

Earliest date allowed by the effective plan and system archive floor.

id   integer     

Internal user identifier.

name   string     

Account display name.

email   string     

Normalized account email.

email_verified_at   string|null     

ISO-8601 timestamp when the account email was verified.

subscriptions   object[]   

Active subscription rows currently attached to the account, including provider-neutral subscriptions.

name   string     

Subscription label, typically default.

provider   string     

Payment provider key for the subscription row.

status   string     

Provider-neutral subscription status.

ends_at   string|null     

ISO-8601 timestamp when the subscription access ends, if canceled.

current_period_start   string|null     

ISO-8601 timestamp when the provider billing period starts.

current_period_end   string|null     

ISO-8601 timestamp when the provider billing period ends.

entitlements   object[]   

Active entitlements currently attached to the account.

product   string|null     

Product name attached to the entitlement.

granted_at   string|null     

ISO-8601 timestamp when the entitlement was granted.

expires_at   string|null     

ISO-8601 timestamp when the active entitlement expires, or null for perpetual access.

current_period   object   

Current monthly quota window used for usage and remaining balances.

start   string     

Start date of the current monthly quota period in Y-m-d format.

end   string     

End date of the current monthly quota period in Y-m-d format.

reset_at   string|null     

ISO-8601 timestamp when monthly counters reset.

usage   object   

Current usage counters for the active monthly period.

api_calls   integer     

Successful Rate API lookups consumed in the current monthly period.

proofs   integer     

Proof bundle openings already consumed in the current monthly period.

remaining   object   

Remaining balances for the current monthly period.

api_calls   integer     

Remaining monthly Rate API lookups.

proofs   integer     

Remaining monthly proof bundle openings.

plan   object   

Effective plan summary resolved for the authenticated user.

level   string     

Internal plan level key. Enum: basic, pro, team, enterprise

name   string     

Human-readable plan name.

message   string     

Standard Laravel unauthenticated message or system error message.

error   string     

Machine-readable error code for failed requests.

details   object   
scope   string     

Throttle or diagnostic scope returned for failed requests.

limit   integer     

Active per-minute throttle limit for this user.

Authentication

Create a short-lived Bearer token for protected API calls.

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/tokens';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'email' => 'api-user@example.com',
            'password' => 'secret-pass-123',
            'device_name' => 'Production integration',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/tokens'
payload = {
    "email": "api-user@example.com",
    "password": "secret-pass-123",
    "device_name": "Production integration"
}
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/tokens"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "api-user@example.com",
    "password": "secret-pass-123",
    "device_name": "Production integration"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
curl --request POST \
    "https://fxproof.org/api/v1/tokens" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"api-user@example.com\",
    \"password\": \"secret-pass-123\",
    \"device_name\": \"Production integration\"
}"

Example response (201, Token issued):

            {
    "token_type": "Bearer",
    "access_token": "1|fxproof-example-token",
    "expires_at": "2026-03-31T12:00:00Z",
    "expires_in": 86400,
    "user": {
        "id": 1,
        "name": "API User",
        "email": "api-user@example.com"
    }
}
     

Example response (400, HTTPS required):

            {
    "error": "insecure_transport",
    "message": "Token issuance requires HTTPS."
}
     

Example response (422, Validation error):

            {
    "error": "validation_error",
    "message": "Invalid authentication request.",
    "details": {
        "email": [
            "The email field must be a valid email address."
        ],
        "device_name": [
            "The device name field is required."
        ]
    }
}
     

Example response (422, Invalid credentials):

            {
    "error": "invalid_credentials",
    "message": "Invalid credentials."
}
     

Example response (429, Authentication throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Too many authentication attempts. Please try again later.",
    "details": {
        "scope": "auth_token_email",
        "limit": 5
    }
}
     

Example response (429, Malformed request throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Too many invalid authentication requests. Please try again later.",
    "details": {
        "scope": "auth_token_malformed_ip",
        "limit": 5
    }
}
     

Example response (500, System error):

            {
    "error": "system_error",
    "message": "Unexpected system error.",
    "details": {
        "scope": "auth_tokens_store"
    }
}
     

Request

POST api/v1/tokens

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Account email. Maximum: 255 characters. Example: api-user@example.com

password   string     

Account password. Maximum: 1024 characters. Example: secret-pass-123

device_name   string     

Label for this client or integration. Example: Production integration

Response

Response Fields

token_type   string     

Authentication scheme for the issued token.

access_token   string     

Short-lived Sanctum bearer token to send in the Authorization header.

expires_at   string     

ISO-8601 timestamp when the issued token expires.

expires_in   integer     

Token lifetime in seconds from the time of issuance.

user   object   

Summary of the authenticated account tied to the issued token.

id   integer     

Internal user identifier.

name   string     

Account display name.

email   string     

Normalized account email address.

error   string     

Machine-readable error code for failed token requests.

message   string     

Human-readable status or error message.

details   object   

Validation or throttle metadata for failed requests.

email   string[]     

Validation messages for the email field.

password   string[]     

Validation messages for the password field.

device_name   string[]     

Validation messages for the device_name field.

scope   string     

Throttle scope when HTTP 429 is returned. Enum: auth_token_ip, auth_token_email, auth_token_malformed_ip

limit   integer     

Active per-minute throttle limit for this endpoint.

Proof Bundles

List proof bundles already opened by the authenticated user.

requires authentication

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/proofs';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'dataset' => 'fed|h10_daily|daily',
            'date' => '2026-03-30',
            'page' => '1',
            'per_page' => '20',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/proofs'
params = {
  'dataset': 'fed|h10_daily|daily',
  'date': '2026-03-30',
  'page': '1',
  'per_page': '20',
}
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/proofs"
);

const params = {
    "dataset": "fed|h10_daily|daily",
    "date": "2026-03-30",
    "page": "1",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
curl --request GET \
    --get "https://fxproof.org/api/v1/proofs?dataset=fed%7Ch10_daily%7Cdaily&date=2026-03-30&page=1&per_page=20" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"

Example response (200, Opened bundles):

            {
    "data": [
        {
            "id": 16,
            "dataset": "fed|h10_daily|daily",
            "source": "fed",
            "source_name": "Federal Reserve System",
            "product": "h10_daily",
            "product_name": "H10 Daily",
            "periodicity": "daily",
            "periodicity_name": "Daily",
            "date": "2026-03-30",
            "opened_at": "2026-03-30T10:00:00Z",
            "last_accessed_at": "2026-03-30T10:30:00Z",
            "documents": [
                {
                    "id": "019cb777-c5b6-734e-9552-0c4443fab21c",
                    "name": "Signed FXProof Proof PDF",
                    "type": "generated_current_pdf",
                    "role": "proof",
                    "is_official": false,
                    "mime_type": "application/pdf",
                    "size_bytes": 24567,
                    "source_url": null,
                    "download_url": "https://fxproof.org/api/v1/proofs/16/files/019cb777-c5b6-734e-9552-0c4443fab21c"
                }
            ]
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
     

Example response (401, Unauthenticated):

            {
    "message": "Unauthenticated."
}
     

Example response (422, Invalid dataset):

            {
    "error": "invalid_filter",
    "message": "One or more filters are invalid",
    "details": {
        "field": "dataset",
        "value": "fed|wrong_code|daily"
    }
}
     

Example response (422, Invalid request parameters):

            {
    "error": "validation_error",
    "message": "Invalid request parameters",
    "details": {
        "date": [
            "The date field must match the format Y-m-d."
        ]
    }
}
     

Example response (429, RPM throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "details": {
        "scope": "rpm",
        "limit": 10
    }
}
     

Example response (500, System error):

            {
    "error": "system_error",
    "message": "Unexpected system error.",
    "details": {
        "scope": "proofs_index"
    }
}
     

Request

GET api/v1/proofs

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

dataset   string  optional    

Optional stable public dataset identifier. Maximum: 120 characters. Default: no dataset filter. Example: fed|h10_daily|daily

date   string  optional    

Optional bundle date in Y-m-d format. Default: no date filter. Example: 2026-03-30

page   integer  optional    

Optional page number. Default: 1. Example: 1

per_page   integer  optional    

Optional items per page. Default: 20. Maximum: 100. Example: 20

Response

Response Fields

data   object   
id   integer     

Proof access id for subsequent detail and download calls.

dataset   string     

Stable public dataset identifier for the opened bundle.

source   string     

Source code of the opened bundle.

source_name   string     

Human-readable source name.

product   string     

Product code of the opened bundle.

product_name   string     

Human-readable product name.

periodicity   string     

Periodicity code of the opened bundle.

periodicity_name   string     

Human-readable periodicity name.

date   string     

Bundle date in Y-m-d format.

opened_at   string|null     

ISO-8601 timestamp when access was first opened.

last_accessed_at   string|null     

ISO-8601 timestamp when the bundle was last reused or downloaded.

documents   object[]   

Downloadable documents currently attached to this bundle.

id   string     

Document identifier used by the bundle download endpoint.

name   string     

Human-readable document label.

type   string     

Internal document type code.

role   string     

Internal document role code.

is_official   boolean     

Whether the file is an official source document.

mime_type   string|null     

MIME type recorded for the file.

size_bytes   integer|null     

File size in bytes when known.

source_url   string|null     

Original source URL when available.

download_url   string     

API URL for downloading the file from this bundle.

meta   object   
current_page   integer     

Current page number.

last_page   integer     

Last available page number.

per_page   integer     

Page size used for this response.

total   integer     

Total number of opened bundles matching the filters.

Open or reuse one proof bundle for one dataset and one date.

requires authentication

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/proofs';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'dataset' => 'fed|h10_daily|daily',
            'date' => '2026-03-30',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/proofs'
payload = {
    "dataset": "fed|h10_daily|daily",
    "date": "2026-03-30"
}
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/proofs"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "dataset": "fed|h10_daily|daily",
    "date": "2026-03-30"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
curl --request POST \
    "https://fxproof.org/api/v1/proofs" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"dataset\": \"fed|h10_daily|daily\",
    \"date\": \"2026-03-30\"
}"

Example response (200, Existing bundle reused):

            {
    "data": {
        "id": 16,
        "dataset": "fed|h10_daily|daily",
        "source": "fed",
        "source_name": "Federal Reserve System",
        "product": "h10_daily",
        "product_name": "H10 Daily",
        "periodicity": "daily",
        "periodicity_name": "Daily",
        "date": "2026-03-30",
        "opened_at": "2026-03-30T10:00:00Z",
        "last_accessed_at": "2026-03-30T10:30:00Z",
        "already_opened": true,
        "documents": [
            {
                "id": "019cb777-c5b6-734e-9552-0c4443fab21c",
                "name": "Signed FXProof Proof PDF",
                "type": "generated_current_pdf",
                "role": "proof",
                "is_official": false,
                "mime_type": "application/pdf",
                "size_bytes": 24567,
                "source_url": null,
                "download_url": "https://fxproof.org/api/v1/proofs/16/files/019cb777-c5b6-734e-9552-0c4443fab21c"
            }
        ]
    }
}
     

Example response (201, New bundle opened):

            {
    "data": {
        "id": 16,
        "dataset": "fed|h10_daily|daily",
        "source": "fed",
        "source_name": "Federal Reserve System",
        "product": "h10_daily",
        "product_name": "H10 Daily",
        "periodicity": "daily",
        "periodicity_name": "Daily",
        "date": "2026-03-30",
        "opened_at": "2026-03-30T10:00:00Z",
        "last_accessed_at": "2026-03-30T10:00:00Z",
        "already_opened": false,
        "documents": [
            {
                "id": "019cb777-c5b6-734e-9552-0c4443fab21c",
                "name": "Signed FXProof Proof PDF",
                "type": "generated_current_pdf",
                "role": "proof",
                "is_official": false,
                "mime_type": "application/pdf",
                "size_bytes": 24567,
                "source_url": null,
                "download_url": "https://fxproof.org/api/v1/proofs/16/files/019cb777-c5b6-734e-9552-0c4443fab21c"
            }
        ]
    }
}
     

Example response (401, Unauthenticated):

            {
    "message": "Unauthenticated."
}
     

Example response (403, Source unavailable):

            {
    "error": "source_unavailable",
    "message": "Source is unavailable"
}
     

Example response (403, Archive window exceeded):

            {
    "error": "archive_window_exceeded",
    "message": "The requested date is outside your plan's archive window.",
    "details": {
        "requested_date": "2025-12-30",
        "earliest_allowed_date": "2025-12-31",
        "history_years": 1,
        "history_days": 366
    }
}
     

Example response (404, Proof not found):

            {
    "error": "proof_not_found",
    "message": "Proof files not found for the specified criteria"
}
     

Example response (409, Proof bundle not ready):

            {
    "error": "proof_bundle_not_ready",
    "message": "Proof bundle is not ready yet."
}
     

Example response (422, Invalid dataset):

            {
    "error": "invalid_filter",
    "message": "One or more filters are invalid",
    "details": {
        "field": "dataset",
        "value": "fed|wrong_code|daily"
    }
}
     

Example response (422, Invalid request parameters):

            {
    "error": "validation_error",
    "message": "Invalid request parameters",
    "details": {
        "dataset": [
            "The dataset field is required."
        ],
        "date": [
            "The date field is required."
        ]
    }
}
     

Example response (429, Proof quota exceeded):

            {
    "error": "proof_limit_exceeded",
    "message": "Proof bundle quota exceeded"
}
     

Example response (429, RPM throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "details": {
        "scope": "rpm",
        "limit": 10
    }
}
     

Example response (500, System error):

            {
    "error": "system_error",
    "message": "Unexpected system error.",
    "details": {
        "scope": "proofs_store"
    }
}
     

Request

POST api/v1/proofs

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

dataset   string     

Stable public dataset identifier. Maximum: 120 characters. Example: fed|h10_daily|daily

date   string     

Requested document date in Y-m-d format. Example: 2026-03-30

Response

Response Fields

details   object   
history_years   integer     

Archive depth in calendar years, or null for the full available archive. Example: 1

Get one previously opened proof bundle.

requires authentication

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/proofs/architecto';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/proofs/architecto'
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/proofs/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
curl --request GET \
    --get "https://fxproof.org/api/v1/proofs/architecto" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"

Example response (200, Bundle detail):

            {
    "data": {
        "id": 16,
        "dataset": "fed|h10_daily|daily",
        "source": "fed",
        "source_name": "Federal Reserve System",
        "product": "h10_daily",
        "product_name": "H10 Daily",
        "periodicity": "daily",
        "periodicity_name": "Daily",
        "date": "2026-03-30",
        "opened_at": "2026-03-30T10:00:00Z",
        "last_accessed_at": "2026-03-30T10:30:00Z",
        "documents": [
            {
                "id": "019cb777-c5b6-734e-9552-0c4443fab21c",
                "name": "Signed FXProof Proof PDF",
                "type": "generated_current_pdf",
                "role": "proof",
                "is_official": false,
                "mime_type": "application/pdf",
                "size_bytes": 24567,
                "source_url": null,
                "download_url": "https://fxproof.org/api/v1/proofs/16/files/019cb777-c5b6-734e-9552-0c4443fab21c"
            }
        ]
    }
}
     

Example response (401, Unauthenticated):

            {
    "message": "Unauthenticated."
}
     

Example response (404, Proof access not found):

            {
    "error": "proof_access_not_found",
    "message": "Proof access not found"
}
     

Example response (409, Proof bundle not ready):

            {
    "error": "proof_bundle_not_ready",
    "message": "Proof bundle is not ready yet."
}
     

Example response (429, RPM throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "details": {
        "scope": "rpm",
        "limit": 10
    }
}
     

Example response (500, System error):

            {
    "error": "system_error",
    "message": "Unexpected system error.",
    "details": {
        "scope": "proofs_show"
    }
}
     

Request

GET api/v1/proofs/{proof}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

proof   integer     

Proof access id. Example: 16

Download one file from an opened proof bundle.

requires authentication

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/proofs/16/files/019cb777-c5b6-734e-9552-0c4443fab21c';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/proofs/16/files/019cb777-c5b6-734e-9552-0c4443fab21c'
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/proofs/16/files/019cb777-c5b6-734e-9552-0c4443fab21c"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
curl --request GET \
    --get "https://fxproof.org/api/v1/proofs/16/files/019cb777-c5b6-734e-9552-0c4443fab21c" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"

Example response (200, Downloaded file):

        Binary data -  PDF file stream

     

Example response (401, Unauthenticated):

            {
    "message": "Unauthenticated."
}
     

Example response (404, Proof access not found):

            {
    "error": "proof_access_not_found",
    "message": "Proof file not found for this access"
}
     

Example response (409, Proof bundle not ready):

            {
    "error": "proof_bundle_not_ready",
    "message": "Proof bundle is not ready yet."
}
     

Example response (429, Concurrent operation limit):

            {
    "error": "rate_limit_exceeded",
    "message": "Too many concurrent data requests.",
    "details": {
        "scope": "parallel_streams",
        "limit": 1
    }
}
     

Example response (429, RPM throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "details": {
        "scope": "rpm",
        "limit": 10
    }
}
     

Example response (500, System error):

            {
    "error": "system_error",
    "message": "Unexpected system error.",
    "details": {
        "scope": "proofs_download"
    }
}
     

Example response (503, File not available):

            {
    "error": "file_not_available",
    "message": "Proof file is not available for the selected access."
}
     

Request

GET api/v1/proofs/{proof}/files/{document}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

proof   integer     

Proof access id. Example: 16

document   string     

Document id from the bundle manifest. Example: 019cb777-c5b6-734e-9552-0c4443fab21c

Public Verification

Verify a FXProof PDF by document id and payload hash.

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/check-pdf';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'id' => 'doc-id',
            'hash' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/check-pdf'
params = {
  'id': 'doc-id',
  'hash': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
}
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/check-pdf"
);

const params = {
    "id": "doc-id",
    "hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
curl --request GET \
    --get "https://fxproof.org/api/v1/check-pdf?id=doc-id&hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"

Example response (200, Verified):

            {
    "data": {
        "verification_status": "verified",
        "country": "USA",
        "source_name": "Federal Reserve System",
        "product": "H10_DAILY",
        "reference_period": "2026-03-30",
        "rate_frequency": "Daily",
        "source_url": "https://example.com/source.pdf",
        "fxproof_document_id": "doc-id",
        "payload_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "signature_status": "unsigned"
    }
}
     

Example response (404, Document not found):

            {
    "error": "not_found",
    "message": "Document not found"
}
     

Example response (422, Validation error):

            {
    "error": "validation_error",
    "message": "Invalid request parameters",
    "details": {
        "id": [
            "The id field is required."
        ],
        "hash": [
            "The hash field is required."
        ]
    }
}
     

Example response (422, Hash mismatch):

            {
    "error": "hash_mismatch",
    "message": "Payload hash does not match"
}
     

Example response (429, Verification throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Too many verification requests. Please try again later.",
    "details": {
        "scope": "public_verification",
        "limit": 5
    }
}
     

Example response (500, System error):

            {
    "error": "system_error",
    "message": "Unexpected system error.",
    "details": {
        "scope": "pdf_check_show"
    }
}
     

Request

GET api/v1/check-pdf

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

id   string     

Public document identifier printed in the proof document. Example: doc-id

hash   string     

Payload SHA-256 hash printed in the proof document. Example: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Response

Response Fields

data   object   
verification_status   string     

Verification result for a successful match. Enum: verified

country   string|null     

Issuing country inferred from the matched proof metadata when available.

source_name   string|null     

Human-readable source name from the matched proof metadata when available.

product   string|null     

Product code stored in the matched proof metadata when available.

reference_period   string     

Reference date or period covered by the verified proof.

rate_frequency   string     

Frequency of the rates represented by the proof.

source_url   string|null     

Original source URL attached to the proof when available.

fxproof_document_id   string     

Public FXProof document identifier embedded in the proof.

payload_hash   string     

SHA-256 payload hash embedded in the proof.

signature_status   string     

Signature status recorded for the proof document.

error   string     

Machine-readable error code for validation or verification failure.

message   string     

Human-readable verification result message.

details   object   

Validation or throttle metadata for failed requests.

id   string[]     

Validation messages for the id parameter.

hash   string[]     

Validation messages for the hash parameter.

scope   string     

Throttle or diagnostic scope returned for failed requests.

limit   integer     

Active per-minute throttle limit for this endpoint.

Rates

Get the full list of public datasets.

requires authentication

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/rates/catalog';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/rates/catalog'
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/rates/catalog"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
curl --request GET \
    --get "https://fxproof.org/api/v1/rates/catalog" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"

Example response (200, Catalog payload):

            {
    "data": {
        "datasets": [
            {
                "id": "ecb|eurofxref_daily|daily",
                "source": "ecb",
                "source_name": "European Central Bank",
                "product": "eurofxref_daily",
                "product_name": "Euro FX Reference Rates",
                "periodicity": "daily",
                "periodicity_name": "Daily",
                "first_available_date": "2026-03-01",
                "latest_available_date": "2026-03-30",
                "bases": [
                    "EUR"
                ],
                "symbols": [
                    "GBP",
                    "USD"
                ],
                "proof_available": true
            }
        ]
    }
}
     

Request

GET api/v1/rates/catalog

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object   
datasets   object[]   

Flat list of active dataset definitions discovered from stored rates.

id   string     

Stable public dataset identifier accepted by /api/v1/rates and proof bundle APIs.

source   string     

Source code that owns the dataset.

source_name   string     

Human-readable source name.

product   string     

Product code that owns the dataset.

product_name   string     

Human-readable product name.

periodicity   string     

Periodicity code that owns the dataset.

periodicity_name   string     

Human-readable periodicity name.

first_available_date   string|null     

Earliest stored date for this dataset.

latest_available_date   string|null     

Latest stored date for this dataset.

bases   string[]     

Base currencies available for this dataset.

symbols   string[]     

Quote currencies available for this dataset.

proof_available   boolean     

Whether at least one PDF proof/supporting document exists for this dataset.

Get exchange rates for one dataset.

requires authentication

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/v1/rates';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'dataset' => 'ecb|eurofxref_daily|daily',
            'date' => '2026-03-30',
            'base' => 'EUR',
            'symbols' => 'USD,GBP,KZT',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/v1/rates'
params = {
  'dataset': 'ecb|eurofxref_daily|daily',
  'date': '2026-03-30',
  'base': 'EUR',
  'symbols': 'USD,GBP,KZT',
}
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()
const url = new URL(
    "https://fxproof.org/api/v1/rates"
);

const params = {
    "dataset": "ecb|eurofxref_daily|daily",
    "date": "2026-03-30",
    "base": "EUR",
    "symbols": "USD,GBP,KZT",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
curl --request GET \
    --get "https://fxproof.org/api/v1/rates?dataset=ecb%7Ceurofxref_daily%7Cdaily&date=2026-03-30&base=EUR&symbols=USD%2CGBP%2CKZT" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"

Example response (200, Rates payload):

            {
    "dataset": "ecb|eurofxref_daily|daily",
    "source": "ecb",
    "product": "eurofxref_daily",
    "periodicity": "daily",
    "date": "2026-03-30",
    "base": "EUR",
    "rates": {
        "GBP": 0.8561,
        "USD": 1.0825
    },
    "meta": {
        "cached": false,
        "signature": "sha256:example-signature"
    }
}
     

Example response (401, Unauthenticated):

            {
    "message": "Unauthenticated."
}
     

Example response (403, Source unavailable):

            {
    "error": "source_unavailable",
    "message": "Source is unavailable"
}
     

Example response (403, Archive window exceeded):

            {
    "error": "archive_window_exceeded",
    "message": "The requested date is outside your plan's archive window.",
    "details": {
        "requested_date": "2025-12-30",
        "earliest_allowed_date": "2025-12-31",
        "history_years": 1,
        "history_days": 366
    }
}
     

Example response (404, No data):

            {
    "error": "no_data",
    "message": "No rates found for the specified criteria",
    "available_dates": {
        "previous": "2026-03-29",
        "next": "2026-03-31",
        "latest": "2026-03-31"
    }
}
     

Example response (409, Ambiguous base):

            {
    "error": "ambiguous_data",
    "message": "Base currency is required for this dataset on the requested date",
    "details": {
        "available_bases": [
            "EUR",
            "USD"
        ]
    }
}
     

Example response (422, Validation error):

            {
    "error": "validation_error",
    "message": "Invalid request parameters",
    "details": {
        "dataset": [
            "The dataset field is required."
        ],
        "date": [
            "The date field must match the format Y-m-d."
        ]
    }
}
     

Example response (422, Invalid filter):

            {
    "error": "invalid_filter",
    "message": "One or more filters are invalid",
    "details": {
        "field": "base",
        "value": "ZZZ"
    }
}
     

Example response (429, Monthly Rate API lookup quota exceeded):

            {
    "error": "rate_limit_exceeded",
    "message": "Rate API lookup quota exceeded",
    "details": {
        "scope": "user",
        "limit": 1500
    }
}
     

Example response (429, Concurrent operation limit):

            {
    "error": "rate_limit_exceeded",
    "message": "Too many concurrent data requests.",
    "details": {
        "scope": "parallel_streams",
        "limit": 1
    }
}
     

Example response (429, RPM throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "details": {
        "scope": "rpm",
        "limit": 10
    }
}
     

Example response (500, System error):

            {
    "error": "system_error",
    "message": "Unexpected system error.",
    "details": {
        "scope": "rates_index"
    }
}
     

Request

GET api/v1/rates

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

dataset   string     

Dataset id from /api/v1/rates/catalog. Maximum: 120 characters. Example: ecb|eurofxref_daily|daily

date   string  optional    

Optional rate date in Y-m-d format. Default: latest date allowed by the effective archive window. If base is sent, the latest allowed date is resolved inside that base. Example: 2026-03-30

base   string  optional    

Optional 3-letter base currency code. Default: auto-select when one base exists for the selected date; return 409 ambiguous_data when more than one base exists. Example: EUR

symbols   string  optional    

Optional comma-separated quote currency filter. Maximum: 1024 characters and 128 values. Default: all available quote currencies for the selected dataset, date, and base. The full set must be valid for the selected dataset and base. Example: USD,GBP,KZT

Response

Response Fields

details   object   

Validation, filter, throttle, or diagnostic metadata for failed requests.

history_years   integer|null     

Archive depth in calendar years, or null for the full available archive. Example: 1

field   string     

Invalid filter field name when invalid_filter is returned.

value   string     

Rejected filter value when invalid_filter is returned.

invalid_values   string[]     

Unsupported symbol list when symbols contains invalid values.

available_bases   string[]     

Available bases on the requested date when base is required.

scope   string     

Throttle or diagnostic scope returned for failed requests.

limit   integer     

Active quota or per-minute limit associated with the failure.

requested_date   string     

Date rejected by the effective archive window.

earliest_allowed_date   string     

Earliest date allowed by the effective plan and system archive floor.

history_days   integer     

Deprecated compatibility field. Inclusive day count derived from earliest_allowed_date.

dataset   string     

Stable public dataset identifier resolved for this response.

source   string     

Source code that owns the dataset.

product   string     

Product code that owns the dataset.

periodicity   string     

Periodicity code that owns the dataset.

date   string     

Actual rate date used for this response. If the request omits date, this is the latest allowed date for the dataset or selected base scope.

base   string     

Base currency used by the response payload.

rates   object     

Quote map keyed by ISO currency symbol.

meta   object   
cached   boolean     

Whether the response body came from the application cache.

signature   string     

Deterministic SHA-256 signature of the returned rates object.

available_dates   object   
previous   string|null     

Nearest earlier date with matching data.

next   string|null     

Nearest later date with matching data.

latest   string|null     

Latest known date with matching data.

error   string     

Machine-readable error code for failed requests.

message   string     

Human-readable status or failure message.

System

Return a small public health payload.

Example request:
$client = new \GuzzleHttp\Client();
$url = 'https://fxproof.org/api/health';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://fxproof.org/api/health'
headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()
const url = new URL(
    "https://fxproof.org/api/health"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
curl --request GET \
    --get "https://fxproof.org/api/health" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"

Example response (200):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
x-request-id: 01KXRVBV9R7XFFDR6W96DKPWSM
access-control-allow-origin: *
 
            {
    "status": "healthy",
    "version": "1.0.0",
    "timestamp": "2026-07-17T20:12:56.004444Z"
}
     

Example response (429, Health check throttle):

            {
    "error": "rate_limit_exceeded",
    "message": "Too many health check requests. Please try again later.",
    "details": {
        "scope": "health",
        "limit": 60
    }
}
     

Request

GET api/health

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

status   string     

Public health status of the API. Enum: healthy

version   string     

Current public API version string.

timestamp   string     

ISO-8601 timestamp when the health payload was generated.

error   string     

Machine-readable error code when the endpoint is throttled.

message   string     

Human-readable status or throttle message.

details   object   
scope   string     

Throttle scope when HTTP 429 is returned. Enum: health

limit   integer     

Active per-minute throttle limit for this endpoint.