- DX’s private API, which only DizCore calls, over a private network. Nothing on it faces the internet.
- The staff host, at base path
/v1, which the DX console calls through the staff BFF.
packages/contracts/openapi/authoring.yaml (OpenAPI 3.1), with its shapes in packages/contracts/schemas/authoring.yaml.
Who calls it
Clients never call it. Client platforms, the Creator Network and, later, Da Vinci call
api.dizplai.com. DizCore checks their machine credentials and per-client limits, answers their reads from its own copy, and forwards their writes, votes and Fan Zone messages here. Fans’ tokens pass through to DX, which checks them. Its public reference, on developer.dizplai.com, is DizCore’s Dizplai API, which documents the writes, votes and messages DX receives. DX publishes no API reference of its own.
Our hubs never call it either. They call the Fan API directly, on each hub’s own address, and never route through DizCore.
Signing in and headers
On the private API. The network comes first: an API Gateway private REST API, reached through an interface VPC endpoint in DizCore’s VPC, shared with AWS RAM. The API’s resource policy accepts only that endpoint. Then:
DX never receives a client’s machine credential (the app’s publishable key, a platform’s secret key or issuer token), and trusts DizCore’s word for which client is calling. It doesn’t take DizCore’s word for the fan: it checks the fan’s token itself, so a leaked service key alone can’t vote as a fan. DX enforces one vote per fan, the per-fan limits, its own throttles, every business rule on writes, and the fan side’s integrity checks. DizCore keeps only the per-client limits.
A missing, invalid or expired fan token answers
401 (invalid-fan-token) with WWW-Authenticate: Bearer error="invalid_token", which DizCore passes back to the app. Each client login’s settings (issuer, keys address, audience, user-ID claim and lifetime) are held by DX’s Fan identity, carried over from today’s embedded sign-in (DEV-3095).
On the staff host. The console signs people in, and the staff BFF calls this API with their token from the staff user pool. Dizplai staff sign in through IAM Identity Center; clients’ own producers are invited users with MFA. The BFF sets x-dizplai-org-id to the console’s active client after checking the user’s memberships. What someone may do comes from their role (ADR-7): Dizplai staff with the viewer role can read every client’s experiences, and operators can change one only under a 15-minute act-as grant.
Statuses
There are no drafts and no publishing. An item is createdhidden, unless you send scheduled or live, and its status changes with PATCH {"status": …}. Status is one open set, shared with the Dizplai API: values are added, never removed.
locked and settled are DX’s additions to the Dizplai API’s set, still to agree with DizCore. An invalid transition answers 400. An experience’s status is worked out from its items until DX sends its own: live if any item is, otherwise hidden, otherwise closed. PATCH an experience to deleted to retire it and every item in it.
Where experiences are kept, and how a hub gets them
DX owns experience definitions. Experiences and their items are saved in DX first, in Production’s DynamoDBdefinitions table (the authoring store, formerly drafts). Saving never waits on DizCore.
DizCore holds the one read copy. DizCore is Dizplai’s company data platform. It holds the one read copy of DX’s experiences and results, kept current by DX’s push feed, and answers every read by other Dizplai products and client platforms. It also owns who each client is.
The fan side runs from its own fast copy. Each fan side keeps a copy of its experiences, and is the only writer of live state and of votes, tallies and results. A fan’s vote is checked against that copy, never against DizCore.
- Every write is checked, then saved in DX. This API checks each item against its widget type’s plugin schema, then saves it in the
definitionstable. A quiz’s correct answers stay in DX, and never leave it before the quiz is marked. - Every create or edit sends whole items to the fan side through the staff contract:
experience.upsertwith every item, orwidget.upsertwith one. Never a diff. A new item arrives hidden, and the fan side swaps an edited one in only if itsdefinitionVersionis newer than the copy it holds. - Status changes go straight to the fan side, as
widget.lifecycle,widget.settleandwidget.removecommands, and never wait on DizCore.starts_atandends_attravel in the upserts, and the fan side sets and clears its timers from them. - DX’s push feed keeps DizCore’s copy current. After any change to an experience, its items’ state or their results, DX sends DizCore the whole experience. See The push feed to DizCore.
- A nightly reconciliation compares the
definitionstable with each fan side’s copy, checks DizCore’s copy against the push feed’s latest versions, and reports any difference.
The push feed to DizCore
- One complete document per experience: every item, deleted ones included, never a delta. At most two deliveries a second per experience.
- Signed webhooks to DizCore’s receiver (Standard Webhooks), retried for 24 hours.
- Recovery and catch-up pulls are on the private API:
GET /v1/push/feeds/experiences/{experience_id}returns the latest document, andGET /v1/push/feeds/experiences?updated_since=…&after=…&limit=…every document changed since a time. - One version per experience, which goes up on any change to the experience or its items, results included. Receivers keep the highest.
finalis true once the experience is closed and fully counted. - What it carries: definitions, status and result totals, in the Dizplai API’s shapes. Hidden items are included as soon as they’re created. No fan personal data, no ballots, no leaderboards and no Fan Zone messages, and quiz answers only once the quiz is marked.
Live results
Results come on each item, as in the Dizplai API:total_votes, final, and votes and percentages on the options. The console and on-air overlays read GET /v1/{plural}/{id}/results every second while an item is live: every result, whatever results_visibility says, from the fan side’s own results document, cached for one second. It doesn’t read DizCore, which receives results a step after the fan side.
Our hubs get their results with each widget, from the Fan API.
One set of routes per widget type
Each widget type has its own set of routes, generated from its plugin folder inpackages/widget-types (ADR-12). Inside DX each item is still checked against its plugin’s schemas, which the Fan API and the staff contract use unchanged.
Every type has
GET and POST /v1/{plural}, to list and create, and GET and PATCH /v1/{plural}/{id}. Types with results have GET /v1/{plural}/{id}/results, and types that take votes have POST /v1/{plural}/{id}/votes, which only DizCore calls. The Fan Zone has three fan routes instead, which only DizCore calls: an upload for a voice note or photo, a fan’s message, and the fan’s own messages with any statement of reasons (Messaging). A second vote from the same fan is accepted and never counted (202, decided 22 October 2026); the Dizplai API’s 409 is to align.
The shared routes:
Fields every item shares
Every ID is DX’s. Every option in a poll, rating, picker or quiz, and every player in a line-up, has an
id that DX mints, opt_ and 26 characters, and never reuses, even after the option is removed. Leave id out only for a new option or player. When you change them, send the whole list with each existing id; an id the item doesn’t have answers 400 with the problem type unknown-option. Quiz questions work the same way, with qst_ IDs and unknown-question.
DX’s additions, not yet in the Dizplai API: subtitle; api_version, the widget type’s own API version, kept for the item’s life; results_visibility (live, after_vote, after_close or hidden: when the hub may show results to fans) and requires_sign_in on types that take part; and locks_at on predictors. Images are left out until the shared image model is agreed.
Conventions
As in the Dizplai API:- snake_case throughout.
- Errors are RFC 9457 problem details with
type,title,status,detailandrequest_id, and every response carriesx-request-id. Quote it when you report a problem. Field errors come inerrors, each with a JSON Pointer. Idempotency-Keyon creates, honoured for 24 hours. The same key with a different body answers409.- Paging with
afterandlimit(50 by default, 100 at most), returning{items, next}, withnextnull on the last page. - Filters:
status(a comma list; every status butdeletedby default),experience_id, andmetadata[key]=value. - No ETags. Edits aren’t guarded against a concurrent editor.
- Limits are per caller. Over the limit answers
429withRetry-After;503means DX can’t take writes right now.
Open items
To agree with DizCore’s team:- Forwarding the fan’s token unchanged in
x-dizplai-fan-token, and a signed service identity (SigV4) as well as the service key. - The private network: an interface VPC endpoint in DizCore’s VPC, shared with AWS RAM.
lockedandsettledin the status set; hidden items instead of drafts, and whether hidden items appear in platform reads.- DX’s IDs as opaque, rather than per-type prefixes.
- Rating (T1): the Dizplai API rates one subject on a scale; DX rates each player. Line-up (T2): DX’s formation catalogue and players. DX’s predictor and sponsor bodies, and
subtitle. - A second, different vote: DX answers
202and never counts it (decided 22 October 2026); the Dizplai API’s409is to align (T7). - The shared image model (DizCore’s assets API).
- Erasure requests for fans who sign in with a client’s own login, through DizCore.
- What may change on an item once it has taken votes, and whether edits need a concurrency guard without ETags.
- How Creator Network overlays reach the results routes now the API is private: through DizCore, or DizCore’s copy once a load test shows results on air within 2 seconds.
- Which route the Creator Network uses to register its creators’ hubs with DX (ADR-8).

