Printable asset service · production

CADDIE.100
Printables.

Submit your own complete print-design JSON, select a saved template, or select a multi-output package recipe. CADDIE.100 resolves any declared course dependencies, verifies the result, and returns a downloadable production package. Open-ended designs such as Smart Minimal 18 can be associated with any valid course ID without using course geometry.

Private-key-only service

Call Printables from your server.

Every public API request requires a database-backed private c100_live_… or c100_test_… credential with the course-printables:create scope. The client account, credential, organization, and permission must all be active. Send the credential with x-api-key or Authorization: Bearer ….

A missing or invalid credential receives 401 api-access-required. An active credential whose key or organization policy lacks the Printables scope receives 403 insufficient-api-scope.

Browser requests are forbiddenPublishable c100_pk_… keys and requests carrying an Origin header receive 403 private-api-key-required. The Printables scope cannot be added to an organization's browser entitlements.

See Credentials, scopes & rotation for key formats, environments, organization roles, and a safe rotation workflow.

Server quickstart · JavaScript

Create your first printable.

Keep the API key in a server-side secret, choose a stable idempotency key for the logical order, and persist the returned printable ID and links.self URL. If a connection fails before you receive the response, retry the same normalized request with the same key.

01Create a job with a private key and stable idempotency key.
02Poll links.self until the job reaches READY or FAILED.
03Download the signed artifact URLs within 15 minutes or retrieve the job again to refresh them.
04Open the matching PDF or PNG and follow HOW TO PRINT.txt before printing.
const apiKey = process.env.CADDIE100_API_KEY;
if (!apiKey) throw new Error("CADDIE100_API_KEY is not configured");

// Persist this value with your order and reuse it if the request is retried.
const idempotencyKey = "your-system:order-42";
const response = await fetch(
  "https://caddie100.com/api/v1/courses/COURSE_ID/printables",
  {
    method: "POST",
    headers: {
      "x-api-key": apiKey,
      "Idempotency-Key": idempotencyKey,
      "content-type": "application/json",
    },
    body: JSON.stringify({
      identifier: "order-42",
      template: { slug: "greens-one-sheet" },
      binding: { foldEdge: "left", foldLine: true },
      settings: {
        graphic: {
          schema: "caddie100-print-green-resource@1",
          capabilityRevision: "print-graphic-design@1",
          profile: "par-the-course-greens-one-sheet@1",
          subject: { type: "green" },
          style: {
            graphicStyle: "colored-pencil",
            graphicStyling: { features: { green: { fill: "#B7D99A", strokeColor: "#587C3D" } } },
            paperTheme: "#FFFFFF",
          },
          composition: { orientation: "approach", paddingMeters: 5 },
          smartComponents: [
            { type: "grids", spacingYards: 5, scope: "green-buffer", origin: "maximum-dimensions", style: { showLabels: false } },
            { type: "cup", target: "maximum-clearance", style: { size: 16 } },
          ],
          presentation: { panelBackground: "#FFFFFF" },
        },
      },
    }),
  },
);

const payload = await response.json();
if (!response.ok) {
  throw new Error([payload.code, payload.title].filter(Boolean).join(": "));
}

// Store both values so another process can resume polling after a restart.
const printableId = payload.id;
const statusUrl = payload.links.self;
Use one key per logical createDo not generate a new idempotency key on every network retry. Save it before the first request and replace it only when the customer intentionally requests a new printable.

Asynchronous jobs

Submit, poll, then download.

POST/api/v1/courses/{courseId}/printables/compile

Normalizes and validates a draft, returning warnings, stable design/revision identities, exact preview geometry, a safe resource plan, current course eligibility, component-level availability issues, and a one-hour compilation reference. The returned normalizedDraft remains a directly resubmittable request body. This private, non-billable operation does not require an Idempotency-Key and does not create or queue a job.

POST/api/v1/courses/{courseId}/printables

Accepts a complete request or a compact compilation reference, rechecks current eligibility, creates a printable job, records one Printables usage unit after durable acceptance, and returns 202 Accepted. The CADDIE.100 course ID and an Idempotency-Key header are required.

POST/api/v1/printables/journals/compile

Non-billable validation and page-geometry compilation for the course-independent ten-round journal. No course ID or Idempotency-Key is needed.

POST/api/v1/printables/journals

Creates a course-independent ten-round journal. Supply the journal template and an Idempotency-Key; no course ID is required. This uses the same private-key scope, job lifecycle, and metering as other printables.

GET/api/v1/courses/{courseId}/printables/{printableId}

Returns QUEUED, RENDERING, PACKAGING, READY, or FAILED. A ready job includes short-lived private download URLs for its ZIP, PDF, and proof.

GET/api/v1/printables

Lists the authenticated organization's retained jobs newest-first. Use limit and the opaque nextCursor for pagination.

GET/api/v1/printables/{printableId}

Retrieves a known job without requiring its course ID. Ready jobs receive newly signed ZIP, PDF, and proof URLs.

DELETE/api/v1/printables/{printableId}

