Both hosts serve the same contract. The
environment column is the name your key is bound
to, which is what GET /v1/health reports back. Integrate against the sandbox, then change
one config value to go live: read the whole base URL from config, version prefix included,
rather than compiling it in.
One caveat: the sandbox runs against the production dataset. Users created with a sandbox key are real profiles in the same database,
and the isolation is the key’s environment rather than a separate copy of the data. Use
throwaway addresses on a domain you own for sandbox users, and ask us when you want them
cleaned up.
Headers
Errors
error.code is stable and machine-readable, so branch on it rather than on message.
message is for your logs and should not be shown to end users. details is present only
where it adds something, and its shape varies by code.
Those nine are the published application errors. Four more are framework-level codes that a
malformed or unsupported request can trigger, and a strict
switch on error.code will hit
them during integration:
Per-item rejections are not errors. A batch containing unusable items still returns
200,
and the items come back in rejected[] with a reason; see
The response. error.code means the whole request failed.
Retries and idempotency
Every endpoint is safe to retry, so a timeout never needs special handling:POST /usersis idempotent on email. A retry returns the sameuser_idrather than creating a second profile.POST /users/{user_id}/mediais safe to re-send. Photos that matched upsert on your asset id; photos that matched nothing are re-evaluated rather than duplicated. See Incremental syncs.POST /users/{user_id}/sync-completeis idempotent; see Telling FanFeed a sync finished.- The
GETendpoints are reads.
429 after Retry-After and 5xx with exponential backoff. Do not retry a 4xx other
than 429; the request will fail the same way again.
Timestamps
ISO 8601, UTC, on everything you send. One exception on the way back: an event’sstarts_at_local is the local date and time at the venue with no offset. Do not convert
it. It is already the time the user experienced, and shifting it will show someone a
7:30 p.m. concert at 2:30 a.m.
Pagination
Cursor-based. Passnext_cursor from the previous response as cursor. has_more tells you
when to stop. Cursors are opaque; do not parse them.
Operational endpoints
Two more endpoints sit outside the five that carry the integration. Neither is part of the API reference, and neither touches user data.GET /v1/ping needs no key and is never rate-limited. Returns:
GET /v1/health requires a valid key, but is deliberately not rate-limited, so it
still answers while you are being throttled. It echoes back the key’s partner_id and
environment along with the rate-limit policy in force for that key:
429 to see what the limit actually is; see
Rate limiting.