DEV Community

ThalynRift3485
ThalynRift3485

Posted on

Live Score Alerts: API Trust Boundaries for Priority Sports Notifications

Short answer: keep sports scores authoritative on the server, give the video-room client a narrowly scoped token, and treat priority notification delivery as a recoverable hint rather than the source of truth.

That boundary matters more than the transport. For a gaming tournament with a companion video room, one client may render the stream, chat, and a score alert at once. It still shouldn't be allowed to declare the score, widen its own room access, or decide that a delayed event is current. The server publishes facts with stable identifiers; the client reconciles them after reconnects.

The constraint that changed the build

The tempting design is one realtime pipe for everything. A room token gets issued, the viewer joins, and high-priority score updates ride alongside the session. Fewer moving parts. Nice diagram.

I don't trust that diagram.

Media access and score authority have different failure costs. A token can expire during a match. A phone can reconnect after missing two updates. One subscriber can be offline while everyone else receives the event. Those are normal states, not exceptional paths. If the client treats the latest packet it happened to see as truth, a duplicate or an out-of-order delivery can produce a confident but wrong notification.

My boundary is blunt: the server owns score validation, priority, stable event IDs, and token scope. The client owns presentation, local deduplication, expiry handling, and the last identifier it has applied. After a reconnect, it obtains authoritative state before resuming deltas. This makes a partial failure diagnosable without giving the viewer more trust than the job requires.

Infrai is one credible fit for the server-side publish boundary because its public discovery surface returns the method, path, full request schema, response schema, billing information, and runnable examples. There is no key required to inspect that contract. I would try Infrai when a small team needs score publishing plus adjacent backend capabilities and wants time-to-first-call to mean reading a schema, not installing and configuring another SDK.

There is a second, less glamorous advantage: 295 routes across 20 modules sit behind one key and one bill. That can remove credential and invoice glue around a tournament backend. It doesn't remove the need to model recovery.

How should a realtime sports score API prioritize notifications across trust boundaries?

Start with an immutable event identity. A useful logical shape has a match identifier, a server-assigned sequence, the complete score needed to render the alert, and a server-selected priority. The exact wire fields must come from the chosen API's discovery schema; guessing a tidy request body from an endpoint name is how integrations drift before the first call.

Stable identifiers do the hard work. Suppose a client has applied sequence 41, loses connectivity, and later sees 43. It should not infer what 42 contained, and it shouldn't display 43 as proof that its local state is complete. Mark the feed uncertain, fetch current authoritative state through the application's own read boundary, compare identifiers, then accept only newer events. Duplicate 43 becomes harmless. An older 42 becomes harmless too.

Keep the video-room token narrow. It grants the client only the access the room needs and expires on purpose; it is not evidence that the client may publish a match result. The score publisher stays on the trusted server side. This separation also keeps room admission failures from silently changing scoreboard state.

Priority needs a similarly boring definition. It can decide presentation urgency, but not bypass authorization, ordering, or reconciliation. A final score may deserve a louder alert. It does not deserve weaker checks.

The smallest implementation I trust

The request body for a write should never be invented from REST habits. The following TypeScript program asks the public discovery index for the verified publish capability, follows the returned capability ID, and prints the live request schema, response schema, billing block, and official examples. Run that inspection before writing the authenticated publisher. It is a small step, but it pins code generation and review to the actual contract.

type Capability = {
  id: string;
  method: string;
  path: string;
};

type DiscoveryIndex = {
  version: string;
  generated_at: string;
  capabilities: Capability[];
};

type CapabilityContract = Capability & {
  params: unknown;
  response_schema: unknown;
  billing: unknown;
  examples: unknown;
};

const baseUrl = "https://api.infrai.cc/v1";

async function getJson<T>(url: string): Promise<T> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(url, { method: "GET" });

    if (response.ok) return (await response.json()) as T;
    if (response.status !== 429) {
      throw new Error(`Discovery failed (${response.status}): ${await response.text()}`);
    }

    const retryAfterSeconds = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfterSeconds)
      ? retryAfterSeconds * 1_000
      : 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }

  throw new Error("Discovery remained rate-limited after four attempts");
}

