Developers/Scorecards JS

Browser capture SDK · v0.1 beta

Install and integrate scorecards-js.

Open a CADDIE.100-owned scorecard capture experience from a client website. The SDK collects round details, accepts photos from a camera or file picker, submits them through a browser-safe boundary, and returns versioned scoring results to the host application.

Framework-agnosticPublishable keysCamera and file inputVersioned callbacks

Installation

Add one client-owned capture entry point.

Install the package in the website that will open Smart Scorecards. The package includes TypeScript contracts, the browser client, and the complete full-screen, modal, and inline widget UI.

npm install @caddie100/scorecards-js
Private beta availabilityScorecards JS is currently distributed to approved clients while browser and physical-device acceptance continues. CADDIE.100 will provide the approved package source or registry version for each integration.

Browser credentials

Use a publishable key for an allowed origin.

Scorecards JS accepts only a c100_pk_live_… or c100_pk_test_… key carrying the separately licensed scorecards:capture scope. CADDIE.100 verifies the requesting browser origin on every configuration and capture request.

Never place a private c100_live_… or c100_test_… credential in browser code, browser storage, a public repository, an analytics event, or a client-visible log. Publishable keys cannot call the private normalization or processing endpoints.

import { createScorecardsClient } from "@caddie100/scorecards-js";

const scorecards = createScorecardsClient({
  apiKey: "c100_pk_live_…",
  // Defaults to https://www.caddie100.com.
});

For local development, set baseUrl: "http://localhost:3100" and serve the client from its registered local origin. baseUrl must be an HTTP or HTTPS origin without a path, query, fragment, or embedded credentials.

Quick start

Create the widget once. Open it from a user action.

createCaptureWidget validates configuration without touching the DOM. Calling open() attaches the SDK-owned UI. It does not request camera permission or open a file picker until the user chooses that source inside the widget.

const widget = scorecards.createCaptureWidget({
  presentation: "fullscreen",
  sources: ["camera", "file"],
  maxCaptures: 18,
  externalReference: "client-round-123",
  initialRound: {
    courseId: "COURSE_ID",
    teeBox: "Blue",
    holesPlayed: 18,
    startingHole: 1,
    playedOn: "2026-09-18",
  },
  onComplete(result) {
    void widget.close();
    hostScorecards.handleResult(result);
  },
});

const openButton = document.querySelector<HTMLButtonElement>("#open-scorecards");
if (!openButton) throw new Error("The Scorecards launch button is missing.");

const openWidget = () => {
  void widget.open();
};
openButton.addEventListener("click", openWidget);

// When the host experience unmounts:
openButton.removeEventListener("click", openWidget);
await widget.destroy();
  1. Create one client.Initialize it with the site's publishable key and, when needed, a non-production API origin.
  2. Create one widget per submission.Pass the presentation, permitted sources, initial round information, and host callbacks.
  3. Open from a button or other user action.The widget owns its capture UI while the host keeps control of when the experience begins.
  4. Store the returned result.Use externalReference to associate the response with the client's user, round, or workflow.
  5. Destroy on unmount.Release camera tracks, previews, object URLs, event listeners, and SDK-owned DOM.

Configuration

Choose how much the host supplies.

presentationfullscreen by default · modal · inline
sourcescamera and file by default
maxCaptures1 by default · 1–24 supported
initialRoundCourse, tee, holes, starting hole, date, and optional tee time
lockedRoundFieldsPrevent edits to values already owned by the host
externalReferenceOpaque client identifier returned unchanged

Use initialRound when the host already knows part or all of the round. Add those field names to lockedRoundFields when the widget should display—but not change—the supplied values. Every required locked field must have an initial value.

Published tee names appear as suggestions after course selection. The tee field stays editable because a physical scorecard can use a local label that has not reached the public course catalog.

const container = document.querySelector<HTMLElement>("#scorecard-capture");
if (!container) throw new Error("The Scorecards container is missing.");

const widget = scorecards.createCaptureWidget({
  presentation: "inline",
  container,
  sources: ["file", "camera"],
});

await widget.open();

Capture flow

Keep the source local until the golfer approves it.

File or camera roll

Choose photos

Accepts JPEG, PNG, and WebP sources up to 25 MB, validates image headers and dimensions before decoding, prevents duplicates, and shows revocable local previews before processing.

Live camera

Take one photo

Requests the rear camera only after an explicit user action. A denied, unavailable, or unsupported camera leaves file selection available.

Review

