Best practices
Non-binding recommendations that will make your integration faster, cheaper, and easier to operate. None of these are required — the API works without them — but every one of them has come up in real customer support tickets.
Networking & retries
Always set a request timeout
A hung request is worse than a failed one. Set explicit connect + read timeouts on every API call.
requests.get(url, headers=headers, timeout=(5, 30)) # 5s connect, 30s read
const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000);
fetch(url, {headers, signal: controller.signal});
Retry transient failures with backoff + jitter
Retry on 429, 500, 502, 503, 504. Don't retry 400, 401, 403, 404 — they fail the same way next time.
Combine exponential backoff with random jitter so parallel workers don't all retry at the same instant:
import random, time
def backoff(attempt: int) -> float:
return min(60, (2 ** attempt) + random.uniform(0, 1))
Cap at ~5 attempts; surface the failure to your caller after that.
Honour Retry-After headers
When you receive a 429, the response includes Retry-After (seconds). Use it as the wait time instead of your own backoff for that attempt. See Rate limits.
Pagination & data volume
Use the biggest page size you can
One request returning 1,000 records is roughly ten times cheaper than ten requests returning 100. Network round-trip dominates per-call cost.
Narrow with filters before paginating
A date range, site list, or record-type filter applied server-side is always cheaper than client-side filtering after the fact.
Don't fetch what you don't need
If you only display serial + last_login_time, don't pull the full device record. (The API reference lists exactly which fields each endpoint returns — use it to plan your requests.)
Caching
Cache anything that rarely changes.
| Cache for | Resource |
|---|---|
| Hours | Site list, site metadata, account metadata |
| Minutes | Per-site configuration |
| Don't cache | Live measurement data, export status, command results |
Conditional requests where supported
One endpoint — currently the batch-request status summary (GET /api/v1/devices/batch-requests/{batch_request_id}/) — supports ETag + If-None-Match conditional requests. The /results/ endpoint alongside it does not. When the resource hasn't changed since you last saw it, the server returns 304 Not Modified with no body, saving the bandwidth.
# First call — server returns 200 + ETag
curl -i -H "Authorization: Token ..." \
https://mytrafficdata.com/api/v1/devices/batch-requests/abc-123/
# ...response includes: ETag: "a1b2c3"
# Subsequent call — pass the ETag back
curl -i -H "Authorization: Token ..." \
-H 'If-None-Match: "a1b2c3"' \
https://mytrafficdata.com/api/v1/devices/batch-requests/abc-123/
# ...returns 304 Not Modified if unchanged
Most endpoints don't emit ETag yet. For those, use a client-side TTL or your platform's standard cache layer; invalidate on user-driven actions.
Idempotency on writes
For write operations that dispatch device commands or create resources, assume the network can drop the response before you see it. If a retry creates a duplicate, you have a problem.
The MyTrafficData API implements the Idempotency-Key HTTP header pattern (IETF draft) on selected write endpoints. In /api/v1/ that is:
| Endpoint | Idempotent? |
|---|---|
POST /api/v1/devices/dsd/flex/trigger/{id}/activate/ | ✅ Yes |
POST /api/v1/devices/dsd/flex/trigger/{id}/deactivate/ | ✅ Yes |
POST /api/v1/devices/dsd/by-serial/{int_serial}/flex/configure/lte/push/ | ❌ No — use read-after-write |
The endpoints that accept the header declare it as a parameter in the per-endpoint reference, so a generated client will expose it. Treat that reference as authoritative: sending the header to an endpoint that doesn't implement it is silently ignored, not rejected, so a retry will repeat the side effect.
How to use it on supported endpoints:
- Generate a UUID before the first attempt.
- Send it in the
Idempotency-Keyrequest header. - Retry with the same key on network failures. The server detects the duplicate, returns the original response, and does not repeat the side effect.
- Use a new key for a logically new operation.
curl -X POST \
-H "Authorization: Token ..." \
-H "Idempotency-Key: 6b2f9d3a-3e3b-4a1f-9e8d-7c4d8f2a1e5d" \
-H "Content-Type: application/json" \
-d '{...}' \
https://mytrafficdata.com/api/v1/devices/dsd/flex/trigger/{id}/activate/
Notes:
- Keys are scoped per endpoint — the same UUID on a different endpoint is treated as a new request.
- Keys are remembered for 72 hours, then expire.
- If you replay a key with a different payload, you get a
409 Conflictrather than the duplicated side effect.
For endpoints that don't accept Idempotency-Key — including the LTE push above — the read-after-write fallback works: after a successful POST, check the listing or status endpoint to confirm before retrying.
Token & secret hygiene
- One token per integration. Never share a token across multiple deployments. Revoking one shouldn't take down everything.
- Secret manager, not source control. Tokens have the same blast radius as passwords.
- Rotate periodically. Every 6–12 months, or immediately after any suspected leak. See Authentication.
- Audit usage. The MyTrafficData dashboard shows when your token was last used. Investigate unexpected gaps or spikes.
Time & timezones
- Always use ISO 8601 strings in requests and responses (we always do; you should too).
- Read the offset in each response value — don't assume a single timezone for the whole response. Different fields can carry different offsets. See Timezones.
- Store timestamps as UTC in your own database if you ever do timezone math; convert to local for display only.
Error handling
- Branch on the HTTP status code first, then inspect the body shape. v1 has three body shapes — see Errors.
- Tolerate unknown fields in error bodies. We may add new fields (e.g., trace IDs, new error codes) without bumping the major version.
- Surface human-readable messages (
message,detail, or per-field arrays) directly to your end users — they're written to be safe. - Log the full response body on unexpected failures, plus the request URL, method, and timestamp. This shortens any support ticket dramatically.
- Branch on
error.codefor domain errors (INVALID_ID,TIME_RANGE_EXCEEDED, etc.) — those codes are stable.
Operational hygiene
- Monitor your own metrics — request count, latency p95, error rate by status code. We can't tell you why your integration broke at 3am; your metrics can.
- Identify yourself. Set a meaningful
User-Agentheader (MyApp/1.4.2 (ops@example.com)) so we can correlate traffic if you contact support. - Test against a staging account if you have one — don't write integration tests that hit production data.
Summary checklist
- ✅ Set timeouts on every request.
- ✅ Retry transient errors with exponential backoff + jitter.
- ✅ Honour
Retry-Afteron 429s. - ✅ Page with bigger
limitvalues; narrow with filters first. - ✅ Cache stable resources (site list, account metadata).
- ✅ Use the
Idempotency-Keyheader on supported endpoints. - ✅ One token per integration; rotate regularly.
- ✅ Branch on HTTP status first; tolerate unknown fields.
- ✅ Set a meaningful
User-Agent.