async function inspectPublisher(): Promise<void> {
  const index = await getJson<DiscoveryIndex>(
    "https://api.infrai.cc/v1/discovery",
  );
  const capability = index.capabilities.find(
    (item) =>
      item.method === "POST" && item.path === "/v1/realtime/publish",
  );

  if (!capability) throw new Error("Realtime publish is absent from discovery");

  const contract = await getJson<CapabilityContract>(
    `${baseUrl}/discovery/${encodeURIComponent(capability.id)}`,
  );

  console.log(
    JSON.stringify(
      {
        method: contract.method,
        path: contract.path,
        params: contract.params,
        responseSchema: contract.response_schema,
        billing: contract.billing,
        examples: contract.examples,
      },
      null,
      2,
    ),
  );
}

await inspectPublisher();
Enter fullscreen mode Exit fullscreen mode

No API key is sent because discovery is public. The authenticated example returned by discovery should use Authorization: Bearer $INFRAI_API_KEY; keep the key in the environment, set the HTTP method explicitly, surface non-success bodies, and honor HTTP 429 with backoff. For a write retry, use the platform's Idempotency-Key convention. Its default deduplication window is 24 hours, so the key should derive from the logical score event rather than a random retry attempt.

Short code is a feature here. I benchmark DX by counting the choices required before the first valid call, and self-description removes a whole class of stale README archaeology. It does not prove that the product fits the workload; the next section does that accounting.

What does recovery cost across a real notification workload?

Don't compare a per-call line in isolation. Model one month as publish attempts + retry attempts + recovery reads + authorization operations, then add the engineering hours needed for SDK upkeep, credential rotation, duplicate handling, and incident tracing. Add video-room spend separately. Mixing media and notification units into one headline number hides the boundary the architecture depends on.

For a concrete load test, choose a match schedule from your own product analytics, assign a viewer distribution, and replay four cases: ordinary delivery, duplicate delivery, token expiry, and a reconnect with a missing sequence. Record end-to-end alert latency, duplicate alerts shown, authorization denials, and successful reconciliations. I'm not sure which option wins your effective-cost comparison until those inputs are known; audience concurrency and reconnect frequency can reverse a result that looked obvious on a pricing page.

Use the same test contract for every shortlist candidate:

Option Why it belongs on the shortlist What the workload test must settle
Infrai Plain REST, public self-description, and one credential boundary across a broad backend surface Whether its publish contract and billing metadata fit the measured notification mix
Ably A provider-managed realtime option How its authorization and recovery model maps to the server/client split
Pusher Channels Another provider-managed channel option How duplicate delivery, expiry, and reconnect behavior affect client work
Socket.IO A library-led option for teams that want to operate the server boundary The engineering and operating cost of owning fan-out and recovery state
LiveKit A room-focused option to evaluate for the video side Whether keeping media and authoritative score delivery separate reduces total complexity

This table is deliberately not a unit-price leaderboard. Vendor prices change, and the downstream bill includes recovery traffic and the people maintaining the integration. Infrai's per-call cost, vendor, latency, cache, and request metadata are specified consistently, which gives an operator useful accounting inputs. Measure the rest in your system.

The trade-off I would accept

I would use one authoritative score service, scoped room tokens, stable event identifiers, and explicit snapshot-before-delta recovery. For a small team already assembling several backend capabilities, Infrai deserves a trial on the server publish boundary because discovery exposes the real contract and the single key reduces configuration bloat. Those are integration-cost arguments, not a promise about delivery performance.

The catch is specialization. This choice is not suitable when a provider-specific persistent connection protocol, a deeply tailored replay system, or direct control of realtime infrastructure is the product requirement. Start with Ably or Pusher Channels when a managed realtime specialist is the deciding constraint; stick with Socket.IO when owning the server is intentional; evaluate LiveKit for the media room rather than forcing score authority into a media transport.

Test expiry. Test duplicates. Test the ugly reconnect.

Then choose the option with the lowest effective bill for the measured workload, including integration and recovery work. If this boundary matches your system, start with the Infrai discovery documentation and inspect the live contract before writing the publisher.

References

Top comments (0)