Error Responses
This document describes the error response format and common error codes used by the Geog API.
Response Format
All error responses follow a consistent JSON format:
{
"success": false,
"error": "Error Type",
"message": "Human-readable error description",
"code": "MACHINE_READABLE_CODE"
}
| Field | Type | Description |
|---|---|---|
success | boolean | Always false for errors |
error | string | Short error type or title |
message | string | Detailed, human-readable explanation |
code | string | Machine-readable error code for programmatic handling |
HTTP Status Codes
2xx Success
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request successful |
| 204 | No Content | Request successful, no response body (e.g., empty tile) |
4xx Client Errors
| Code | Status | Description |
|---|---|---|
| 400 | Bad Request | Invalid request parameters or body |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource does not exist |
| 429 | Too Many Requests | Rate limit exceeded |
5xx Server Errors
| Code | Status | Description |
|---|---|---|
| 500 | Internal Server Error | Unexpected server error |
| 502 | Bad Gateway | Upstream service failure (e.g., JWKS fetch) |
| 503 | Service Unavailable | Service temporarily unavailable |
Error Codes Reference
Authentication Errors
AUTH_REQUIRED
Status: 401 Unauthorized
Cause: Request lacks authentication credentials.
Response:
{
"success": false,
"error": "Unauthorized",
"message": "Missing or invalid authorization header",
"code": "AUTH_REQUIRED"
}
Resolution: Include a valid Bearer token in the Authorization header.
INVALID_TOKEN
Status: 401 Unauthorized
Cause: JWT token is malformed or invalid.
Response:
{
"success": false,
"error": "Unauthorized",
"message": "Invalid token format",
"code": "INVALID_TOKEN"
}
Resolution: Ensure the token is a valid self-issued JWT.
TOKEN_EXPIRED
Status: 401 Unauthorized
Cause: JWT token has expired.
Response:
{
"success": false,
"error": "Unauthorized",
"message": "Token has expired",
"code": "TOKEN_EXPIRED"
}
Resolution: Refresh the token or re-authenticate.
INSUFFICIENT_SCOPE
Status: 403 Forbidden
Cause: Token lacks required permission scope.
Response:
{
"success": false,
"error": "Forbidden",
"message": "Token does not have required scope: tiles:read",
"code": "INSUFFICIENT_SCOPE"
}
Resolution: Request a token with the required scopes.
JWKS_FETCH_FAILED
Status: 502 Bad Gateway
Cause: Unable to fetch JWKS for token validation.
Response:
{
"success": false,
"error": "Bad Gateway",
"message": "Failed to fetch JWKS",
"code": "JWKS_FETCH_FAILED"
}
Resolution: Retry the request. Contact support if persistent.
Vector Tiles Errors
INVALID_EXTENSION
Status: 400 Bad Request
Cause: Tile request uses unsupported file extension.
Response:
{
"success": false,
"error": "Bad Request",
"message": "Invalid tile extension. Must be 'mvt' or 'pbf'",
"code": "INVALID_EXTENSION"
}
Resolution: Use .mvt or .pbf extension in tile requests.
TILE_NOT_FOUND
Status: 404 Not Found
Cause: Requested tile does not exist or is outside valid zoom range.
Response:
{
"success": false,
"error": "Not Found",
"message": "Tile not found at z/x/y",
"code": "TILE_NOT_FOUND"
}
Resolution: Verify tile coordinates are within valid range for the tileset.
TILESET_NOT_FOUND
Status: 404 Not Found
Cause: Requested tileset does not exist.
Response:
{
"success": false,
"error": "Not Found",
"message": "Tileset 'unknown' not found",
"code": "TILESET_NOT_FOUND"
}
Resolution: Verify tileset name is correct.
Rate Limiting Errors
RATE_LIMIT_EXCEEDED
Status: 429 Too Many Requests
Cause: Client has exceeded rate limit.
Response:
{
"success": false,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please retry after 60 seconds.",
"code": "RATE_LIMIT_EXCEEDED",
"retryAfter": 60
}
Headers:
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1699999999
Resolution: Wait for the rate limit window to reset. Implement exponential backoff.
Validation Errors
VALIDATION_ERROR
Status: 400 Bad Request
Cause: Request body failed validation.
Response:
{
"success": false,
"error": "Bad Request",
"message": "Validation failed",
"code": "VALIDATION_ERROR",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
}
]
}
Resolution: Check the details array for specific field errors.
Client Implementation
Retry Wrapper with Error Handling
// Helper function for delays
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// Token management (implement based on your auth system)
let token: string;
async function refreshToken() {
/* ... */
}
async function apiRequest(url: string, options?: RequestInit, retryCount = 0) {
const MAX_RETRIES = 3;
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
...options?.headers
}
});
if (!response.ok) {
const error = await response.json();
switch (error.code) {
case 'TOKEN_EXPIRED':
// Refresh token and retry (with retry limit)
if (retryCount < MAX_RETRIES) {
await refreshToken();
return apiRequest(url, options, retryCount + 1);
}
throw new ApiError(error);
case 'RATE_LIMIT_EXCEEDED':
// Wait and retry with backoff (with retry limit)
if (retryCount < MAX_RETRIES) {
await sleep(error.retryAfter * 1000);
return apiRequest(url, options, retryCount + 1);
}
throw new ApiError(error);
case 'VALIDATION_ERROR':
// Handle validation errors
throw new ValidationError(error.details);
default:
throw new ApiError(error);
}
}
return response.json();
}
Logging Errors
Include the error code and message in logs for debugging:
try {
await apiRequest('/v1/tiles/tileset/14/4680/6125.mvt');
} catch (error) {
const { code, message } = error as ApiError;
console.error(`API Error [${code}]: ${message}`);
}
User-Facing Messages
Map error codes to user-friendly messages:
const userMessages = {
AUTH_REQUIRED: 'Please log in to continue.',
TOKEN_EXPIRED: 'Your session has expired. Please log in again.',
INSUFFICIENT_SCOPE: 'You do not have permission to access this resource.',
RATE_LIMIT_EXCEEDED: 'Too many requests. Please wait a moment.'
};
Handling Rate Limits
Free Tier Limit Handling
async function fetchTile(url: string, token: string) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }
});
if (response.status === 429) {
const data = await response.json();
const resetDate = new Date(data.reset);
// Show user when limits reset
console.warn(`Rate limit exceeded. Resets at ${resetDate.toLocaleString()}`);
// Consider upgrading
showUpgradePrompt();
return null;
}
return response;
}
Tips for free tier limits:
- Monitor rate limit headers: Check
X-RateLimit-<Type>-Remainingbefore making requests - Cache aggressively: Free tier limits are generous but caching reduces requests
- Query usage endpoint: Monitor your usage throughout the day to avoid surprises
Exponential Backoff
// Helper function for delays
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
async function fetchWithRetry(url: string, retries = 3) {
for (let i = 0; i < retries; i++) {
const response = await fetch(url);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
await sleep(retryAfter * 1000 * Math.pow(2, i));
continue;
}
return response;
}
throw new Error('Rate limit exceeded after retries');
}
Optimizing Usage
- Cache responses: Vector tiles are cacheable for 24 hours. Implement client-side caching.
- Use appropriate zoom levels: Request only the zoom levels needed for your use case.
- Implement request deduplication: Avoid duplicate requests for the same resource.
See Also
- Authentication — Token types and scopes
- Rate Limiting — Rate limit details and headers
- API Overview — Full endpoint reference
- Examples — Working integration examples