2929 events are exempt: screen.*, runtime.*, and application.event. Heartbeats
3030 and the retry preamble never charge. playlist.*, media.*, kv.*,
3131 application.published, operation.*, account.*, feedback.*, stream.cursor,
32- and stream.resync_required are billed. SSE debit idempotency is
32+ and stream.resync_required are billed. GET /dashboard/v1/events/stream
33+ bills arriving events under this same per-event meter and the same
34+ exemption set; like every SSE response it omits the ScreenRig-Credits-*
35+ headers. A dashboard session reads the same whole-credit balance through
36+ GET /dashboard/v1/balance, which never debits and remains available at zero.
37+ SSE debit idempotency is
3338 sse-event:<account_id>:<sequence>; reconnect replay is free. An unpaid
3439 billed event is not written and the stream closes.
3540jsonSchemaDialect : https://json-schema.org/draft/2020-12/schema
@@ -1479,6 +1484,21 @@ paths:
14791484 " 200 " : { description: Account summary counters., headers: { Cache-Control: { schema: { type: string, enum: ["private, no-store"] } } }, content: { application/json: { schema: { $ref: "#/components/schemas/DashboardSummary" } } } }
14801485 " 401 " : { $ref: "#/components/responses/Problem" }
14811486 default : { $ref: "#/components/responses/Problem" }
1487+ /dashboard/v1/balance :
1488+ servers : [{ url: https://dashboard.screenrig.ai, description: Backend-owned account dashboard origin }]
1489+ get :
1490+ operationId : getDashboardBalance
1491+ description : >-
1492+ Returns the session account's public whole-credit balance. This
1493+ cookie-authenticated read never debits, so it remains available at zero
1494+ after an unpaid billable dashboard-stream event closes the SSE response.
1495+ It is the dashboard recovery surface; the cookie-only dashboard never
1496+ presents an account bearer to GET /api/v1/account.
1497+ security : [{ dashboardSession: [] }]
1498+ responses :
1499+ " 200 " : { description: Public whole-credit balance., headers: { Cache-Control: { schema: { type: string, enum: ["private, no-store"] } } }, content: { application/json: { schema: { $ref: "#/components/schemas/DashboardBalance" } } } }
1500+ " 401 " : { $ref: "#/components/responses/Problem" }
1501+ default : { $ref: "#/components/responses/Problem" }
14821502 /dashboard/v1/screens :
14831503 servers : [{ url: https://dashboard.screenrig.ai, description: Backend-owned account dashboard origin }]
14841504 get :
@@ -1810,7 +1830,7 @@ paths:
18101830 security : [{ dashboardSession: [] }]
18111831 parameters : [{ $ref: "#/components/parameters/AfterCursor" }, { $ref: "#/components/parameters/LastEventID" }]
18121832 responses :
1813- " 200 " : { description: "Replayable SSE stream. No ScreenRig-Credits-* headers.", headers: { Cache-Control: { schema: { type: string, enum: ["no-cache, no-store, no-transform"] } }, X-Accel-Buffering: { schema: { type: string, enum: [no] } } }, content: { text/event-stream: { schema: { type: string } } } }
1833+ " 200 " : { description: "Replayable SSE stream. Arriving billable events debit 1 whole credit each under the same meter and exemption set as GET /api/v1/events/stream; heartbeats and the retry preamble never charge. An unpaid billable event is not written and closes the stream; GET /dashboard/v1/balance remains available at zero. No ScreenRig-Credits-* headers.", headers: { Cache-Control: { schema: { type: string, enum: ["no-cache, no-store, no-transform"] } }, X-Accel-Buffering: { schema: { type: string, enum: [no] } } }, content: { text/event-stream: { schema: { type: string } } } }
18141834 " 401 " : { $ref: "#/components/responses/Problem" }
18151835 default : { $ref: "#/components/responses/Problem" }
18161836 /dashboard/v1/playback :
@@ -2613,7 +2633,30 @@ components:
26132633 required : [version, url, bytes, sha256, entrypoint, contentType]
26142634 properties :
26152635 version : { type: string, enum: ["screenrig.webapp-package/v1"] }
2616- url : { type: string, format: uri, description: "Same-origin protected path under /content/v1/manifests/{manifest_revision}/releases/{release_id}/package; production uses https://play.screenrig.ai." }
2636+ url :
2637+ type : string
2638+ format : uri-reference
2639+ description : &manifest-bound-route >-
2640+ Manifest-bound route. The runtime origin is the origin this session
2641+ fetched /runtime/v1/manifest from; that origin is deployment
2642+ configuration, so this binding is normative prose rather than a
2643+ machine-readable constraint. A consumer resolves this value — an
2644+ absolute URL or a root-relative path — against the runtime origin
2645+ and requires all of the following of the resolved URL : its scheme,
2646+ host, and effective port equal the runtime origin's, with
2647+ default-port normalization (443 for https, 80 for http); it carries
2648+ no query component, no fragment, and no user info; and its path is
2649+ byte-for-byte exactly the route emitted for this field —
2650+ /content/v1/manifests/{manifest_revision}/media/{media_id} for an
2651+ image or video item, with the containing manifest revision and the
2652+ item's mediaId, or
2653+ /content/v1/manifests/{manifest_revision}/releases/{release_id}/package
2654+ for an application package, with the containing manifest revision
2655+ and the content's releaseId. A value that fails this binding makes
2656+ the whole manifest invalid : the consumer rejects the manifest at
2657+ parse time, before any credentialed fetch, and never requests the
2658+ route. The sibling sha256 pins bytes after transfer; hash-pinning
2659+ is never destination authorization and never relaxes this binding.
26172660 bytes : { type: integer, minimum: 1, maximum: 272629760 }
26182661 sha256 : { type: string, pattern: "^[a-f0-9]{64}$" }
26192662 entrypoint : { type: string, enum: [index.html] }
@@ -2635,7 +2678,7 @@ components:
26352678 required : [mediaId, src, intrinsicSize, sha256, bytes, contentType]
26362679 properties :
26372680 mediaId : { type: string }
2638- src : { type: string }
2681+ src : { type: string, format: uri-reference, description: *manifest-bound-route }
26392682 intrinsicSize : { $ref: "#/components/schemas/RuntimeIntrinsicSize" }
26402683 sha256 : { type: string, pattern: "^[a-f0-9]{64}$", description: Immutable content digest for integrity and optional native caching; not an authorization capability. }
26412684 bytes : { type: integer, minimum: 1, maximum: 1073741824 }
@@ -2657,7 +2700,7 @@ components:
26572700 required : [mediaId, src, intrinsicSize, sha256, bytes, contentType]
26582701 properties :
26592702 mediaId : { type: string }
2660- src : { type: string }
2703+ src : { type: string, format: uri-reference, description: *manifest-bound-route }
26612704 intrinsicSize : { $ref: "#/components/schemas/RuntimeIntrinsicSize" }
26622705 sha256 : { type: string, pattern: "^[a-f0-9]{64}$", description: Immutable content digest for integrity and optional native caching; not an authorization capability. }
26632706 bytes : { type: integer, minimum: 1, maximum: 1073741824 }
@@ -2821,6 +2864,14 @@ components:
28212864 screens : { $ref: "#/components/schemas/DashboardScreenSummary" }
28222865 media : { $ref: "#/components/schemas/DashboardMediaSummary" }
28232866 applications : { $ref: "#/components/schemas/DashboardApplicationSummary" }
2867+ DashboardBalance :
2868+ type : object
2869+ additionalProperties : false
2870+ required : [credit_remaining, credit_included, credit_reset]
2871+ properties :
2872+ credit_remaining : { type: integer, minimum: 0, description: "Remaining prepaid credits as a whole integer. Never negative." }
2873+ credit_included : { type: integer, minimum: 0, description: "Whole credits in the account's recurring included grant." }
2874+ credit_reset : { type: string, format: date-time, description: "Next instant at which the recurring included grant resets." }
28242875 DashboardScreenSummary :
28252876 type : object
28262877 additionalProperties : false
0 commit comments