Deletes an organization-owned READY or FAILED job and its job-specific delivery artifacts. Active jobs must reach a terminal state first.

POST/api/v1/printables/search

Returns every retained job associated with an exact client identifier. Identifiers are not required to be unique.

curl -X POST "https://caddie100.com/api/v1/courses/COURSE_ID/printables" \
  -H "x-api-key: c100_live_YOUR_PRIVATE_KEY" \
  -H "Idempotency-Key: ptc-order-42" \
  -H "content-type: application/json" \
  -d '{
    "template": { "slug": "greens-one-sheet" },
    "binding": { "foldEdge": "left", "foldLine": true },
    "settings": {
      "graphic": {
        "schema": "caddie100-print-green-resource@1",
        "capabilityRevision": "print-graphic-design@1",
        "profile": "par-the-course-greens-one-sheet@1",
        "subject": { "type": "green" },
        "style": {
          "graphicStyle": "colored-pencil",
          "graphicStyling": { "features": { "green": { "fill": "#B7D99A", "strokeColor": "#587C3D" } } },
          "paperTheme": "#FFFFFF"
        },
        "composition": { "orientation": "approach", "paddingMeters": 5 },
        "smartComponents": [
          { "type": "grids", "spacingYards": 5, "scope": "green-buffer", "origin": "maximum-dimensions", "style": { "showLabels": false } },
          { "type": "cup", "target": "maximum-clearance", "style": { "size": 16 } }
        ],
        "presentation": { "panelBackground": "#FFFFFF" }
      }
    }
  }'

For complex multi-page designs, compile once and create from the returned reference. The reference is bound to the authenticated organization, course, canonical request hash, and expiration; a rotated active key for the same organization may use it.

// First compile the complete draft. This call is non-billable.
const compiled = await fetch(
  "https://caddie100.com/api/v1/courses/COURSE_ID/printables/compile",
  {
    method: "POST",
    headers: { "x-api-key": apiKey, "content-type": "application/json" },
    body: JSON.stringify(completeDraft),
  },
).then(async (response) => {
  const value = await response.json();
  if (!response.ok) throw new Error(value.code + ": " + value.title);
  return value;
});

if (!compiled.eligibility.eligible) {
  // Each path is an RFC 6901 JSON Pointer into compiled.normalizedDraft.
  console.table(compiled.eligibility.componentIssues);
  throw new Error("Edit normalizedDraft and compile it again before Create.");
}

// Then create with the organization-, course-, and hash-bound reference.
const response = await fetch(
  "https://caddie100.com/api/v1/courses/COURSE_ID/printables",
  {
    method: "POST",
    headers: {
      "x-api-key": apiKey,
      "Idempotency-Key": "your-system:order-42",
      "content-type": "application/json",
    },
    body: JSON.stringify({
      schema: "caddie100-printable-create-from-compilation@1",
      kind: "compiled-design",
      compilationId: compiled.compilation.id,
      requestHash: compiled.compilation.requestHash,
      identifier: "order-42",
    }),
  },
);

Resolve component availability before Create.

Compile separates request validation from course-data availability. Invalid JSON, unsupported properties, and incompatible Smart Component options still return a request error. A structurally valid draft returns 200, even when one or more requested visuals cannot be produced. In that case eligibility.eligible is false, componentIssues identifies the exact normalized node, and Create remains strict.

graphicThe base course graphic cannot be produced, such as an unavailable hole, green, or profile-elevation subject.
smart-componentThe base graphic is valid, but one overlay cannot be produced from that hole's available geometry, measurements, analysis, or elevation data.
componentA non-graphic printable component cannot be produced, such as a page component that references an unavailable hole.
{
  "severity": "error",
  "code": "overlay-target-unavailable",
  "scope": "smart-component",
  "path": "/definition/pages/17/components/0/props/resource/smartComponents/2",
  "pageId": "hole-18",
  "componentId": "fairway-width-preview",
  "componentType": "course-graphic",
  "resourceHash": "sha256:…",
  "subject": { "type": "hole", "holeNumber": 18 },
  "smartComponentId": "fairway-widths",
  "smartComponentType": "fairway-widths",
  "message": "None of the requested radial fairway-width stations can be produced for hole 18."
}

path is an RFC 6901 JSON Pointer into the returned normalizedDraft, not necessarily the client's original input. It points through /definition/… for definition-authored designs and /design/… for complete raw designs. Use the IDs and types for display and logging; use path to locate the node. Do not parse message or infer behavior from array order.

The issue envelope is shared across all component types. Component-specific inputs remain at the pointed-to node, so a client does not need a separate error schema for every Smart Component. For example, when a fairway-widths issue points to a node containing yardages [205, 225, 245], the client already knows which requested stations belonged to that failed component. A design tool may remove the node, change its settings, or update related layout itself; CADDIE.100 does not remove neighboring preview frames or make product-layout decisions.

componentIssueSummary always reports total error and warning counts. At most 256 issues are returned, sorted by stable path and code. If truncated is true, resolve the returned issues and compile again to retrieve any remaining issues. Submit Create only after a fresh Compile returns eligible: true; Compile references preserve the exact normalized draft and are not mutation instructions.

