When to use it
Use partner sync if you keep a local mirror of a user’s library and need to stay in step with it. If you only read a screenful at a time, the ordinary library endpoints withlimit and cursor are simpler and enough.
Reading: snapshot, then follow the feed
The change feed is populated by database triggers, so web activity, imports, provider webhooks, first-party apps, and your own writes all land in one ordered feed per account. Resources are filtered to the read scopes on your token.1
Open a cursor
Call
GET /api/platform/v1/me/sync/changes with no cursor. Keep the
returned nextCursor. A new client sees resetRequired: true.2
Backfill each resource
For every resource named in
resources, page through
GET /api/platform/v1/me/sync/snapshot?resource=...&after=...&limit=500
until it is exhausted.3
Follow the feed
Call
/me/sync/changes?cursor=RETAINED_CURSOR. Apply changes in the order
returned, and persist each nextCursor only after that page has committed
locally. If you persist the cursor first and then fail, you have silently
skipped changes.4
Handle tombstones and resets
operation: "delete" with data: null is a tombstone — remove the row. If
resetRequired ever becomes true, discard the mirror and repeat the
snapshot step.resetRequired: true rather than an error.
Keep the mirror and cursor per PunchPlay account, app, and read-scope set. If a
user later grants another read scope, start a new cursor and snapshot the newly
visible resource; an older cursor does not backfill history that the earlier
token could not read.
Delivery is at-least-once
Apply every change idempotently, keyed onresource and resourceId. Receiving
the same change twice is normal and must be harmless.
This is a deliberate trade. A change’s id is assigned when it is written, but
it only becomes visible when its transaction commits — so two changes can
commit out of order. A cursor therefore also records which transactions were
still open when it was issued, and the next read re-checks them. The
alternative is worse: a change committing late would fall behind the cursor
and never be delivered at all, leaving your mirror silently wrong.
Writing: bulk endpoints
Each request takes up to 100 items within a 512 KiB body. Size batches against
the byte limit as well as the item count — a batch of long-titled entries can
reach 512 KiB before it reaches 100 items.
Responses carry one outcome per submitted index. An
invalid item does not fail
its valid neighbours, so surface the per-item error text rather than treating
the whole batch as failed.
History items also need a stable client_item_id, derived from your source
watch event, for example nuvio-profile-4:history:1735927200:tmdb:550. It
prevents duplicates across separately keyed batches.
Identifying items
Every bulk item carries at least one oftmdb_id, imdb_id, tvdb_id or
mal_id. If IMDb is your canonical identifier, send it directly — there is no
need to translate to TMDB first. Sending several ids at once is allowed:
tmdb_id is the write identity, and the others are cross-checked against
evidence already held, at no extra upstream cost.
title and year are optional on history items, and title on watchlist
items. Anything omitted is hydrated from TMDB, and a write is never lost merely
because its metadata could not be fetched.
Deferred items
An item whose identifiers cannot be resolved yet comes back asdeferred. It
has been accepted and durably stored, and PunchPlay retries it on its own
schedule. BulkMutationResponse counts these in a deferred total.
Resending a deferred item only re-banks the same record. Treat it as accepted
and move on; if a later sync of the same item succeeds outright, the banked copy
is cleared automatically, so nothing is applied twice.
Applied items carry
resolved_tmdb_id. Cache it and send it back as tmdb_id
on later syncs — that removes resolution from the request path entirely and is
the single biggest saving available to an IMDb-first client.
This no-loss guarantee covers the bulk endpoints. Path-identified writes have
no banking and return a retryable
503 when resolution is unavailable.client_item_id and the accepted response
locally, and use the change feed or a later mirror read to observe when the
result has materialized. A dashboard for deferred backlog state, retry timing,
and terminal outcomes remains a product decision for a future contract.
Partner ids in paths
The same identifiers work anywhere a path takes a title{id} — no translation
to TMDB first:
Three failure modes, and they want different handling:
Idempotency
Every bulk write requires anIdempotency-Key header. Keys are retained for 24
hours, and replaying a key with the same body returns the original result rather
than applying it twice.
Three responses need different handling, and the difference matters:
Rate limits
Bulk endpoints allow 30 requests per minute per operation, app, and user, and the change and snapshot reads allow 120 per minute per app and user. The per-token and per-app buckets in Errors & Rate Limits apply on top of those. Throttle onX-RateLimit-Remaining and honour Retry-After on 429.
For support, retain the request ID, UTC timestamp, endpoint, status, and error
body. Ask questions in the PunchPlay Discord
without sharing credentials.