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.
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-jsBrowser 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();- Create one client.Initialize it with the site's publishable key and, when needed, a non-production API origin.
- Create one widget per submission.Pass the presentation, permitted sources, initial round information, and host callbacks.
- Open from a button or other user action.The widget owns its capture UI while the host keeps control of when the experience begins.
- Store the returned result.Use
externalReferenceto associate the response with the client's user, round, or workflow. - Destroy on unmount.Release camera tracks, previews, object URLs, event listeners, and SDK-owned DOM.
Configuration
Choose how much the host supplies.
fullscreen by default · modal · inlinecamera and file by defaultUse 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.
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.
Take one photo
Requests the rear camera only after an explicit user action. A denied, unavailable, or unsupported camera leaves file selection available.
Replace or remove
The golfer can inspect each local preview before choosing Process photo. Opening the widget alone never uploads a source.
Return explicit decisions
Each photo is normalized and processed authoritatively. Uncertain cells and retake conditions remain structured review outcomes rather than silent guesses.
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.
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.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.
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:inimg-srcfor local, revocable photo previews. - Pass the request nonce as
styleNoncewhen the host uses a nonce-basedstyle-srcpolicy. 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.