{
  "schema": "caddie100-printable-job@1",
  "id": "prn_01JEXAMPLE00000000",
  "courseId": "COURSE_ID",
  "status": "QUEUED",
  "createdAt": "2026-08-30T12:00:00.000Z",
  "updatedAt": "2026-08-30T12:00:00.000Z",
  "expiresAt": "2026-09-29T12:00:00.000Z",
  "provenance": {
    "template": { "slug": "greens-one-sheet", "version": 6 },
    "compilerRevision": "greens-one-sheet-compiler@6",
    "renderRecipeRevision": "greens-one-sheet-render-recipe@12",
    "acceptedCourseRevision": "sha256:…"
  },
  "links": {
    "self": "https://caddie100.com/api/v1/printables/prn_01JEXAMPLE00000000"
  }
}

Job lifecycle

Resume safely from every stage.

QUEUEDThe request is durably accepted and waiting to render. Persist the job ID and continue polling.
RENDERINGCourse data and the submitted design, selected template, or package recipe are being rendered.
PACKAGINGDuplex PDFs and their requested delivery files are being assembled while structural PDF, integrity, and ZIP checks complete.
READYThe job is terminal. Retrieve it for fresh, 15-minute signed ZIP, PDF, and proof links.
FAILEDThe job is terminal. Inspect failureCode, retain the job for support, and create a new job only after the cause is resolved.

Polling is a read operation and does not consume another create unit. The endpoint sends Retry-After: 3 while a job is active. The example below caps server-directed delays, stops after a bounded number of attempts, and accepts an AbortSignal so shutdown or request cancellation also cancels its timer and network work.

import { setTimeout as delay } from "node:timers/promises";

async function waitForPrintable(statusUrl, apiKey, signal) {
  for (let attempt = 0; attempt < 120; attempt += 1) {
    const response = await fetch(statusUrl, {
      headers: { "x-api-key": apiKey },
      cache: "no-store",
      signal,
    });
    const payload = await response.json();

    if (!response.ok) {
      throw new Error([payload.code, payload.title].filter(Boolean).join(": "));
    }
    if (payload.status === "READY") return payload;
    if (payload.status === "FAILED") {
      throw new Error("Printable failed: " + (payload.failureCode ?? "unknown"));
    }

    const requestedDelay = Number(response.headers.get("retry-after") ?? 3);
    const seconds = Number.isFinite(requestedDelay)
      ? Math.min(Math.max(requestedDelay, 1), 30)
      : 3;
    await delay(seconds * 1000, undefined, { signal });
  }

  throw new Error("Printable did not finish within the polling window");
}

const controller = new AbortController();
const readyJob = await waitForPrintable(statusUrl, apiKey, controller.signal);

A ready job has a stable authenticated links.self URL plus ephemeral links.package and links.proof URLs. links.pdf is also present when the primary output is a PDF; a raw PNG design does not invent a PDF. Stored standard-set jobs return their registered Letter and Legal proof set. A custom-design standard set returns one 150-DPI first-sheet thumbnail for the primary PDF; it does not generate or store one PNG per PDF page. The links.proof shortcut and optional proofs entry identify that preview and include its exact pixel dimensions and SHA-256 hash. Treat each signed URL like a temporary credential: keep it out of analytics and logs, do not send the CADDIE.100 API key to the artifact host, and retrieve the job again when a link expires.

import { createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { Readable } from "node:stream";

async function savePackage(packageUrl, outputPath, signal) {
  // Signed artifact URLs are capabilities; do not attach your API key.
  const response = await fetch(packageUrl, { redirect: "error", signal });
  if (!response.ok || !response.body) {
    throw new Error("Package download failed with HTTP " + response.status);
  }

  await pipeline(
    Readable.fromWeb(response.body),
    createWriteStream(outputPath, { flags: "wx" }),
    { signal },
  );
}

await savePackage(readyJob.links.package, "./printable-package.zip");
Stream large packagesThe Node example streams into a newly created file instead of buffering the complete ZIP in memory. Choose an application-owned destination path, handle disk errors, and delete partial local output after a failed transfer.

Safe retries and stable failures

Retry creates without creating twice.

Give every logical create a unique Idempotency-Key containing 1–128 visible ASCII characters. The key is scoped to your organization and retained with the job for 30 days. Repeating the same normalized request returns the original job, including when the first response was lost. Reusing the key with a different request returns 409 idempotency-conflict. If the original job was deleted, replay returns 410 printable-deleted; use a new key only for genuinely new work.

Only one durably accepted job consumes one course-printables:create unit. Compile calls, expired or mismatched compilation references, validation failures, unavailable or incomplete courses, idempotent replays, list/search/get requests, status polling, and signed-link refresh do not consume additional create units.

CADDIE.100 may reuse an exact, independently verified artwork render for the same course revision and visual specification. Reuse never bypasses account, private-key, permission, ownership, or create-metering checks: every accepted request receives its own organization-owned job, freshly assembled ZIP, license, printing instructions, and private download paths. Partner identifiers, credentials, webhook metadata, and licensee names never enter the shared artwork archive.

Supported Printables capabilities are available to every authenticated client account with the required private-key permission. There is no organization quota, course allowlist, feature/style cohort, or staged percentage. Operational per-minute rate limits may still return 429 rate-limit-exceeded with Retry-After.

Traditional, Pencil, and Colored Pencil all accept elevation heatmaps and downhill arrows independently. Pencil styles retain quieter defaults, but either analysis overlay can be enabled explicitly.

Pencil and Colored Pencil graphics also accept style.graphicStyling. The same style-specific palette contract applies to a stored greens-one-sheet settings.graphic resource and to generic course-graphic resources for hole, hole-region, green, course, and profile-elevation subjects. Partial palettes are strictly deep-merged into the current Graphics defaults; Traditional rejects this property.

Before dispatch, course-dependent designs verify their referenced holes, mapped geometry, measurements, and requested analysis coverage. Known-ineligible requests receive an actionable 422 problem with structured capability details, and requested analysis is never silently omitted. smart-minimal-18-sheet@5 is intentionally course-agnostic: it has no hole, green, measurement, or analysis dependency, and the path's course ID is only the job association.

Active jobs are reconciled independently of client polling. Poll links.self about every three seconds and obey Retry-After. Jobs are retained for 30 days; each successful get mints private download links valid for 15 minutes.

Organization-owned history

List, retrieve, and reconnect jobs.

Every management endpoint is server-to-server and requires the same private Printables scope. Jobs belong to the organization, not one credential, so another active private key for that organization can retrieve jobs created before a key rotation. A job owned by another organization is returned as not found.

List responses include status, course, identifier, timestamps, and a stable links.self URL. They intentionally do not mint three signed artifact URLs for every row. Retrieve a specific ready job to receive fresh 15-minute download URLs.

curl "https://caddie100.com/api/v1/printables?limit=25" \
  -H "x-api-key: c100_live_YOUR_PRIVATE_KEY"
curl "https://caddie100.com/api/v1/printables/prn_01JEXAMPLE00000000" \
  -H "x-api-key: c100_live_YOUR_PRIVATE_KEY"

Search identifiers in a JSON body rather than a URL so opaque customer or order associations do not become part of routine access-log URLs. Search results are paginated because an identifier is association metadata and may match more than one job.

curl -X POST "https://caddie100.com/api/v1/printables/search" \
  -H "x-api-key: c100_live_YOUR_PRIVATE_KEY" \
  -H "content-type: application/json" \
  -d '{
    "identifier": "client/order:42",
    "limit": 25
  }'

Delete a retained terminal job when its association is no longer needed. A successful delete returns 204 No Content, immediately scrubs the retained request metadata, and purges the job-specific S3 prefix. Deleting an absent job or one owned by another organization also returns 204, preventing existence leaks. An active owned job returns 409 printable-job-active; wait for READY or FAILED and retry.

curl -X DELETE "https://caddie100.com/api/v1/printables/prn_01JEXAMPLE00000000" -H "x-api-key: c100_live_YOUR_PRIVATE_KEY"
Deletion has bounded residual accessA signed URL issued before deletion may remain usable for the rest of its 15-minute lifetime. Deletion does not release the original Idempotency-Key before its normal expiry.

Polling-only delivery

Webhook admission is disabled.

The production contract does not accept webhook destinations. A new request containing webhook receives 400 printable-webhooks-disabled before course eligibility, dispatch, or metering.

Use authenticated polling of links.self as the source of truth. If signed webhook delivery is added later, it will use a new versioned contract with verification fixtures and replay-defense guidance.

Previously accepted records remain recoverableAn exact idempotent replay may return an already accepted job and its retained delivery history. This compatibility path cannot create a new webhook-enabled job.

Design JSON · templates · package recipes

Choose the source of the print job.

The create endpoint accepts exactly one of three sources. Use design when your client owns the layout. caddie100-single-print-job@1 preserves the greens-panel, personal-scorecard, and yardage-book component family. caddie100-single-print-job@2 supports arbitrary logical pages containing course graphics and safe page primitives. Both schemas define finished-page dimensions, physical stock—including arbitrary Custom width and height—sheet sides and placements, and a PDF or PNG filename. Use template as a shortcut to a stored, versioned design. Use package as a shortcut to a stored recipe that creates a coordinated set of outputs.

// 1. Send the complete design JSON you authored.
{ "kind": "design", "design": { "schema": "caddie100-single-print-job@1 or @2", "…": "…" } }

// 2. Select a stored design.
{ "kind": "template", "template": { "slug": "greens-one-sheet" } }
{ "kind": "template", "template": { "slug": "smart-minimal-18-sheet", "version": 5 } }
{ "kind": "template", "template": { "slug": "minimal-smart-scorecard-journal-10", "version": 1 } } // POST /api/v1/printables/journals

