Esta página aún no está traducida. Estás leyendo la versión en inglés.

API overview

Base URL, authentication with API keys and sessions, conventions, errors, quotas, pagination and versioning for the MapDove REST API.

The MapDove REST API exposes everything the app does: reference data, place-level foot traffic, market intelligence, the SDSS models, saved projects and account management. It is JSON over HTTPS, with GeoJSON for geometry.

The API is included on Pro, Business and Enterprise plans. The Free plan has no API keys and an API quota of zero.

Base URL

https://api.mapdove.com/api/v1

All paths in this reference are relative to /api/v1 unless noted (the health check also lives at the root). For local development the API listens on http://localhost:8088, so the same path is http://localhost:8088/api/v1/....

Authentication

Every data endpoint — cities, places, market, SDSS, projects — requires authentication. There are two mechanisms.

API keys (server-to-server)

Organization API keys are the way to integrate MapDove into your own systems. Send the key as a bearer token:

Authorization: Bearer ms_live_<key>
  • Keys belong to the organization, not to a user, and act with the permissions of the user who created them.
  • Every request authenticated with a key counts as one API call toward the organization’s monthly quota.
  • Keep keys on the server. Never embed them in browser or mobile code.

Sessions (browser)

The MapDove app uses a session cookie, ms_session (httpOnly, SameSite=Lax, Secure in production), set by POST /auth/login or POST /auth/signup. A session token may also be sent as Authorization: Bearer <token>. Sessions are intended for interactive use; for automation use API keys. See Account endpoints.

Key management

Action How
Create Settings → API keys (owners and admins), or POST /keys with { "name": "..." } from a signed-in session
List GET /keys — returns name, prefix, created, last used and revoked timestamps
Revoke DELETE /keys/:id — takes effect immediately

Key management, membership, billing and profile endpoints accept sessions only; an API key cannot create or revoke other keys. See Account endpoints.

The full secret (ms_live_…) is returned once, at creation. MapDove stores only a hash; if you lose a key, revoke it and create a new one. Plans limit the number of active keys (Pro 3, Business 10, Enterprise unlimited). Rotate keys by creating the new one, deploying it, then revoking the old one.

Your first request

curl https://api.mapdove.com/api/v1/cities \
  -H "Authorization: Bearer $MAPDOVE_KEY"
const res = await fetch('https://api.mapdove.com/api/v1/cities', {
  headers: { Authorization: `Bearer ${process.env.MAPDOVE_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).error.message);
const cities = await res.json();
console.log(cities.map((c) => c.slug));
import os, requests

r = requests.get(
    "https://api.mapdove.com/api/v1/cities",
    headers={"Authorization": f"Bearer {os.environ['MAPDOVE_KEY']}"},
    timeout=30,
)
r.raise_for_status()
print([c["slug"] for c in r.json()])

Conventions

Topic Convention
Format JSON request and response bodies (Content-Type: application/json); CSV for export.csv
Dates YYYY-MM-DD
Timestamps RFC 3339, e.g. 2026-11-01T14:03:22Z
IDs Users, orgs, keys, projects, alerts: UUID strings. Cities, categories, brands: integers with string slugs (use slugs in paths and params). Places and hexes: integers
Coordinates lng, lat in decimal degrees (WGS 84)
GeoJSON FeatureCollection in EPSG:4326; coordinates [lng, lat]
Units Fields suffixed _km, _km2 are metric; income is in the city’s currency (USD or AED)
Percentages *_pct fields are percentages (4.7 = 4.7%); share, overlap and market_share are fractions (0–1)
Empty results Empty arrays, never null lists

Date ranges

Analytics endpoints accept from and to. Defaults and rules:

  • to defaults to the last data day (2026-10-31) and is capped to it.
  • from defaults to to minus 89 days — the last 90 days of data.
  • The plan’s history_days clamps the range: on Free, nothing earlier than 90 days before the last data day; on paid plans, 365 days. Out-of-range values are clamped, not rejected.
  • from after to returns 400 bad_request.
  • The previous period (for change_pct, visits_prev) has the same length and ends the day before from.

Errors

Errors use one envelope with a matching HTTP status:

{ "error": { "code": "plan_limit", "message": "Your free plan includes 25 analyses per month. Upgrade to run more." } }
Status code Meaning
400 bad_request Invalid parameter or body (bad date, unknown criterion, point outside the city, too many rings)
401 unauthorized Missing, invalid or revoked credentials
401 totp_required Login only: two-factor code missing or invalid
402 plan_limit A plan limit or quota is reached (API calls, analyses, keys, seats, alerts, exports not included)
403 forbidden Authenticated, but not allowed (role, suspended workspace, API key on a session-only endpoint)
404 not_found The resource does not exist or is not visible to your organization
409 conflict Duplicate, e.g. an email that is already registered
429 rate_limited Too many requests in a short window
500 internal Unexpected server error — safe to retry with backoff

Show message to humans; branch on code in code.

Quotas and rate limits

Monthly quotas are per organization and reset on the first day of each calendar month (UTC):

Plan API calls / month SDSS analyses / month
Free 0 25
Pro 10,000 500
Business 100,000 unlimited
Enterprise unlimited unlimited
  • Each request authenticated with an API key counts as one API call, including failed ones.
  • Each successful SDSS call (/sdss/*) also counts one analysis.
  • Over quota, requests return 402 plan_limit until the next month or an upgrade. Do not retry 402s automatically.
  • Check current usage with GET /org/usage (reference).

Short-term throttling. To protect the service, bursts may be throttled with 429 rate_limited. Retry with exponential backoff and jitter:

import random, time, requests

def get_with_backoff(url, headers, attempts=6):
    for n in range(attempts):
        r = requests.get(url, headers=headers, timeout=60)
        if r.status_code not in (429, 500, 502, 503):
            return r
        time.sleep(min(30, 2 ** n) + random.random())
    r.raise_for_status()

SDSS calls are compute-heavy (a Huff run can take around a second); run them sequentially or with low concurrency, and cache results — store them as projects if you need them later.

Pagination

List endpoints that can return many rows accept limit; GET /places also accepts offset for paging:

Endpoint Default limit Max offset
GET /places 100 500 yes
GET /rankings 50 500 no — top N only
GET /places/:id/cross-shopping 10 50 no

Values above the maximum are clamped to it.

GET /places returns { total, items }; iterate with offset += limit until offset >= total:

async function allPlaces(city, key) {
  const out = [];
  for (let offset = 0; ; offset += 500) {
    const url = `https://api.mapdove.com/api/v1/places?city=${city}&limit=500&offset=${offset}`;
    const page = await (await fetch(url, { headers: { Authorization: `Bearer ${key}` } })).json();
    out.push(...page.items);
    if (offset + 500 >= page.total) return out;
  }
}

Each page is one API call.

Versioning

The version is in the path (/api/v1). Within v1 we only make additive changes: new endpoints, new optional parameters, new response fields. Clients should ignore unknown fields. Breaking changes ship as a new version with advance notice in the changelog.

Reference

  • Reference data — health, stats, plans, cities, hexes, neighborhoods, categories, brands, segments
  • Places — places, visits, hourly, trade areas, cross-shopping, export, nearby, compare
  • Market — overview, rankings, brand performance
  • SDSS — suitability, Huff, catchment, compare sites
  • Projects and alerts — projects, favorites, alerts
  • Account — auth, org, members, keys, usage, billing