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/tokensover HTTPS to get a Bearer token. - Call
GET /api/v1/rates/catalogand choose adatasetID such assource|product|periodicity. - Call
GET /api/v1/rateswith thatdataset. Adddatefor a specific day, or omit it to use the latest date allowed by your plan. Addbasewhen a dataset has more than one base currency; ifdateis omitted,basealso narrows the latest-date lookup. - Open or reuse proof files with
POST /api/v1/proofsusing the samedatasetanddate. 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, andX-Quota-Reseton quota-aware responses. The endpoint examples below show these headers on success and documented errors;401responses do not include them. - Handle expected errors by code. Refresh the catalog for
invalid_filter, choose a returned date forno_data, retry later for not-ready proof files, and treatsystem_erroras 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"
}
}
Received response:
Request failed with error:
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"
}
}
Received response:
Request failed with error:
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"
}
}
Received response:
Request failed with error:
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"
}
}
Received response:
Request failed with error:
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"
}
}
Received response:
Request failed with error:
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."
}
Received response:
Request failed with error:
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"
}
}
Received response:
Request failed with error:
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
}
]
}
}
Received response:
Request failed with error:
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"
}
}
Received response:
Request failed with error:
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
}
}
Received response:
Request failed with error:
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.