// 3. Select a stored multi-output recipe.
{ "kind": "package", "package": { "slug": "greens-one-sheet-standard-set" } }
{ "kind": "package", "package": { "slug": "smart-minimal-18-standard-set", "version": 5 } }
{ "kind": "package", "package": { "slug": "custom-design-standard-set", "version": 1 }, "definition": { "…": "…" }, "outputs": [
  { "profile": "letter-left", "format": "pdf", "filename": "course-letter.pdf", "primary": true },
  { "profile": "legal-top", "format": "pdf", "filename": "course-legal.pdf" }
] }

// 4. Create from a short-lived course compilation without resending its draft.
{
  "schema": "caddie100-printable-create-from-compilation@1",
  "kind": "compiled-design",
  "compilationId": "cmp_…",
  "requestHash": "sha256:…",
  "identifier": "optional-order-42"
}
Raw JSON is a validated design DSLYou control paper size, page geometry, placements, and registered component settings. The API accepts declarative Smart Components—not executable HTML, CSS, JavaScript, local paths, object keys, SVG markup, or arbitrary server code. Version 1 registers greens-panel, personal-scorecard, and yardage-book-page. Version 2 registers course-graphic, text, shape, approved-image, image, hole-header-v1, hole-info-block-v1, divider-v1, hole-score-v1, green-elevation-heatmap-legend-v1, and green-slope-heatmap-legend-v1.

hole-header-v1 accepts holeNumber, par, and yardage. It renders the registered Hole, Par, and yardage treatment at a fixed 0.34-inch height without template-authored HTML or CSS.

divider-v1 accepts a 0.25–12 point thicknessPoints, solid, dashed, or dotted style, a 1%–100% width, left/center/right align, and inch-based marginTop and marginBottom. Its default is a centered, full-width, 1.2-point solid divider.

hole-info-block-v1 accepts holeNumber, par, and handicap. Its default top-down layout renders a vertical centered stack. Set layout to left-right for a horizontal block with a non-shrinking Hole number on the left and left-aligned Par/Hcp lines on the right; this layout may use a frame as short as 0.5 inch. The shared textColor, textStrokeColor, and textStrokeWidthPoints remain defaults for all three values. holeNumberStyle, parStyle, and handicapStyle may each override fontSizePoints, textColor, textStrokeColor, and textStrokeWidthPoints. backgroundColor and paddingInches control the container; text strokes and the background may be transparent.

green-elevation-heatmap-legend-v1 and green-slope-heatmap-legend-v1 are course-independent page components. Their absolute frames can be placed anywhere on a logical page. They use the same canonical Graphics JS palettes with a normalized Low–High elevation domain, a 0–8% slope domain, and a simplified 0–4.4° degree domain. Orientation, block or gradient presentation, bar thickness and border, endpoint-only or full labels, below/alongside labels, outside endpoint caps, inside-end labels, optional segment rules, title, typography, background, padding, and spacing are configurable without requesting course data.

A course-graphic component may render hole, green, course, or profile-elevation. Compatible Smart Components use the same normalized meanings as Graphics JS. Each graphic owns its resource settings and frame placement, allowing different subjects and treatments on page 1 and page 2 without introducing another document renderer.

An image component accepts a CADDIE.100 catalog asset or a public HTTPS JPEG, PNG, or static WebP. Remote bytes are securely fetched, validated, normalized, and snapshotted before rendering; Chromium never receives the client URL. The component supports cover, contain, and fill, numeric X/Y alignment, transparent or hexadecimal backgrounds, opacity, strict frame clipping, and a 150 effective-DPI minimum. Supply expectedSha256 when you control immutable source storage.

Client-supplied package filesThe production packageFiles array adds 1–10 client-owned PDF, PNG, or JPEG files to the final ZIP for any request mode. It is available uniformly to every authenticated client with course-printables:create; no organization allowlist, client cohort, feature flag, or test-only gate applies. Each entry supplies a printable-ASCII relative path and a public HTTPS source.url; paths such as SPECIAL-OFFER.png and how-to/materials.pdf are placed relative to the generated package root. Source URLs may contain temporary query parameters but must remain usable when no fresh reusable asset exists. One source may be at most 20 MiB, all source files together at most 50 MiB, and the completed ZIP at most 128 MiB. Supply lowercase expectedSha256 to bind the exact downloaded source bytes and allow a fresh organization-scoped verified asset to be reused before another download. Without it, CADDIE.100 must download the source to calculate its identity but can still reuse a fresh scan afterward. A cache miss is quarantined and malware-scanned. PNG and JPEG inputs are safely re-encoded; each PDF page is rendered at 300 DPI and rebuilt into a fresh page-only PDF so links, annotations, forms, scripts, attachments, metadata, and other source structures cannot enter the package. Simultaneous identical misses are coalesced; clean scan attestations remain reusable for seven days and unused cache objects expire after 90 days. Each job still receives its own verified delivery artifact and ZIP. A shared final assembler rechecks collisions and immutable hashes, then reopens the ZIP to verify every entry before READY. Acquisition or assembly failures are terminal and never echo the source URL.

