Skip to main content
The library endpoints — history, ratings, collection, lists — are shaped for presentation. Re-downloading them on a timer is expensive for you and for PunchPlay, and it cannot tell you what changed. Partner sync exists for clients that maintain their own copy of an account: a durable change feed to read from, and bulk endpoints to write back through.

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 with limit 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.
Cursors stay valid for 90 days. A cursor older than that returns 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 on resource 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 of tmdb_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.
client_item_id is required on any item identified only by imdb_id, tvdb_id or mal_id. Such an item may be deferred, and a deferred item is banked under that key so a corrected resend updates the same record instead of creating a second one. Rating and watchlist items carrying tmdb_id may still omit it — history items must always send it, regardless of which ids accompany them.

Deferred items

An item whose identifiers cannot be resolved yet comes back as deferred. 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.
The public partner contract does not currently expose a per-item deferred-status or requeue endpoint. Keep the source 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:
Path routes do not bank. A single path write has no client_item_id to key a stored record on, so when resolution fails nothing is kept and the retry is yours to own — which is why an outage here is a 503 rather than the deferred bulk sync returns. For anything batch-shaped, or anything you cannot afford to lose, use bulk sync.

Idempotency

Every bulk write requires an Idempotency-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:
idempotency_indeterminate means the write may or may not have applied. PunchPlay will not repeat it blindly, because doing so could double-log a user’s watch history. Read /me/sync/changes to establish what actually landed, then retry the remainder under a new key. Reusing the old key only returns the same error.

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 on X-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.