Replace or remove

The golfer can inspect each local preview before choosing Process photo. Opening the widget alone never uploads a source.

Processing

Return explicit decisions

Each photo is normalized and processed authoritatively. Uncertain cells and retake conditions remain structured review outcomes rather than silent guesses.

Embedded metadata is removed before uploadWhen processing begins, the SDK applies the photo's encoded orientation, draws only its pixels into a browser canvas, and uploads a newly encoded image under a generic filename. It automatically recompresses oversized phone photos under the 4 MB upload boundary and reduces dimensions only when compression alone is insufficient. The original bytes, filename, and embedded EXIF, GPS coordinates, comments, thumbnails, and similar metadata are not submitted. This protection also applies when an integration calls processCapture() directly.

Results and events

Connect the SDK to the host application.

onEvent receives versioned lifecycle events, onComplete receives the final result, and onError receives a safe client error. subscribe immediately returns the current immutable state and then reports later changes. The widget's result promise resolves once after the submission completes.

const widget = scorecards.createCaptureWidget({
  onEvent(event) {
    if (event.type === "upload-progress") {
      updateProgress(event.captureId, event.progress);
    }
  },
  onComplete(result) {
    void widget.close();
    hostScorecards.handleResult(result);
  },
  onError(error) {
    reportSafeClientError(error);
  },
});

const unsubscribe = widget.subscribe((state) => {
  updateHostUi(state.status);
});

const finalResult = await widget.result;
unsubscribe();

COMPLETE, PARTIAL, and NEEDS_REVIEW are all terminal handoffs when the API returns a valid result. The SDK does not stop on NEEDS_REVIEW to display or edit scores. Close the capture widget from onComplete, then let the host application store the result, choose its messaging and navigation, or open its own review experience.

The final caddie100-scorecard-capture-result@1 includes the round, capture statuses, issues, warnings, and a browser-safe caddie100-scorecard-capture-processing@1 projection. That projection includes printed design identity, parsed cell decisions, page warnings, and aggregate counts.

Capture session, not a durable jobThe current sessionId authorizes and correlates a short-lived synchronous submission. It is returned with the result, but there is no polling endpoint or refresh-resumable background job in this release.
Deliberately excludedBrowser callbacks do not receive normalization transforms, semantic region maps, temporary artifact URLs, provider provenance, usage data, or processing cost data.
import { ScorecardsApiError } from "@caddie100/scorecards-js";

const widget = scorecards.createCaptureWidget({
  onError(error) {
    if (error instanceof ScorecardsApiError && error.retryAfterSeconds) {
      showRetryGuidance(error.retryAfterSeconds);
      return;
    }
    showCaptureError(error.message);
  },
});

Lifecycle

Close, reopen, and clean up predictably.

open()Attach the UI and retain widget-owned round state
close()Detach the UI without discarding local state
close({ discard: true })Detach and clear round details and captures
retryCapture(id)Retry a local photo or eligible transport failure safely
destroy()Abort work and release every SDK-owned browser resource
resultResolve once with the final versioned result

A completed widget is intentionally single-use. Calling close() from onComplete hands control back to the host without changing the result. Create a new widget for the next submission. Call destroy() when a component, route, or client-owned experience unmounts.

Browser and host requirements

Preserve camera, preview, and policy boundaries.

  • Serve the host over HTTPS before enabling live camera capture. Localhost is accepted for development.
  • Allow the configured CADDIE.100 origin in connect-src.
  • Allow blob: in img-src for local, revocable photo previews.
  • Pass the request nonce as styleNonce when the host uses a nonce-based style-src policy. The SDK applies it to its Shadow DOM stylesheet.
  • When embedded, grant camera access through Permissions Policy and the iframe's allow="camera" attribute.

Test the exact production Content Security Policy. Do not weaken a global policy merely to accommodate the widget.

Current beta boundary

Manual capture first. Automatic capture follows later.

The current release supports normal file uploads, mobile camera-roll selection, and one manually triggered live-camera still. Multi-photo sessions are coordinated in page memory and each photo is processed synchronously.

Automatic shutter behavior, live marker coaching, page-number progress badges, video/page streaming, HEIC conversion, durable drafts, background processing, and refresh recovery are not part of the current implementation. Physical-device acceptance on supported iOS and Android browsers remains a release gate.

For the private server normalization and processing contracts—including request limits, response versions, capture guidance, retention, and stable problem codes—use the Smart Scorecards HTTP API documentation.