packageFiles is all-or-nothing: if any source expires, cannot be fetched, exceeds a limit, changes from its expected hash, has a mismatched type, fails structural or safety validation, or conflicts with a generated path, the job ends in FAILED and no partial package reaches READY. Prefer short-lived, narrowly scoped download URLs that remain valid long enough for the first worker acquisition. Do not put reusable API keys or other long-lived secrets in a URL. By requesting inclusion, the client represents that it is authorized to distribute the supplied file and accepts responsibility for its terms. CADDIE.100 safety-validates supplied PDFs but does not treat them as authored, endorsed, licensed, or dimensionally print-preflighted CADDIE.100 output.

A raw design produces the one file named by design.output, plus its verified delivery ZIP and proof. Its nested design.course.id must exactly match the course ID in the endpoint path. A single raw design may contain the greens and scorecard family or the yardage-book family; it cannot mix those renderer families in one output. Only course holes referenced by the design are required at admission. Component render contracts and the geometry, measurement, and analysis resources they require are validated before the job is queued.

custom-design-standard-set@1 accepts one reusable 4.25 x 6.875 in logical-page definition and one or two PDF outputs. The registered letter-left profile saddle-stitches the pages onto 11 x 8.5-inch landscape Letter stock; legal-top saddle-stitches the same pages onto 8.5 x 14-inch portrait Legal stock. The worker compiles the definition once per requested profile, renders and persists every PDF side as a bounded progress unit, merges each document, independently preflights both PDFs, and places them under Prints/Letter and Prints/Legal in one verified ZIP. Optional packageFiles are added by the same safety-checked package pipeline.

For an existing Pennsauken-style caddie100-single-print-job@2, move the authored design.pages into definition.pages and keep each page's ID, background, component frames, and props unchanged. Do not send the old physical design.sheets, stock, binding, placements, or design.output; those are now deterministic compiler outputs. Remove compiler-generated blank pages—the package compiler restores any required saddle-stitch padding. Future ParTC requests should build the logical definition directly instead of compiling one physical PDF first.

// Pennsauken migration: keep logical pages; remove compiled sheets/output.
const previous = pennsaukenRequest.design; // caddie100-single-print-job@2

const packageRequest = {
  schema: "caddie100-printable-request@1",
  kind: "package",
  identifier: pennsaukenRequest.identifier, // optional client/order association
  package: { slug: "custom-design-standard-set", version: 1 },
  definition: {
    schema: "caddie100-printable-design-definition@1",
    capabilityRevision: "printable-design-definition@1",
    title: previous.document.title,
    unit: "in",
    finishedPage: { width: 4.25, height: 6.875 },
    pages: previous.pages
      .filter((page) => !/^generated-blank-\d+$/.test(page.id))
      .map(({ id, background, components }) => ({ id, background, components })),
  },
  binding: { foldLine: false },
  outputs: [
    { profile: "letter-left", format: "pdf", filename: "pennsauken-custom-yardage-book-letter.pdf", primary: true },
    { profile: "legal-top", format: "pdf", filename: "pennsauken-custom-yardage-book-legal.pdf" },
  ],
  // Omit packageFiles unless at least one additional file is required.
  // packageFiles: [{
  //   path: "Includes/partner-instructions.pdf",
  //   source: { type: "url", url: "https://…", expectedSha256: "sha256:…" },
  // }],
};

The stored greens-one-sheet template remains the easiest path to the folded player format. Every stored Letter and Legal booklet profile finishes at 4.25 x 6.875 in. Letter places two panels side by side on 11 x 8.5-inch landscape stock; Legal stacks two panels on 8.5 x 14-inch portrait stock. Version 1 is the greens-only compatibility layout. Version 2 places nine green diagrams and the matching personal scorecard on both physical sides with the established five-setting interface. Version 3 preserves that duplex geometry while applying one normalized Smart Component treatment to all 18 hole-specific greens. Version 4 is the frozen first machine-readable scorecard treatment. Version 5 introduced the corrected minimal-table perimeter, GIR Approach Shot, FT/BT totals, and independently scannable M9H1GG/M9H10GG scorecard pages. Version 6 is the current default and adds optional generic Image slots in the two non-processable footer frames without changing that scorecard capture contract. The stored greens-one-sheet-standard-set package recipe is available in versions 1 through 6 and creates the verified Letter and Legal PDF/PNG set.

For v6, provide optional Image props under settings.images.front-nine-footer-image and settings.images.back-nine-footer-image. Omit either slot to leave that fixed footer frame blank. The existing flat visual settings remain accepted and are migrated to the normalized graphic resource, but may not be mixed with settings.graphic.

smart-minimal-18-sheet@5 is a reusable, course-agnostic four-page scorecard: front cover; Round Details with holes 1–9 (M18F); holes 10–18 with Round Summary and Penalty Counter (M18B); and back cover. Each processable interior page has its own four-marker identity. The covers are print-only Image components and do not participate in scorecard processing.

