Normalization & handwriting · Client API
Smart
Scorecards.
Normalize photographed CADDIE.100 scorecards, correct camera perspective, and extract supported handwriting without converting uncertain evidence into guessed values.
Choose an integration surface
One processor. Three ways to use it.
POST /normalizePrivate server request for perspective correction, design identity, canonical images, and registered semantic geometry.POST /processPrivate server request for normalization, handwriting extraction, golf interpretation, and explicit review decisions.scorecards:capture scope.scorecards:process credentials. Browser capture uses publishable keys and never exposes the private endpoints.The HTTP endpoints are synchronous and intended for trusted backends. Scorecards JS owns the browser capture experience and returns a deliberately smaller, browser-safe result. Both paths use the same authoritative processing contracts.
Printed identity and registered geometry
Let the page identify its own design.
Version 4 groups each complete four-marker page independently, normalizes that page from the full-resolution photograph, and only then reads its required S identity from the standardized crop. The validated identity selects immutable design geometry for the final canonical image. Camera rotation, crop, skew, and perspective are corrected per page.
The checksummed label beside the lower-left marker uses self-routing chunks. Strip S from a design chunk such as SM9H1, SM9H1GG, SM9H10GG, SM18F, or SM18B to obtain its required design code. The GG and M18 identities select exact front- or back-nine page geometry; clients do not infer crop percentages from the code. The open-ended M18 pages do not encode a course. Other designs may include an optional C course chunk such as CNJ282; E identifies the checksum chunk. Because each chunk declares its type, their order does not affect decoding. Normalization returns any course code but does not yet apply course data.
The generated design catalog contains SM18B SM18F SM9H1 SM9H10 SM9H10GG SM9H1GG . The production API accepts only identities in rollout scorecard-production-rollout-phase-9u@1: SM9H1 SM9H10GG SM9H1GG SM18F SM18B . Controlled previews remain available to local verification but are not production-routable. Catalog revision scorecard-design-catalog@3.
POST /api/v1/scorecards/normalize
Upload a file or provide a public URL.
Use a private licensed server credential with scorecards:process. A CADDIE.100 administrator enables the organization entitlement; an organization owner or admin then assigns the scope to a private key in the client dashboard. The same scope authorizes both normalization and processing. Publishable browser keys cannot receive it. See the credential security guide for the two-level permission model.
Send the private key through x-api-key or Authorization: Bearer from a trusted backend. Public Scorecards endpoints do not accept employee-session fallback. The internal playground is an employee diagnostic surface, not a client integration URL; it exercises the same production application handlers without changing which designs clients can use.
Only the image is required. courseId, teeBox, and identifier are optional metadata echoed back when supplied; normalization and handwriting processing do not depend on them.
Multipart accepts one JPEG, PNG, or WebP image up to 4,000,000 bytes inside a complete request of at most 4,250,000 bytes. If the file is larger, host it at a public HTTPS URL and use JSON ingestion.
URL ingestion permits up to 15 MiB and 40,000,000 decoded pixels. The service revalidates every redirect and address, allows at most three redirects, and applies one 15-second deadline to the complete download.
curl -X POST "https://caddie100.com/api/v1/scorecards/normalize" \
-H "x-api-key: c100_live_YOUR_PRIVATE_KEY" \
-F "identifier=your-round-42" \
-F "image=@scorecard.jpg;type=image/jpeg"curl -X POST "https://caddie100.com/api/v1/scorecards/normalize" \
-H "x-api-key: c100_live_YOUR_PRIVATE_KEY" \
-H "content-type: application/json" \
-d '{
"identifier": "your-round-42",
"image": { "url": "https://cdn.example.com/scorecard.jpg" }
}'Omit caddie-scorecard-normalization-version for production version 4, which discovers and normalizes up to four independent pages from one photograph. Send 2 only for the frozen square-marker M9 response or 3 for the prior one-capture topology-neutral response. Version 4 returns every complete page even when other visible markers form only an incomplete page. Identity reading uses a full-resolution-derived page crop; a crop below the qualified source-detail floor is reported as a page issue and is never sent to the identity provider.
Separate browser project
Scorecards JS now has dedicated documentation.
The HTTP normalization and processing contracts remain on this page. Installation, browser-key setup, widget configuration, capture sources, callbacks, results, lifecycle, host security policy, and current SDK limitations now live in a focused Scorecards JS guide.
Client launch checklist
Prove the permission boundary before live traffic.
- Confirm the client organization is active and has an owner or administrator.
- Confirm the organization policy includes
scorecards:process. - Create a narrowly scoped
c100_test_private key and store it in the integration backend's secret manager. - Verify no-key, publishable-key, and browser-origin requests fail; then run one authorized normalization.
- Create a separate
c100_live_key only after test verification and store it independently. - Assign owners for usage review, rate-limit changes, rotation, and emergency revocation.
Plaintext credentials are displayed only once. Never place a private key in a printable, browser bundle, browser storage, repository, support ticket, analytics event, or log.
Photograph the registered capture surface
Keep the whole page or spread—and every marker—in frame.
Use the topology named by the printed design and keep the phone approximately parallel to the paper. Ordinary rotation is corrected after upload, but steep perspective, clipped boundaries, deep fold shadows, and glare reduce usable marker evidence. The table below lists catalog-derived marker counts; it does not make a preview topology production-eligible.
| Topology | Markers | Framing | Fold treatment | Retake when |
|---|---|---|---|---|
single-page | 4 required | Photograph the complete page boundary with all corner whitespace visible. Keep every marker and the adjacent S identity label unobstructed. | No fold is expected inside the capture surface; flatten curled paper without covering a marker. | Retake when any edge or marker is clipped, the S label is unreadable, or glare/shadow crosses a marker or writable table. |
horizontal-spread | 6 required | Photograph the complete left/right spread in one image. Center the camera over the spread and include all four outer markers plus both fold-safe markers. | Keep the vertical fold visible but gently opened. Do not press, crop, or shadow the fold-safe markers beside the crease. | Retake when either page edge is missing, a fold marker is hidden, the crease creates severe perspective, or glare/shadow obscures either page. |
vertical-spread | 6 required | Photograph the complete top/bottom spread in one image. Center the camera between both halves and include all four outer markers plus both fold-safe markers. | Keep the horizontal fold visible but gently opened. Preserve clearance around the markers above and below the crease. | Retake when either half is cropped, a fold marker is hidden, rotation prevents the whole spread fitting cleanly, or glare/shadow obscures a marker. |
Before submitting, confirm that marker blacks are not washed out by glare, marker whites are not lost in shadow, the unique index marker and its adjacent S label are legible, and no hand, clip, scorecard cover, or page edge overlaps a required marker. If in doubt, retake from slightly farther away. With response version 4, a photo containing one complete page and additional incomplete marker evidence returns the complete page with PARTIAL status and structured issues instead of discarding the usable result. Two complete booklet pages in one photograph are normalized separately, so fold or page warp does not force one transform across both pages.
POST /api/v1/scorecards/process · Authenticated client API
Extract values without hiding uncertainty.
The processing endpoint accepts the same file and public-HTTPS inputs as normalization. It additionally requires an Idempotency-Key containing 8–128 visible ASCII characters. Reuse the same key with the exact same metadata, image, and processing version to recover the stored result without another model call or usage unit; using that key with different input returns 409.
Processing version 3 is the default client contract. The service discovers up to four complete pages and keeps successful pages when another page cannot be processed. One page can succeed while another is returned as NORMALIZED_ONLY with a structured issue.
Public processing version 3 supports F9, B9 panels through production-registered processors: M9H1/front-nine-page, M9H1GG/front-nine-page, M9H10GG/back-nine-page, M18F/front-nine-page, and M18B/back-nine-page. It returns extracted fields, review reasons, supported supplemental form data, and scoring-ready canonical records. Crop geometry, marker evidence, model/provider details, cost and token usage, internal revisions, and debug artifacts are omitted from client responses. The M18 front page adds permissive Round Details transcription; the back page adds Round Summary and Penalty transcription. Blank or unreadable fields remain null. Other discovered designs remain explicit partial results until a production processor is registered. Pages are not automatically merged into one round because two sheets in one photo are ambiguous without a shared printed instance token.
The service handles image preparation, page normalization, extraction retries, and bounded provider work internally. Clients receive only the resulting scorecard data and review guidance. One completed logical photo request records one scorecards:process unit.
curl -X POST "https://caddie100.com/api/v1/scorecards/process" \
-H "x-api-key: c100_live_YOUR_PRIVATE_KEY" \
-H "Idempotency-Key: your-round-42-attempt-1" \
-H "caddie-scorecard-processing-version: 3" \
-F "identifier=your-round-42" \
-F "image=@scorecard.jpg;type=image/jpeg"curl -X POST "https://caddie100.com/api/v1/scorecards/process" \
-H "x-api-key: c100_live_YOUR_PRIVATE_KEY" \
-H "Idempotency-Key: your-round-42-attempt-1" \
-H "caddie-scorecard-processing-version: 3" \
-H "content-type: application/json" \
-d '{
"identifier": "your-round-42",
"image": { "url": "https://cdn.example.com/scorecard.jpg" }
}'Every processed page returns field decisions with literal, normalized value, field-valid bestGuess, reason codes, and either ACCEPTED or NEEDS_REVIEW. A review field remains editable but is not empty when the service has a meaningful guess. A photo whose pages all process but contain review fields returns COMPLETE_WITH_REVIEW; PARTIAL is reserved for missing or normalized-only page results. The response also includes canonical round records that can be corrected and submitted to Scoring. Internal crops, transforms, marker evidence, provider attempts, prompts, model identifiers, usage/cost data, and debug artifacts are never returned to client API keys.
See Smart Scorecards scoring logic for the complete handwritten numeric, club, FIR, GIR, direction, and review-decision vocabulary.
{
"schema": "caddie100-scorecard-processing@3",
"requestId": "scn_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"courseId": "example-course",
"teeBox": "Blue",
"identifier": "your-round-42",
"status": "COMPLETE_WITH_REVIEW",
"pages": [
{
"pageInstanceId": "page-1",
"status": "NEEDS_REVIEW",
"design": {
"designId": "M9H1GG",
"panel": "F9"
},
"processing": {
"status": "NEEDS_REVIEW",
"fields": [
{
"row": "1",
"field": "score",
"decision": "ACCEPTED",
"value": 4,
"literal": "4",
"bestGuess": 4,
"reasonCodes": []
}
],
"summary": {
"totalCells": 70,
"acceptedCells": 65,
"reviewCells": 5,
"reviewRate": 0.0714
}
},
"warnings": []
}
],
"roundDrafts": [
{
"draftId": "draft_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"selectionPolicy": "best-available",
"pageInstanceIds": [
"page-1"
],
"record": {
"schema": "caddie100-scorecard-round-record@1",
"holes": [
{
"holeNumber": 1,
"score": 4
}
]
}
}
],
"summary": {
"discoveredPages": 1,
"processedPages": 1,
"normalizedOnlyPages": 0,
"acceptedCells": 65,
"reviewCells": 5
},
"issues": [],
"warnings": []
}The initial six-photo evaluation auto-accepted 85.95% of cells and flagged 14.05% for review. That 14.05% is a review rate, not a 14.05% success rate. Review cells are withheld after retry and still require the client or a later workflow to resolve them. The 99.45% measurement is precision among auto-accepted cells, not the probability that an entire card is perfect. Card-level exact-match performance is not yet qualified. The fixtures represent only two physical cards and one writer, so clients should retain review handling while broader physical-card and writer sampling continues as post-release quality assurance.
Synchronous v2, v3, and v4 contracts
Receive verified temporary artifacts.
Version 4 is the default production strip-marker contract. It returns a pages array, separate artifacts for each normalized page, and unmatched-marker or unreadable-identity issues without failing complete pages. Version 2 remains the frozen square-marker M9 compatibility response, while version 3 retains the prior one-capture topology-neutral evidence contract. Every successful response returns the selected version in caddie-scorecard-normalization-version. source.persisted is always false.
url- Temporary signed HTTPS bearer URL. Treat it as sensitive.
contentType- image/png for the normalized image or image/jpeg for the marker diagnostic.
sizeBytes- Verified object size in bytes.
sha256- SHA-256 digest of the returned artifact bytes.
width / height- Pixel dimensions for this specific artifact.
expiresAt- Exact time at which signed download access ends.
retentionUntil- Earliest lifecycle-deletion eligibility, not a guaranteed deletion time.
{
"schema": "caddie100-scorecard-normalization@2",
"requestId": "scn_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"courseId": "example-course",
"teeBox": "Blue",
"identifier": "example-round-42",
"source": {
"method": "multipart",
"persisted": false
},
"template": {
"designId": "M9H1",
"slug": "greens-one-sheet",
"version": 4,
"panel": "F9",
"layoutRevision": "greens-one-sheet-scorecard@4",
"fiducialFamily": "caddie-2x9-h10-page-family@1",
"source": "fiducials"
},
"printedMetadata": {
"text": "SM9H1-CNJ282-EF88",
"courseToken": "NJ282"
},
"normalization": {
"rotationDegrees": 0,
"sourceWidth": 3072,
"sourceHeight": 4080,
"sourceCorners": {
"topLeft": {
"x": 612.25,
"y": 498.5
},
"topRight": {
"x": 2510.75,
"y": 522.5
},
"bottomRight": {
"x": 2491.5,
"y": 3608.25
},
"bottomLeft": {
"x": 590.5,
"y": 3584.75
}
},
"outputWidth": 1275,
"outputHeight": 2063
},
"regions": {
"coordinateSpace": {
"origin": "top-left",
"unit": "normalized-0-to-1",
"canonicalPixels": {
"width": 1275,
"height": 2063
}
},
"panel": "F9",
"markerText": {
"x1": 0.085882,
"y1": 0.96,
"x2": 0.552941,
"y2": 0.992
},
"holes": [
{
"holeNumber": 1,
"row": {
"x1": 0.089412,
"y1": 0.14742,
"x2": 0.910588,
"y2": 0.204025
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.14742,
"x2": 0.171529,
"y2": 0.204025
},
"score": {
"x1": 0.171529,
"y1": 0.14742,
"x2": 0.263912,
"y2": 0.204025
},
"putts": {
"x1": 0.263912,
"y1": 0.14742,
"x2": 0.356294,
"y2": 0.204025
},
"teeFir": {
"x1": 0.356294,
"y1": 0.14742,
"x2": 0.448676,
"y2": 0.204025
},
"teeClub": {
"x1": 0.448676,
"y1": 0.14742,
"x2": 0.571853,
"y2": 0.204025
},
"approachGir": {
"x1": 0.571853,
"y1": 0.14742,
"x2": 0.664235,
"y2": 0.204025
},
"approachClub": {
"x1": 0.664235,
"y1": 0.14742,
"x2": 0.787412,
"y2": 0.204025
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.14742,
"x2": 0.910588,
"y2": 0.204025
}
}
},
{
"holeNumber": 2,
"row": {
"x1": 0.089412,
"y1": 0.204025,
"x2": 0.910588,
"y2": 0.26063
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.204025,
"x2": 0.171529,
"y2": 0.26063
},
"score": {
"x1": 0.171529,
"y1": 0.204025,
"x2": 0.263912,
"y2": 0.26063
},
"putts": {
"x1": 0.263912,
"y1": 0.204025,
"x2": 0.356294,
"y2": 0.26063
},
"teeFir": {
"x1": 0.356294,
"y1": 0.204025,
"x2": 0.448676,
"y2": 0.26063
},
"teeClub": {
"x1": 0.448676,
"y1": 0.204025,
"x2": 0.571853,
"y2": 0.26063
},
"approachGir": {
"x1": 0.571853,
"y1": 0.204025,
"x2": 0.664235,
"y2": 0.26063
},
"approachClub": {
"x1": 0.664235,
"y1": 0.204025,
"x2": 0.787412,
"y2": 0.26063
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.204025,
"x2": 0.910588,
"y2": 0.26063
}
}
},
{
"holeNumber": 3,
"row": {
"x1": 0.089412,
"y1": 0.26063,
"x2": 0.910588,
"y2": 0.317235
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.26063,
"x2": 0.171529,
"y2": 0.317235
},
"score": {
"x1": 0.171529,
"y1": 0.26063,
"x2": 0.263912,
"y2": 0.317235
},
"putts": {
"x1": 0.263912,
"y1": 0.26063,
"x2": 0.356294,
"y2": 0.317235
},
"teeFir": {
"x1": 0.356294,
"y1": 0.26063,
"x2": 0.448676,
"y2": 0.317235
},
"teeClub": {
"x1": 0.448676,
"y1": 0.26063,
"x2": 0.571853,
"y2": 0.317235
},
"approachGir": {
"x1": 0.571853,
"y1": 0.26063,
"x2": 0.664235,
"y2": 0.317235
},
"approachClub": {
"x1": 0.664235,
"y1": 0.26063,
"x2": 0.787412,
"y2": 0.317235
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.26063,
"x2": 0.910588,
"y2": 0.317235
}
}
},
{
"holeNumber": 4,
"row": {
"x1": 0.089412,
"y1": 0.317235,
"x2": 0.910588,
"y2": 0.37384
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.317235,
"x2": 0.171529,
"y2": 0.37384
},
"score": {
"x1": 0.171529,
"y1": 0.317235,
"x2": 0.263912,
"y2": 0.37384
},
"putts": {
"x1": 0.263912,
"y1": 0.317235,
"x2": 0.356294,
"y2": 0.37384
},
"teeFir": {
"x1": 0.356294,
"y1": 0.317235,
"x2": 0.448676,
"y2": 0.37384
},
"teeClub": {
"x1": 0.448676,
"y1": 0.317235,
"x2": 0.571853,
"y2": 0.37384
},
"approachGir": {
"x1": 0.571853,
"y1": 0.317235,
"x2": 0.664235,
"y2": 0.37384
},
"approachClub": {
"x1": 0.664235,
"y1": 0.317235,
"x2": 0.787412,
"y2": 0.37384
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.317235,
"x2": 0.910588,
"y2": 0.37384
}
}
},
{
"holeNumber": 5,
"row": {
"x1": 0.089412,
"y1": 0.37384,
"x2": 0.910588,
"y2": 0.430445
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.37384,
"x2": 0.171529,
"y2": 0.430445
},
"score": {
"x1": 0.171529,
"y1": 0.37384,
"x2": 0.263912,
"y2": 0.430445
},
"putts": {
"x1": 0.263912,
"y1": 0.37384,
"x2": 0.356294,
"y2": 0.430445
},
"teeFir": {
"x1": 0.356294,
"y1": 0.37384,
"x2": 0.448676,
"y2": 0.430445
},
"teeClub": {
"x1": 0.448676,
"y1": 0.37384,
"x2": 0.571853,
"y2": 0.430445
},
"approachGir": {
"x1": 0.571853,
"y1": 0.37384,
"x2": 0.664235,
"y2": 0.430445
},
"approachClub": {
"x1": 0.664235,
"y1": 0.37384,
"x2": 0.787412,
"y2": 0.430445
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.37384,
"x2": 0.910588,
"y2": 0.430445
}
}
},
{
"holeNumber": 6,
"row": {
"x1": 0.089412,
"y1": 0.430445,
"x2": 0.910588,
"y2": 0.48705
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.430445,
"x2": 0.171529,
"y2": 0.48705
},
"score": {
"x1": 0.171529,
"y1": 0.430445,
"x2": 0.263912,
"y2": 0.48705
},
"putts": {
"x1": 0.263912,
"y1": 0.430445,
"x2": 0.356294,
"y2": 0.48705
},
"teeFir": {
"x1": 0.356294,
"y1": 0.430445,
"x2": 0.448676,
"y2": 0.48705
},
"teeClub": {
"x1": 0.448676,
"y1": 0.430445,
"x2": 0.571853,
"y2": 0.48705
},
"approachGir": {
"x1": 0.571853,
"y1": 0.430445,
"x2": 0.664235,
"y2": 0.48705
},
"approachClub": {
"x1": 0.664235,
"y1": 0.430445,
"x2": 0.787412,
"y2": 0.48705
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.430445,
"x2": 0.910588,
"y2": 0.48705
}
}
},
{
"holeNumber": 7,
"row": {
"x1": 0.089412,
"y1": 0.48705,
"x2": 0.910588,
"y2": 0.543654
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.48705,
"x2": 0.171529,
"y2": 0.543654
},
"score": {
"x1": 0.171529,
"y1": 0.48705,
"x2": 0.263912,
"y2": 0.543654
},
"putts": {
"x1": 0.263912,
"y1": 0.48705,
"x2": 0.356294,
"y2": 0.543654
},
"teeFir": {
"x1": 0.356294,
"y1": 0.48705,
"x2": 0.448676,
"y2": 0.543654
},
"teeClub": {
"x1": 0.448676,
"y1": 0.48705,
"x2": 0.571853,
"y2": 0.543654
},
"approachGir": {
"x1": 0.571853,
"y1": 0.48705,
"x2": 0.664235,
"y2": 0.543654
},
"approachClub": {
"x1": 0.664235,
"y1": 0.48705,
"x2": 0.787412,
"y2": 0.543654
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.48705,
"x2": 0.910588,
"y2": 0.543654
}
}
},
{
"holeNumber": 8,
"row": {
"x1": 0.089412,
"y1": 0.543654,
"x2": 0.910588,
"y2": 0.600259
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.543654,
"x2": 0.171529,
"y2": 0.600259
},
"score": {
"x1": 0.171529,
"y1": 0.543654,
"x2": 0.263912,
"y2": 0.600259
},
"putts": {
"x1": 0.263912,
"y1": 0.543654,
"x2": 0.356294,
"y2": 0.600259
},
"teeFir": {
"x1": 0.356294,
"y1": 0.543654,
"x2": 0.448676,
"y2": 0.600259
},
"teeClub": {
"x1": 0.448676,
"y1": 0.543654,
"x2": 0.571853,
"y2": 0.600259
},
"approachGir": {
"x1": 0.571853,
"y1": 0.543654,
"x2": 0.664235,
"y2": 0.600259
},
"approachClub": {
"x1": 0.664235,
"y1": 0.543654,
"x2": 0.787412,
"y2": 0.600259
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.543654,
"x2": 0.910588,
"y2": 0.600259
}
}
},
{
"holeNumber": 9,
"row": {
"x1": 0.089412,
"y1": 0.600259,
"x2": 0.910588,
"y2": 0.656864
},
"cells": {
"hole": {
"x1": 0.089412,
"y1": 0.600259,
"x2": 0.171529,
"y2": 0.656864
},
"score": {
"x1": 0.171529,
"y1": 0.600259,
"x2": 0.263912,
"y2": 0.656864
},
"putts": {
"x1": 0.263912,
"y1": 0.600259,
"x2": 0.356294,
"y2": 0.656864
},
"teeFir": {
"x1": 0.356294,
"y1": 0.600259,
"x2": 0.448676,
"y2": 0.656864
},
"teeClub": {
"x1": 0.448676,
"y1": 0.600259,
"x2": 0.571853,
"y2": 0.656864
},
"approachGir": {
"x1": 0.571853,
"y1": 0.600259,
"x2": 0.664235,
"y2": 0.656864
},
"approachClub": {
"x1": 0.664235,
"y1": 0.600259,
"x2": 0.787412,
"y2": 0.656864
},
"approachDistance": {
"x1": 0.787412,
"y1": 0.600259,
"x2": 0.910588,
"y2": 0.656864
}
}
}
],
"totals": {
"x1": 0.089412,
"y1": 0.656864,
"x2": 0.910588,
"y2": 0.713469
}
},
"artifacts": {
"normalizedImage": {
"url": "https://delivery.example.com/example/normalized.png?Expires=1789396200&Signature=redacted&Key-Pair-Id=EXAMPLE",
"contentType": "image/png",
"sizeBytes": 2310842,
"sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"width": 1275,
"height": 2063,
"expiresAt": "2026-09-14T14:30:00.000Z",
"retentionUntil": "2026-09-15T00:00:00.000Z"
},
"debugImage": {
"url": "https://delivery.example.com/example/debug.jpg?Expires=1789396200&Signature=redacted&Key-Pair-Id=EXAMPLE",
"contentType": "image/jpeg",
"sizeBytes": 481993,
"sha256": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"width": 1656,
"height": 2200,
"expiresAt": "2026-09-14T14:30:00.000Z",
"retentionUntil": "2026-09-15T00:00:00.000Z"
}
},
"warnings": []
}{
"schema": "caddie100-scorecard-normalization@3",
"requestId": "scn_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"courseId": "example-course",
"teeBox": "Blue",
"identifier": "example-round-42",
"source": {
"method": "multipart",
"persisted": false
},
"template": {
"designId": "M9H1",
"slug": "greens-one-sheet",
"version": 4,
"panel": "F9",
"layoutRevision": "greens-one-sheet-scorecard@4",
"fiducialFamily": "caddie-2x9-h10-page-family@1",
"source": "fiducials",
"captureId": "front-nine-page",
"topology": "single-page",
"definitionHash": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"revisionManifestHash": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
},
"printedMetadata": {
"text": "SM9H1-CNJ282-EF88",
"courseToken": "NJ282",
"designChunk": "SM9H1",
"checksum": "F88",
"correctionCount": 0
},
"normalization": {
"rotationDegrees": 0,
"sourceWidth": 3072,
"sourceHeight": 4080,
"sourceCorners": {
"topLeft": {
"x": 612.25,
"y": 498.5
},
"topRight": {
"x": 2510.75,
"y": 522.5
},
"bottomRight": {
"x": 2491.5,
"y": 3608.25
},
"bottomLeft": {
"x": 590.5,
"y": 3584.75
}
},
"outputWidth": 1275,
"outputHeight": 2063,
"sourceToCanonical": [
0.6715,
0.0047,
-412.8,
-0.0052,
0.6679,
-329.1,
0,
0,
1
]
},
"regions": {
"coordinateSpace": {
"origin": "top-left",
"unit": "normalized-0-to-1",
"canonicalPixels": {
"width": 1275,
"height": 2063
}
},
"captureId": "front-nine-page",
"topology": "single-page",
"pages": [
{
"pageId": "front-nine",
"frame": {
"x1": 0,
"y1": 0,
"x2": 1,
"y2": 1
}
}
],
"semanticRegions": []
},
"artifacts": {
"normalizedImage": {
"url": "https://delivery.example.com/example/normalized.png?Expires=1789396200&Signature=redacted&Key-Pair-Id=EXAMPLE",
"contentType": "image/png",
"sizeBytes": 2310842,
"sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"width": 1275,
"height": 2063,
"expiresAt": "2026-09-14T14:30:00.000Z",
"retentionUntil": "2026-09-15T00:00:00.000Z"
},
"debugImage": {
"url": "https://delivery.example.com/example/debug.jpg?Expires=1789396200&Signature=redacted&Key-Pair-Id=EXAMPLE",
"contentType": "image/jpeg",
"sizeBytes": 481993,
"sha256": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"width": 1656,
"height": 2200,
"expiresAt": "2026-09-14T14:30:00.000Z",
"retentionUntil": "2026-09-15T00:00:00.000Z"
}
},
"warnings": [],
"quality": {
"confidence": 0.986,
"band": "high",
"detectedCandidates": 4,
"decodedFiducials": 4,
"matchedFiducials": 4,
"reprojectionRmsPixels": 0.31,
"reprojectionMaxPixels": 0.52,
"markerEvidence": [
{
"name": "bottomLeft",
"kind": "index-front",
"sourceCenter": {
"x": 590.5,
"y": 3584.75
},
"bitErrors": 0,
"contrast": 142.8,
"pixelsPerModule": 11.6,
"reprojectionErrorPixels": 0.21
}
]
}
}{
"schema": "caddie100-scorecard-normalization@4",
"requestId": "scn_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"courseId": "example-course",
"teeBox": "Blue",
"identifier": "example-round-42",
"status": "COMPLETE",
"source": {
"method": "multipart",
"persisted": false
},
"pages": [
{
"pageInstanceId": "page-1",
"status": "NORMALIZED",
"template": {
"designId": "M9H1",
"slug": "greens-one-sheet",
"version": 4,
"panel": "F9",
"layoutRevision": "greens-one-sheet-scorecard@4",
"fiducialFamily": "caddie-2x9-h10-page-family@1",
"source": "fiducials",
"captureId": "front-nine-page",
"topology": "single-page",
"definitionHash": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"revisionManifestHash": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
},
"printedMetadata": {
"text": "SM9H1-CNJ282-EF88",
"courseToken": "NJ282",
"designChunk": "SM9H1",
"checksum": "F88",
"correctionCount": 0
},
"normalization": {
"rotationDegrees": 0,
"sourceWidth": 3072,
"sourceHeight": 4080,
"sourceCorners": {
"topLeft": {
"x": 612.25,
"y": 498.5
},
"topRight": {
"x": 2510.75,
"y": 522.5
},
"bottomRight": {
"x": 2491.5,
"y": 3608.25
},
"bottomLeft": {
"x": 590.5,
"y": 3584.75
}
},
"outputWidth": 1275,
"outputHeight": 2063,
"sourceToCanonical": [
0.6715,
0.0047,
-412.8,
-0.0052,
0.6679,
-329.1,
0,
0,
1
]
},
"regions": {
"coordinateSpace": {
"origin": "top-left",
"unit": "normalized-0-to-1",
"canonicalPixels": {
"width": 1275,
"height": 2063
}
},
"captureId": "front-nine-page",
"topology": "single-page",
"pages": [
{
"pageId": "front-nine",
"frame": {
"x1": 0,
"y1": 0,
"x2": 1,
"y2": 1
}
}
],
"semanticRegions": []
},
"quality": {
"confidence": 0.986,
"band": "high",
"detectedCandidates": 4,
"decodedFiducials": 4,
"matchedFiducials": 4,
"reprojectionRmsPixels": 0.31,
"reprojectionMaxPixels": 0.52,
"markerEvidence": [
{
"name": "bottomLeft",
"kind": "index-front",
"sourceCenter": {
"x": 590.5,
"y": 3584.75
},
"bitErrors": 0,
"contrast": 142.8,
"pixelsPerModule": 11.6,
"reprojectionErrorPixels": 0.21
}
]
},
"artifacts": {
"normalizedImage": {
"url": "https://delivery.example.com/example/normalized.png?Expires=1789396200&Signature=redacted&Key-Pair-Id=EXAMPLE",
"contentType": "image/png",
"sizeBytes": 2310842,
"sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"width": 1275,
"height": 2063,
"expiresAt": "2026-09-14T14:30:00.000Z",
"retentionUntil": "2026-09-15T00:00:00.000Z"
},
"debugImage": {
"url": "https://delivery.example.com/example/debug.jpg?Expires=1789396200&Signature=redacted&Key-Pair-Id=EXAMPLE",
"contentType": "image/jpeg",
"sizeBytes": 481993,
"sha256": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"width": 1656,
"height": 2200,
"expiresAt": "2026-09-14T14:30:00.000Z",
"retentionUntil": "2026-09-15T00:00:00.000Z"
}
},
"warnings": []
}
],
"discovery": {
"detectedCandidates": 4,
"decodedFiducials": 4,
"retainedFiducials": 4,
"matchedFiducials": 4,
"unmatchedFiducials": 0
},
"issues": [],
"warnings": []
}Temporary delivery
Download anything you want to keep.
The submitted source image is processed in memory and is never persisted. Only the normalized PNG and marker-diagnostic JPEG are stored. Their signed URLs remain accessible for no more than 24 hours. Raw provider responses are not retained by the production route.
Objects become eligible for lifecycle deletion after 1 day, rounded to the next UTC midnight under S3 day-based lifecycle rules. Physical deletion is asynchronous, so retentionUntil is not a promise that the object exists until that time or disappears exactly then.
Processing idempotency records—including the response needed for safe replay—have a 24-hour logical lifetime in DynamoDB. DynamoDB TTL deletion is asynchronous. Handwriting images are sent to OpenAI's Responses API with store: false, which disables response application-state storage; OpenAI's default abuse-monitoring logs may still retain customer content for up to 30 days unless the API project has approved Modified Abuse Monitoring or Zero Data Retention controls.
Signed URLs are bearer credentials: do not log, publish, or send them to analytics. The client owns permanent retention and should download and store any desired copy before expiresAt. This service does not refresh or re-sign URLs and does not maintain long-term scorecard storage.
No silent guesses
Unsafe or incomplete evidence fails explicitly.
Errors use application/problem+json with stable status and code fields. Marker or perspective uncertainty returns 422 rather than a guessed crop. Authentication and rate-limit errors use the shared API problem contract.
| Code | HTTP | Retry | Client action |
|---|---|---|---|
invalid-content-length | 400 | No | Send a valid Content-Length or let the HTTP client calculate it. |
invalid-image-url | 400 | No | Use a public HTTPS hostname on the default port without credentials or a fragment. |
invalid-json | 400 | No | Correct the JSON syntax. |
invalid-multipart | 400 | No | Rebuild the multipart body and boundary. |
invalid-request-body | 400 | Conditional | Retry only if transport interruption caused the unreadable stream. |
invalid-scorecard-request | 400 | No | Correct missing, ambiguous, or invalid fields. |
image-decode-failed | 400 | No | Supply a complete JPEG, PNG, or WebP image. |
image-fetch-failed | 502 | Conditional | Confirm the source is reachable for 15 seconds, then retry. |
image-too-large | 413 | No | Reduce the file size or decoded dimensions; use URL ingestion only for a multipart-size overage. |
registration-markers-incomplete | 422 | After correction | No complete page was recoverable. Retake with all four markers for at least one page visible, sharp, and unobstructed. |
scorecard-capture-disabled | 422 | Later | This capture or topology is not in the production support registry. |
scorecard-capture-ambiguous | 422 | After correction | Retake the complete page or spread so its marker topology identifies one capture surface. |
scorecard-artifact-signing-unavailable | 503 | Yes | Retry with backoff; no successful result was metered. |
scorecard-artifact-storage-unavailable | 503 | Yes | Retry with backoff; partial derived objects are cleaned up. |
scorecard-artifact-verification-failed | 503 | Yes | Retry with backoff; unverified derived objects are cleaned up. |
scorecard-design-detection-unavailable | 503 | Yes | Retry with backoff; the printed design reader was unavailable. |
scorecard-design-disabled | 422 | Later | This registered design is not in the production support registry. |
scorecard-design-contradictory | 422 | After correction | Retake the complete printed design; its label and visible marker topology disagree. |
scorecard-design-unknown | 422 | No | Use a registered CADDIE.100 Smart Scorecard design. |
scorecard-design-unreadable | 422 | After correction | Retake the photo with the printed S identity label beside the unique marker visible and sharp. |
scorecard-identity-contradictory | 422 | After correction | Capture one complete scorecard surface without another scorecard or identity label in frame. |
scorecard-index-marker-missing | 422 | After correction | Retake the photo with the unique identity marker and adjacent label visible. |
scorecard-normalization-unavailable | 503 | Yes | Retry with backoff. |
scorecard-normalization-version-incompatible | 406 | After correction | Request version 3 with caddie-scorecard-normalization-version for a topology-neutral capture. |
scorecard-perspective-invalid | 422 | After correction | Retake the photo flatter and with all four corners clearly separated. |
scorecard-request-too-large | 413 | No | Reduce the request or use URL ingestion for a large source file. |
unsupported-content-type | 415 | No | Use application/json or multipart/form-data. |
unsupported-image-type | 415 | No | Convert the source to JPEG, PNG, or WebP. |
unsupported-normalization-version | 406 | After correction | Request normalization response version 2, 3, or 4. |
usage-meter-unavailable | 503 | Yes | Retry with backoff; derived artifacts from the failed attempt are cleaned up. |
Processing-only failures
| Code | HTTP | Retry | Client action |
|---|---|---|---|
idempotency-key-conflict | 409 | No | Use the original input or choose a new key for a different logical request. |
invalid-idempotency-key | 400 | After correction | Send 8–128 visible ASCII characters. |
scorecard-extraction-failed | 502 | Conditional | Retry the same key; retake a clearer photo first when image quality caused the failure. |
scorecard-idempotency-unavailable | 503 | Yes | Retry with the same key after backoff. |
scorecard-processing-configuration-invalid | 503 | No | Contact CADDIE.100 support; the server configuration is invalid. |
scorecard-processing-deadline-exceeded | 504 | Conditional | Retry the same key only when instructed; the service prevents an unsafe provider replay. |
scorecard-processing-in-progress | 409 | Yes | Wait for Retry-After, then resend the same key and exact input. |
scorecard-processing-outcome-unknown | 409 | No | Do not create a new key automatically; request human review or contact support. |
scorecard-processing-panel-unsupported | 422 | After correction | Use an F9 panel for handwriting extraction or send this panel to /normalize for geometry only. |
scorecard-processing-unavailable | 503 | Yes | Retry the same key with backoff. |
scorecard-processing-version-incompatible | 406 | After correction | Request processing response version 2 for M18F or M18B; other supported designs may use version 1 or 2. |
scorecard-response-too-large | 503 | No | Contact CADDIE.100 support; the safe replay record could not be stored. |
Both endpoints are synchronous and polling-free. They do not create jobs, issue webhooks, or retain submitted originals. Processing adds stable idempotency, extraction, deadline, disabled-service, and unknown-outcome failures. Retry a metering failure with the same key. Never automatically retry an unknown provider outcome with a new key.
Compatibility history
Changelog.
SM18F and SM18B, including their Round Details and Round Summary/Penalty components. The release uses explicit owner authorization; broader physical and handwriting-provider sampling remains post-release quality assurance.SM9H1GG and SM9H10GG. Internal playground URLs remain employee-only diagnostics; design support is not employee-gated.@2 as the default M9 response, added negotiated @3 for topology-neutral evidence, and restricted production routing to hash-pinned stable identities with an accepted release basis. Preview and internal identities now fail closed.S, C, and E chunks, required M9H1/M9H10 design codes, checksum-assisted OCR recovery, and the optional five-character course-layout code without applying course data.caddie100-scorecard-processing@1 with durable idempotency, categorical review decisions, bounded processing, and one-unit logical-result metering.caddie100-scorecard-normalization@2 with signed artifact descriptors, layout greens-one-sheet-scorecard@4, and fiducial family caddie-4x4-h8@1.Future public schema, recognized layout, template-registry, or fiducial-family changes will receive a dated entry here.