API Integration Guide
How to stay under endpoint rate limits, reduce credit usage, and handle errors gracefully when integrating Data Docked.
Each endpoint has its own request-per-minute ceiling, set based on the cost of the underlying query. Exceeding it returns 429 Too Many Requests.
Heavier endpoints that return larger payloads or run more expensive queries.
Reference and lookup endpoints with moderate payloads.
Realtime position lookups designed for high-frequency polling.
See each endpoint’s reference page for its exact limit and credit cost — and the pricing page for plan options.
Apply these patterns to keep request volume — and credit burn — proportional to the data you actually need.
Instead of making individual requests for each vessel, use our vessels-by-area or vessel-location-bulk endpoints with multiple identifiers.
// Instead of multiple single requests
// GET /api/v1/vessel/211234567
// GET /api/v1/vessel/211234568
// GET /api/v1/vessel/211234569
// Use the bulk endpoint (1 request)
GET /api/v1/vessels?mmsi=211234567,211234568,211234569When monitoring an area, define the smallest bounding box that covers your region of interest to reduce data transfer.
// Singapore Strait focused area
GET /api/v1/vessels/area?lat_min=1.1&lat_max=1.4&lon_min=103.5&lon_max=104.1Request only the fields you need to reduce response size and improve parsing speed.
// Only get position and identity data
GET /api/v1/vessel/211234567?fields=mmsi,name,lat,lon,speed,course,timestampVessel name, IMO, dimensions, and type rarely change. Cache this data for 24 hours and refresh only when needed.
Terrestrial AIS positions refresh every 2-10 seconds; satellite positions every few minutes. Polling faster than the data changes wastes credits and pressures rate limits.
Store historical tracks locally rather than re-requesting them. Once retrieved, track data doesn't change.
Implement a queue that respects each endpoint's req/min band and processes requests in order to avoid bursts.
Route all API calls through a single service to track usage per endpoint and apply consistent caching.
For real-time updates on many vessels, webhooks are more efficient than polling. Contact us for enterprise webhook support.
Treat 429 as expected — pace requests to the endpoint’s band rather than reacting after a failure. Distinguish 403 (out of credits) from 401 (bad or missing key).
| Code | Status | Description | Action |
|---|---|---|---|
| 200 | OK | Request successful | Process response normally |
| 400 | Bad Request | Invalid parameters | Check request format and parameters |
| 401 | Unauthorized | Invalid or missing API key | Verify API key is correct |
| 403 | Forbidden | Out of credits or access denied | Check credit balance — distinct from 401 (auth) |
| 404 | Not Found | Resource not found | Verify MMSI/IMO exists in database |
| 429 | Too Many Requests | Endpoint rate limit exceeded | Pace requests to the band; retry with backoff |
| 500 | Server Error | Internal server error | Retry after delay, contact support if persistent |
async function fetchWithRetry(url, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url);
if (response.status === 429) {
const waitTime = Math.pow(2, i) * 1000;
await sleep(waitTime);
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Two complementary signals tell you when you’re approaching a limit: the dashboard credit balance and your own client-side request counters.
Your current credit balance is visible in the account dashboard. Use it to verify usage and top up before you run out — a 403 response means your balance hit zero.
Instrument a counter per endpoint on the client side. Alert when request volume approaches the band’s req/min ceiling — and when a 429 actually fires, so you can tune your pacing.
A production-ready client with paced retries and clean error handling.
class DataDockedClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.datadocked.com/api/v1';
}
async request(endpoint, options = {}) {
const maxRetries = 3;
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers,
},
});
// Rate limited — back off exponentially
if (response.status === 429) {
const waitMs = Math.pow(2, attempt) * 1000;
await this.sleep(waitMs);
continue;
}
// Server error — retry with backoff
if (response.status >= 500) {
const waitMs = Math.pow(2, attempt) * 1000;
await this.sleep(waitMs);
continue;
}
// Success or client error — return as-is
return response.json();
} catch (error) {
lastError = error;
const waitMs = Math.pow(2, attempt) * 1000;
await this.sleep(waitMs);
}
}
throw lastError || new Error('Request failed after retries');
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Get single vessel
async getVessel(mmsi) {
return this.request(`/vessel/${mmsi}`);
}
// Get vessels in area
async getVesselsInArea(bounds) {
const { latMin, latMax, lonMin, lonMax } = bounds;
return this.request(
`/vessels/area?lat_min=${latMin}&lat_max=${latMax}&lon_min=${lonMin}&lon_max=${lonMax}`
);
}
}
// Usage
const client = new DataDockedClient('your_api_key');
const vessel = await client.getVessel('211234567');Sign up for free and get 20 credits to test our API. No credit card required.