Omit settings.images, leave a cover slot out, or send the slot as null to use the bundled default. To customize a cover, provide normal Image props at settings.images.front-cover-image or settings.images.back-cover-image. The smart-minimal-18-standard-set@5 recipe produces Letter and Legal duplex PDFs plus 300-DPI front/back PNG proofs. The Legal profile automatically rotates only the upper outside back cover 180 degrees for top-edge folding; submit both source images upright and do not pre-rotate them.

curl -X POST "https://caddie100.com/api/v1/courses/COURSE_ID/printables" \
  -H "x-api-key: c100_live_YOUR_PRIVATE_KEY" \
  -H "Idempotency-Key: open-round-scorecard-42" \
  -H "content-type: application/json" \
  -d '{
    "kind": "package",
    "package": { "slug": "smart-minimal-18-standard-set", "version": 5 },
    "binding": { "foldEdge": "left", "foldLine": true },
    "settings": { "images": {} }
  }'

minimal-smart-scorecard-journal-10@1 is a course-independent, 28-page journal: default front/back covers, a default ten-round ledger on page three, four optional interior Image overrides, and ten pairs of the same M18F/M18B scorecards. Submit it to POST /api/v1/printables/journals, without a course ID. The job descriptor uses an internal open-round-journal scope for compatibility with job storage; no course number is printed or encoded in the markers. Only the front and back covers use bundled image defaults. Omitted or null interior Image slots stay blank except title-page-image, which leaves the default ledger in place; supplying that image explicitly replaces the ledger. The six optional slots are front-cover-image, inside-front-cover-image, title-page-image, ending-page-image, inside-back-cover-image, and back-cover-image. The package contains a 14-side duplex PDF for seven sheets of the selected Letter or Legal stock; the ten scorecards remain independently processable. Supply cover imagery upright; the compositor handles fold orientation.

curl -X POST "https://caddie100.com/api/v1/printables/journals" \
  -H "x-api-key: c100_live_YOUR_PRIVATE_KEY" \
  -H "Idempotency-Key: ten-round-journal-42" \
  -H "content-type: application/json" \
  -d '{
    "template": { "slug": "minimal-smart-scorecard-journal-10", "version": 1 },
    "binding": { "foldEdge": "left", "foldLine": true },
    "settings": { "images": {} }
  }'

identifier can associate a job with a client user, order, or application record, but it is never required or assumed to be unique. CADDIE.100 treats it as opaque metadata—not as the printable ID, idempotency key, storage key, log label, filename, manifest value, source value, or license value. It is stored and echoed for the 30-day job-retention period, so use a pseudonymous value. The private identifier-search endpoint can retrieve every retained match. Omit it when the calling integration does not need that association.

{
  "identifier": "optional-client-user-or-order-id",
  "template": { "slug": "greens-one-sheet" }
}
identifierOptional opaque printable text, 1–256 UTF-8 bytes; use a pseudonymous customer or order reference
schemaOptional; defaults to caddie100-printable-request@1
designA complete caddie100-single-print-job@1 or caddie100-single-print-job@2; see the raw-design fixture and OpenAPI component schemas
template.sluggreens-one-sheet, smart-minimal-18-sheet, or minimal-smart-scorecard-journal-10 on the journal endpoint
template.versionOptional. Greens One Sheet defaults to 6 and retains versions 1 through 5. Smart Minimal 18 is currently 5.
binding.foldEdgeleft (default) or top
binding.foldLineBoolean, default false. Adds a thin, light, solid fold line through the center of every physical sheet after the design is composed.
settings.graphicStyletraditional (default), pencil, or colored-pencil. Every style supports the available analysis overlays.
settings.greenMeasurementsBoolean, default true. Hides or shows measurement strokes; depth and width values remain printed.
settings.greenElevationHeatmapBoolean, available for every style. Defaults to true for Traditional and false for Pencil styles.
settings.greenDownhillArrowsBoolean, available for every style and defaulting to false.
settings.gridSpacingYards5, 10, or null (default)
settings.graphic.style.paperThemeVersion 3+: light, dark, transparent, or a six-digit color such as #FFFFFF. This is passed through to the shared Graphics renderer.
settings.graphic.style.graphicStylingVersion 3+: a Pencil- or Colored Pencil-specific partial palette, normalized with the shared Graphics contract and applied to every generated green. Traditional does not accept this property.
settings.graphic.presentation.panelBackgroundVersion 3+: white, soft, stone, transparent, or a six- or eight-digit RGB hex color.
settings.imagesDesign-specific Image slots. The four-page Smart Minimal uses default covers for omitted or null slots. The journal has six Image slots; only its outer covers have bundled defaults and its other four slots remain blank when omitted.
package.sluggreens-one-sheet-standard-set, smart-minimal-18-standard-set, or custom-design-standard-set
package.versionOptional. Greens One Sheet defaults to 6; Smart Minimal 18 is currently 5.
package.outputsStored recipes accept profile/format variants. Custom design packages accept one or two unique letter-left/legal-top PDF descriptors with safe filenames and at most one primary output.
packageFiles1–10 production public-HTTPS PDF, PNG, or JPEG descriptors added relative to the generated ZIP root; send URLs and optional source hashes, not file bytes

