API Integration Guide

API Best Practices

How to stay under endpoint rate limits, reduce credit usage, and handle errors gracefully when integrating Data Docked.

TL;DR

  • Rate limits are per-endpoint, not per-plan.
  • Limits range from 15 to 100 requests/minute depending on the endpoint.
  • Credit cost is documented on each endpoint’s reference page.
  • Use caching, batching, and exponential backoff to stay under limits.

Rate Limits Explained

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.

15 req/min

Heavier endpoints that return larger payloads or run more expensive queries.

50 req/min

Reference and lookup endpoints with moderate payloads.

100 req/min

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.

Optimize Your Usage

Apply these patterns to keep request volume — and credit burn — proportional to the data you actually need.

Efficient Querying

Use batch endpoints when possible

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,211234569

Filter by bounding box

When 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.1

Use field selection

Request 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,timestamp

Caching Strategy

Cache static vessel data

Vessel name, IMO, dimensions, and type rarely change. Cache this data for 24 hours and refresh only when needed.

Respect update frequencies

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.

Implement local storage

Store historical tracks locally rather than re-requesting them. Once retrieved, track data doesn't change.

Architecture Patterns

Use a request queue

Implement a queue that respects each endpoint's req/min band and processes requests in order to avoid bursts.

Centralize API calls

Route all API calls through a single service to track usage per endpoint and apply consistent caching.

Consider webhooks for high-volume

For real-time updates on many vessels, webhooks are more efficient than polling. Contact us for enterprise webhook support.

Handle Errors Gracefully

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).

HTTP Status Codes

CodeStatusDescriptionAction
200OKRequest successfulProcess response normally
400Bad RequestInvalid parametersCheck request format and parameters
401UnauthorizedInvalid or missing API keyVerify API key is correct
403ForbiddenOut of credits or access deniedCheck credit balance — distinct from 401 (auth)
404Not FoundResource not foundVerify MMSI/IMO exists in database
429Too Many RequestsEndpoint rate limit exceededPace requests to the band; retry with backoff
500Server ErrorInternal server errorRetry after delay, contact support if persistent

Retry with exponential backoff

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');
}

Monitor Your Usage

Two complementary signals tell you when you’re approaching a limit: the dashboard credit balance and your own client-side request counters.

Dashboard credits widget

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.

Open dashboard →

Track 429s in your own logs

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.

Complete Retry Example

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');

Ready to Start Building?

Sign up for free and get 20 credits to test our API. No credit card required.