API Documentation
HotelGrade provides official hotel star ratings sourced directly from national tourism authorities. Every rating is government-issued — no scraped data, no review aggregates.
Base URL: https://hotelgrade.eu — All API endpoints are relative to this URL.
What's included
The API gives you access to 24,000+ hotels across France, Portugal, Spain (12 regions), and more countries coming. Each record includes the official star classification, GPS coordinates (where available), contact details, and the source authority.
Quick Start
Get up and running in under 2 minutes.
Create a free account
Go to hotelgrade.eu/login.html and register. You get an API key instantly — no credit card required.
Copy your API key
Your key is shown once after registration and is always visible in your dashboard. It looks like hsa_a1b2c3d4...
Make your first request
Pass your key in the X-API-Key header on every request.
# Search for 5-star hotels in France curl "https://hotelgrade.eu/api/hotels?country=France&stars=5&limit=10" \ -H "X-API-Key: hsa_your_key_here"
const response = await fetch( 'https://hotelgrade.eu/api/hotels?country=France&stars=5&limit=10', { headers: { 'X-API-Key': 'hsa_your_key_here' } } ); const data = await response.json(); console.log(data.data); // array of hotels
import requests response = requests.get( 'https://hotelgrade.eu/api/hotels', params={'country': 'France', 'stars': 5, 'limit': 10}, headers={'X-API-Key': 'hsa_your_key_here'} ) data = response.json() print(data['data'])
Authentication
All API requests require a valid API key passed in the X-API-Key request header.
X-API-Key: hsa_your_key_here
API keys follow the format hsa_ followed by 48 hexadecimal characters. You can generate up to 3 keys per account from your dashboard.
Keep your API key secret. Do not expose it in client-side JavaScript, public repositories, or logs. If a key is compromised, revoke it immediately from your dashboard.
Try the API without a key
The /api/demo endpoint is publicly accessible without authentication and returns up to 5 results. It's rate-limited to 10 requests per minute per IP.
curl "https://hotelgrade.eu/api/demo?country=Portugal&stars=5"
API Key Management
Manage your keys from the dashboard. You can also use the REST endpoints below directly.
Create a new account. Returns a JWT token, user info, and your first API key.
Request body
| Field | Type | Description |
|---|---|---|
email required | string | Your email address |
password required | string | Min. 8 characters |
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI...", // JWT — valid 30 days
"user": { "email": "you@example.com" },
"api_key": "hsa_a1b2c3d4..." // shown once — save it!
}
Authenticate and get a fresh JWT token.
Request body
| Field | Type | Description |
|---|---|---|
email required | string | |
password required | string |
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI...",
"user": { "email": "you@example.com" }
}
Returns your account info and all API keys with usage stats. Requires a JWT token in the Authorization header.
Pass the JWT from login/register as Authorization: Bearer <token> — not your API key.
{
"success": true,
"user": { "email": "you@example.com" },
"keys": [
{
"id": 12,
"name": "My App",
"plan": "free",
"status": "active",
"requests_today": 47,
"requests_total": 1203,
"last_used_at": "2026-03-23T10:42:00Z",
"created_at": "2026-01-15T08:00:00Z"
}
],
"can_create_key": true // false when 3 keys already exist
}
Generate a new API key (max 3 per account). Requires JWT.
Request body
| Field | Type | Description |
|---|---|---|
name optional | string | Label for this key (e.g. "Production") |
Revoke an API key permanently. The key stops working immediately. Requires JWT.
Revocation is permanent. Any application still using the revoked key will receive 403 Forbidden.
Search Hotels
Search and filter hotels across the entire database. Returns paginated results.
Query parameters
| Parameter | Type | Description |
|---|---|---|
country optional | string | Filter by country, case-insensitive. e.g. France, Spain, Portugal |
city optional | string | Partial city name match. e.g. Paris |
stars optional | integer | Filter by star rating: 1 to 5 |
name optional | string | Partial hotel name search. e.g. Hilton |
page optional | integer | Page number, default 1 |
limit optional | integer | Results per page, default 20. Max depends on plan (Free: 20, Starter: 50, Pro: 100) |
curl "https://hotelgrade.eu/api/hotels?country=Spain&stars=4&limit=20" \ -H "X-API-Key: hsa_your_key_here"
const params = new URLSearchParams({ country: 'Spain', stars: 4, limit: 20 }); const res = await fetch(`https://hotelgrade.eu/api/hotels?${params}`, { headers: { 'X-API-Key': 'hsa_your_key_here' } }); const { data, pagination } = await res.json();
r = requests.get('https://hotelgrade.eu/api/hotels', params={'country': 'Spain', 'stars': 4, 'limit': 20}, headers={'X-API-Key': 'hsa_your_key_here'})
Response
{
"success": true,
"data": [
{
"id": 18234,
"name": "Hotel Arts Barcelona",
"stars": 5,
"country": "Spain",
"country_code": "ES",
"city": "Barcelona",
"address": "Carrer de la Marina, 19-21",
"latitude": 41.3874,
"longitude": 2.1965,
"phone": "+34 93 221 10 00",
"website": "https://www.hotelartsbarcelona.com",
"email": null,
"source": "spain-oficial",
"source_id": "es-ct-HO-000123"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 487,
"totalPages": 25
}
}
Rate limit headers
Every response includes headers indicating your current rate limit status:
X-RateLimit-Limit: 10 # requests allowed per minute X-RateLimit-Remaining: 8 # requests left this minute X-RateLimit-Reset: 1711187120 # Unix timestamp of window reset X-RateLimit-Daily-Limit: 100 # daily quota X-RateLimit-Daily-Remaining: 53 # daily quota remaining
Get Hotel by ID
Retrieve full details for a single hotel using its internal ID from a search result.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id required | integer | Hotel ID returned in search results |
curl "https://hotelgrade.eu/api/hotels/18234" \ -H "X-API-Key: hsa_your_key_here"
{
"success": true,
"data": { /* full hotel object */ }
}
Countries Statistics
Returns hotel count and star rating statistics per country. Useful for understanding coverage and market analysis.
curl "https://hotelgrade.eu/api/stats/countries" \ -H "X-API-Key: hsa_your_key_here"
{
"success": true,
"data": [
{
"country": "France",
"total_hotels": 13209,
"avg_stars": "3.2",
"min_stars": 1,
"max_stars": 5,
"hotels_with_stars": 12847
},
// ...
]
}
Stars Distribution
Count of hotels by star rating. Optionally filter by country.
Query parameters
| Parameter | Type | Description |
|---|---|---|
country optional | string | Filter by country |
curl "https://hotelgrade.eu/api/stats/stars?country=France" \ -H "X-API-Key: hsa_your_key_here"
{
"success": true,
"data": [
{ "stars": 1, "count": "512" },
{ "stars": 2, "count": "1840" },
{ "stars": 3, "count": "4203" },
{ "stars": 4, "count": "4918" },
{ "stars": 5, "count": "1736" }
]
}
Countries List
Returns all countries currently available in the database. Use this to populate a country picker in your UI.
{
"success": true,
"data": ["France", "Portugal", "Spain"]
}
Hotel Object Reference
Every hotel endpoint returns objects with the following fields:
| Field | Type | Description |
|---|---|---|
id | integer | Internal unique identifier |
name | string | Official hotel name |
stars | integer | null | Official star rating 1–5, or null if not classified |
country | string | Country name (e.g. France) |
country_code | string | ISO 3166-1 alpha-2 (e.g. FR) |
city | string | null | City name |
address | string | null | Full street address |
latitude | float | null | GPS latitude (WGS84) |
longitude | float | null | GPS longitude (WGS84) |
phone | string | null | Contact phone number |
website | string | null | Hotel website URL |
email | string | null | Contact email |
source | string | Data source identifier (see below) |
source_id | string | Original ID in the source registry |
created_at | ISO 8601 | First ingestion timestamp |
updated_at | ISO 8601 | Last update timestamp |
Source identifiers
| Source | Authority | Priority |
|---|---|---|
atout-france | Atout France — French Ministry of Tourism | 100 (highest) |
turismo-portugal | Turismo de Portugal — RNET registry | 100 |
spain-oficial | Spanish regional tourism authorities | 90 |
datatourisme | DATAtourisme — French open data platform | 70 |
overpass | OpenStreetMap community | 10 (lowest) |
When the same hotel exists in multiple sources, the highest-priority version is kept. Official government data always wins over community data.
Plans & Rate Limits
Rate limits apply per API key. When a limit is exceeded the API responds with HTTP 429.
- 100 requests / day
- 10 requests / minute
- 20 results per page
- Up to 3 API keys
- 2,000 requests / day
- 60 requests / minute
- 50 results per page
- Up to 3 API keys
- 50,000 requests / day
- 200 requests / minute
- 100 results per page
- Up to 3 API keys
- Unlimited requests / day
- 1,000 requests / minute
- 100 results per page
- SLA + dedicated support
Daily quotas reset at 00:00 UTC. The resets_at field in the 429 response gives the exact timestamp of the next reset.
Error Codes
All error responses follow a consistent JSON format:
{
"success": false,
"error": "Human-readable description of the error."
}
retry_after or resets_at429 response example
{
"success": false,
"error": "Limite journalière dépassée.",
"limit": 100,
"plan": "Free",
"resets_at": "2026-03-24T00:00:00.000Z",
"upgrade": "Passez à un plan supérieur pour augmenter votre quota."
}