Unknown request properties are rejected. Direct Create and Compile request bodies may not exceed 1 MiB. Complex designs may compile once and send the small compiled-design envelope to Create instead of retransmitting the full draft. packageFiles does not upload binary data in that body: each entry is only a destination path, HTTPS source URL, and optional expected source hash. Course-specific requests take their course ID from the URL path; journal requests require none.

Problem details and terminal failures

Recover according to the failure.

Non-success HTTP responses use application/problem+json and include type, title, status, and a stable code. Record the code, HTTP status, printable ID when one exists, and your own request correlation ID. Never log the API key, raw signed URLs, or a sensitive client identifier.

400 · Invalid requestExamples: invalid-json, invalid-idempotency-key, invalid-printable-request, or printable-webhooks-disabled. Correct the input; do not retry it unchanged.
401 / 403 · Access deniedCheck the private key, active client account and organization, and course-printables:create scope. Never fall back to a publishable key.
409 · Conflictidempotency-conflict means the key already represents different input. printable-compilation-hash-mismatch means the reference and request hash differ. printable-job-active means deletion must wait for a terminal state.
410 · Goneprintable-deleted preserves an old idempotency reservation. printable-compilation-expired means compile the current draft again before create.
413 · Request too largeKeep direct Create and Compile bodies at or below 1 MiB and identifier-search bodies under 8 KiB. For an accepted complex draft, create from its compact compilation reference.
422 · Course or component ineligibleUse broad capability details and eligibility.componentIssues to resolve missing course data or an unavailable exact component before compiling and creating again. printable-component-unavailable means Compile identified at least one exact design node that cannot be produced.
429 · Operational rate limitHonor Retry-After. An exact idempotent recovery of a previously accepted create does not consume another usage unit.
503 · Temporarily unavailableHonor Retry-After when supplied and retry with backoff. A failed job lookup does not mean the job was lost. Keep its printable ID; for an uncertain create, preserve the exact request and idempotency key.
FAILED · Terminal jobThe status response includes failureCode. Do not keep polling; retain the printable ID for diagnosis and submit new work only after resolving the cause.
FAILED · Package fileCodes distinguish URL/host/fetch, type, size, hash, structural validation, malware safety, and private storage failures. Correct a deterministic source problem before submitting new work; treat fetch or storage failures as transient only after confirming the source and service are healthy.
Retry policyAutomatically retry only transient transport failures, 429, and 503. Use exponential backoff with jitter, cap attempts, honor Retry-After, and keep the original idempotency key for uncertain creates.
When job-management reads are unavailablePause new creates that depend on immediate ZIP delivery. Resume polling or retrieve the saved job ID after service recovery; do not replace the idempotency key or assume a previously accepted job needs to be created again.

Versioned examples

Build against fixtures that ship with the contract.

These examples use placeholder IDs, domains, hashes, and artifact URLs. They contain no usable credential or artifact capability.

Verified delivery handoff

One organized delivery package.

Every client ZIP keeps delivery simple: HOW TO PRINT.txt and LICENSE.txt are at the root beside the requested output. Stored standard-set recipes place their registered PDFs and PNG proofs under Prints/Letter/ and Prints/Legal/. Custom-design standard sets place the requested Letter and Legal PDFs there without page-level PNG proofs; the primary first-sheet thumbnail is available through the job API instead of inside the ZIP. The ten-round journal packages one 14-side duplex PDF for seven sheets of the selected Letter or Legal stock, with a 300-DPI cover proof. When packageFiles is present, its paths are added beneath this same root without moving the generated files. Custom-design PDFs receive page-count and media-box validation in production, while full pixel-bound and visual checks remain part of renderer qualification and golden testing. Client files receive separate type, structure, malware, and hash safety validation. Final manifest-hash, filename, byte, entry-count, and ZIP CRC checks apply to the whole package before READY. Review metadata, source documents, preflight.json, and checksums.sha256 are intentionally excluded from every client package.

The current production output uses the consumer-print profile: RGB output without PDF/X conversion, printer-specific CMYK conversion, bleed, or crop marks. Print at 100% or Actual Size; do not use Fit, Shrink, Scale to Page, or Auto Rotate. Letter landscape standard sets flip on the short edge. Follow each package's HOW TO PRINT.txt for Legal: Greens One Sheet uses its established long-edge workflow, while the four-page Smart Minimal booklet flips on the short edge so opening the cover upward places M18F on top and M18B on the bottom. Smart Minimal's upper Legal back-cover panel is intentionally upside down on the flat outside proof, while the lower front cover remains upright. Fold the top half behind the bottom half; do not rotate the PDF in the print dialog.

01Keep the private key in a secret manager and grant only course-printables:create.
02Persist the idempotency key, printable ID, and status URL before handing work to your polling process.
03Bound polling, honor Retry-After, support cancellation, and alert on terminal failures.
04Protect signed links, review both output profiles, retain only what you need, and use the delete endpoint for early removal.