Docs

Full API reference for Shipyard — endpoints, authentication, request formats, and example payloads.

Shipyard Developer Docs

Shipyard API Reference

The Shipyard API gives you direct access to products, posts, requests, assets, and analytics. All write routes require a bearer token. Read routes for public data are open. Every endpoint below is documented with request shapes, field descriptions, and example payloads.

Production Base URL
https://app.startshipyard.com
Auth Header
Authorization: Bearer <SERVICE_API_KEY>
Public Vars
TURNSTILE_SITE_KEY for the promo-code claim, request, and waitlist widgets

Automate your workflow

The API is designed to integrate with your existing development tools. Pull approved roadmap items into your IDE, generate tasks from community votes, pipe analytics into your own dashboards, or connect AI tools to the hosted planner MCP endpoint.

Example: Pull all approved roadmap items via GET /v1/roadmap, filter by status, and feed them into your IDE or task runner to scaffold implementation work automatically.
MCP: Connect Shipyard to any MCP-compatible tool so your AI assistant can read planner work, create future features, update AI workflow status, and manage checklist tasks.

Auth and verification

Write routes and privileged reads require a bearer token in the Authorization header. Public read routes for products, posts, and public roadmap data do not require auth. Desktop apps can call GET /v1/settings/api-key/current to check when the current database-backed service key expires.

Secret: CLERK_SECRET_KEY for /admin authentication
Secret: CLERK_PUBLISHABLE_KEY for hosted Clerk sign-in redirects
Secret: Database-backed service API keys managed from the admin settings page
Secret: API_TOKEN as a deprecated local/development/test token that production and staging reject
Secret: SESSION_SECRET for signed mobile tokens and promo protection
Secret: TURNSTILE_SECRET_KEY for promo-code claim, public request, and waitlist verification
Key expiration: Use expiresAt, expiresInDays, and rotationRecommended from /v1/settings/api-key/current to warn users before local integrations need a regenerated key.
Verified against the live Hono route handlers in src/routes/api.
Documented admin-created API keys for MCP and internal automation.
Documented Clerk as the sole admin sign-in path.
Documented the sourceSlug alias accepted by asset, post, and request creation routes.
Documented waitlist signup, invite, and bearer-authenticated management routes.
Clarified that private products, unpublished articles, non-published product-post reads, and draft metric reads require bearer auth.
Documented the hosted /mcp planner endpoint, planner-specific service-key scopes, product allowlists, audit logging, and internal AI work controls for planner items.
Promoted /v1 as the stable integration prefix while keeping /api as a compatibility alias.
Reference

Authentication

Service API key metadata for desktop apps, local tools, and trusted integrations.

GET /v1/settings/api-key/current
Bearer auth

Return metadata for the service API key used on this request.

  • Works with any valid database-backed service API key, including api:* and planner:* scoped keys.
  • This endpoint introspects only the current bearer key and never returns the raw secret.
  • Use expiresAt, expiresInSeconds, expiresInDays, and rotationRecommended to warn users before local apps lose API access.
  • rotationRecommended becomes true during the final 14 days before expiration.
  • Legacy API_TOKEN env-var tokens are not database-backed, have no expiration metadata, and are rejected in production and staging.
Request
curl "$BASE_URL/v1/settings/api-key/current" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
Response
{
  "ok": true,
  "serverTime": "2026-06-22T12:00:00.000Z",
  "apiKey": {
    "id": "jwk_shipcapsule_ab12cd34",
    "name": "ShipCapsule",
    "scopes": ["api:write"],
    "allowedProductSlugs": ["markettrail"],
    "status": "active",
    "createdAt": "2026-06-22T12:00:00.000Z",
    "expiresAt": "2026-09-20T12:00:00.000Z",
    "expiresInSeconds": 7776000,
    "expiresInDays": 90,
    "rotationRecommended": false,
    "rotationRecommendedAt": "2026-09-06T12:00:00.000Z",
    "lastUsedAt": null,
    "lastUsedPath": null,
    "lastUsedMethod": null,
    "usageCount": 0
  }
}
Reference

Planner MCP

Hosted MCP access for AI clients that need to read planner work, create future features, manage checklist tasks, and mark selected work as testing needed.

GET /mcp
Bearer auth

Return basic MCP endpoint metadata.

  • Requires a database-backed service API key with a planner scope, such as planner:read, planner:write, planner:ai-status, planner:tasks, or planner:create.
  • Generic api:read/api:write keys are not accepted for MCP. Legacy api:full is not user-creatable and never implies dedicated sensitive scopes.
  • Admin-created service keys can be restricted to comma-separated product slugs.
  • This is useful as a quick authenticated health check for MCP clients and setup scripts.
  • The MCP endpoint is hosted on the same Worker as the app and resolves workspace context the same way the stable API does.
Request
curl "$BASE_URL/mcp" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
Response
{
  "name": "shipyard-planner",
  "version": "1.0.0",
  "transport": "streamable-http",
  "endpoint": "/mcp"
}
POST /mcp
Bearer auth

Call the hosted planner MCP server over JSON-RPC.

  • Reads require planner:read or a planner mutation scope that implies read.
  • create_planner_item requires planner:create or planner:write.
  • set_ai_work_status and mark_testing_needed require planner:ai-status or planner:write.
  • Planner task mutations require planner:tasks or planner:write.
  • Supported MCP methods: initialize, ping, tools/list, tools/call, and notifications/initialized.
  • MCP returns curated planner/product fields instead of raw admin database rows.
  • MCP writes are recorded in the operator audit log with service key identity, target id, changed fields, IP, and user agent.
  • MCP rejects obvious credentials in AI context, notes, summaries, and task titles; existing context is redacted on MCP reads when it matches common secret patterns.
  • MCP enforces conservative AI status transitions. AI cannot move do_not_touch (shown as No AI Action in admin) directly to ready.
  • AI work status is internal planner state and never changes public roadmap status by itself.
  • AI work statuses: do_not_touch, clarification_needed, ready, in_progress, testing_needed, blocked.
  • AI clarification requests are stored separately from human-authored aiWorkContext so AI questions do not overwrite instructions.
  • do_not_touch is shown as No AI Action in admin and is the default so existing planner items are not AI-workable by accident.
  • list_ai_work intentionally includes No AI Action/do_not_touch items so AI clients can show the full app planner while treating those items as non-selectable.
  • Available tools: list_products, get_product_planner, list_ai_work, create_planner_item, update_planner_item, set_ai_work_status, request_clarification, mark_testing_needed, list_planner_tasks, create_planner_task, update_planner_task, delete_planner_task.
JSON Body
Field Type Required Description
jsonrpc 2.0 Yes JSON-RPC version.
id string | number No Request id. Omit only for notifications.
method initialize | ping | tools/list | tools/call Yes MCP/JSON-RPC method.
params object No Method-specific params. tools/call expects name and arguments.
Request
curl -X POST "$BASE_URL/mcp" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "list_ai_work",
      "arguments": {
        "productSlug": "atlas"
      }
    }
  }'
Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{ ...planner payload... }"
      }
    ],
    "structuredContent": {
      "product": { "slug": "atlas" },
      "grouped": [
        { "status": "do_not_touch", "label": "No AI Action", "items": [] },
        { "status": "ready", "label": "Ready for AI", "items": [] },
        { "status": "clarification_needed", "label": "Clarification Needed", "items": [] },
        { "status": "testing_needed", "label": "Testing Needed", "items": [] }
      ]
    }
  }
}
POST /mcp (mark_testing_needed)
Bearer auth

Mark selected AI work as testing needed after implementation.

  • This is a tools/call request to /mcp, not a separate HTTP path.
  • Requires planner:ai-status or planner:write.
  • Updates aiWorkStatus to testing_needed and appends implementation/testing notes to aiWorkContext.
  • Allowed only from AI workflow states that can transition to testing_needed, such as in_progress.
  • Does not change request.status or product.workingVersionStatus.
JSON Body
Field Type Required Description
itemId string Yes Planner item id, passed inside params.arguments.
summary string No What changed.
testingNotes string No What the human/tester should verify.
Request
curl -X POST "$BASE_URL/mcp" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "mark_testing_needed",
      "arguments": {
        "itemId": "req_123",
        "summary": "Added compact dashboard layout and wired existing summary data.",
        "testingNotes": "Verify dashboard cards on mobile, desktop, and empty metrics."
      }
    }
  }'
Reference

Analytics

Authenticated Apple Connect sales summaries plus Cloudflare web analytics for admin and native/internal clients.

GET /v1/analytics
Bearer auth

Return the Apple Connect analytics dashboard payload used by the admin area.

  • This route requires bearer auth for scripts and native clients. Admin sessions may also access it when signed in.
  • The payload includes studio metadata, app filters, summary cards, trend data, top apps, recent breakdowns, and sync runs.
Query Parameters
Field Type Required Description
days 7 | 30 | 90 | 180 No Reporting window. Defaults to 30.
productId string | all No Specific app product id or all.
view day | week | month No Trend bucket size.
activity download | paid | iap | purchase | update | redownload | all No Activity class filter.
Request
curl "$BASE_URL/v1/analytics?days=30&view=day&activity=download" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
GET /v1/analytics/web
Bearer auth

Return Cloudflare edge analytics (visits, bandwidth, top paths) for the current workspace hostname.

  • Requires CLOUDFLARE_API_TOKEN or CF_API_TOKEN on the Worker with Cloudflare zone analytics read access. CLOUDFLARE_ZONE_ID is optional (when omitted, Shipyard will attempt to resolve the active zone for the selected hostname).
  • The workspace hostname list is derived server-side from the workspace slug + any active custom domain, including apex and www variants.
  • If hostname is supplied, it must match one of the allowed hostnames for this workspace.
  • Shared-host API clients should include ws=<workspaceSlug> so the request resolves the intended workspace.
  • The payload includes configured, available, error, hostname, days, totals, trend, topPaths, allowedHostnames, selectedHostname, cached, refreshedAt, and expiresAt.
  • totals.uniqueVisitors is a best-effort period unique-IP count; trend[].uniqueVisitors is daily bucket-level. Both may be null when Cloudflare does not expose the metric for the current token/schema.
Query Parameters
Field Type Required Description
days 7 | 30 No Reporting window. Defaults to 7.
hostname string No Optional hostname selector (must be allowed for this workspace).
ws string No Optional workspace slug override for shared-host API clients.
refresh 0 | 1 No Set to 1 to bypass the server-side cache.
Request
curl "$BASE_URL/v1/analytics/web?ws=acme-studio&hostname=www.acme.example&days=30" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
Reference

Mobile Engagement

ShipyardKit Ask, announcement, Roadmap pull, and engagement count endpoints.

GET /v1/engagement/updates
Bearer auth

Return current Ask prompts and announcements for a mobile app.

  • Requires a mobile feedback token from POST /v1/auth/mobile/public-session.
  • Pass product=:slug.
  • Returns asks and announcements. Legacy response keys remain for older ShipyardKit clients.
  • Add history=1 to include non-live engagement items.
  • Records mobile app activity and increments pull counts used by admin analytics.
Query Parameters
Field Type Required Description
product string Yes Product slug.
history 0 | 1 No Include non-live items.
Request
curl "$BASE_URL/v1/engagement/updates?product=atlas" \
  -H "Authorization: Bearer $MOBILE_FEEDBACK_TOKEN"
GET /v1/engagement/asks
Bearer auth

Return mobile Ask prompts only.

  • Requires a mobile feedback token.
  • /v1/engagement/prompts is a compatibility alias.
Query Parameters
Field Type Required Description
product string Yes Product slug.
Request
curl "$BASE_URL/v1/engagement/asks?product=atlas" \
  -H "Authorization: Bearer $MOBILE_FEEDBACK_TOKEN"
GET /v1/engagement/announcements
Bearer auth

Return mobile announcements only.

  • Requires a mobile feedback token.
Query Parameters
Field Type Required Description
product string Yes Product slug.
Request
curl "$BASE_URL/v1/engagement/announcements?product=atlas" \
  -H "Authorization: Bearer $MOBILE_FEEDBACK_TOKEN"
POST /v1/engagement/asks/:id/respond
Bearer auth

Submit a mobile Ask response.

  • Requires a mobile feedback token.
  • Body shape depends on prompt type.
JSON Body
Field Type Required Description
optionId string No Single-choice option id.
optionIds string[] No Multi-choice option ids.
ratingValue number No Star or numeric rating value.
textValue string No Open-text response.
POST /v1/engagement/announcements/:id/events
Bearer auth

Record a mobile announcement event.

  • Requires a mobile feedback token.
JSON Body
Field Type Required Description
eventType shown | dismissed | clicked Yes Announcement delivery event.
visibleMs number No Optional visible duration.
screenKey string No Optional client screen identifier.
GET /v1/engagement/counts
Bearer auth

Return engagement totals for native/admin dashboards.

  • Requires a service key with api:read.
  • dailyPulls is the sum of mobile_feedback_daily_activity.pull_count.
  • dailyRoadmapPullRows is the stored product/install/day row count.
  • dailyRoadmapInstallCount is distinct mobile responder tokens for Roadmap pull activity.
Request
curl "$BASE_URL/v1/engagement/counts" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
Reference

Products

Product records, product-scoped post feeds, and public promo-code flows.

GET /v1/products
Public read, bearer for privileged access

List public products, or with auth include private ones.

  • Public callers only receive products with visibility=public.
  • Authenticated callers can also read private products and filter by visibility.
  • Each product includes publicUrl for the live site path and supportUrl when the workspace Support page is enabled.
  • Each product includes top-level planning fields plus a planning object with productNumber, workingVersion, workingVersionStatus, and workingVersionProgress.
  • Each product includes latestUpdateAt and daysSinceLastUpdate from published non-FAQ product posts.
  • List responses include updatePriority for app products that are live, in progress, coming soon, beta, or ideas. Live app maintenance uses stale published updates plus synced downloads; stalled working versions and ideas use product/planner activity age.
  • updatePriorityNumber is the numeric queue rank to show in apps. It mirrors updatePriorityRank for older clients.
  • Use includePriority=0, includeLatestUpdates=0, or includeSupport=0 for lightweight product lists. Priority and days-since-update sorts force the enrichment they need.
  • Authenticated callers also receive an appStore sync summary with the synced live version kept separate from workingVersion.
Query Parameters
Field Type Required Description
visibility public | private No Auth only.
type app | web No Optional product type filter.
status string No Optional status filter.
versionStatus string No Optional working version status filter. workingVersionStatus is also accepted.
workingVersion string No Optional exact working version filter.
productNumber string No Optional exact manual product identifier filter.
minProgress number No Minimum manual working-version progress.
maxProgress number No Maximum manual working-version progress.
includePriority 0 | 1 No Defaults to 1. Set to 0 to skip update-priority ranking unless using update_priority sort.
includeLatestUpdates 0 | 1 No Defaults to 1. Set to 0 to skip latestUpdateAt and daysSinceLastUpdate unless required by priority or days-since-update sort.
includeSupport 0 | 1 No Defaults to 1. Set to 0 to skip supportUrl lookup.
sort string No name_asc, name_desc, product_number_asc, product_number_desc, update_priority_asc, update_priority_desc, working_version_asc, working_version_desc, progress_asc, progress_desc, days_since_update_asc, days_since_update_desc, status, version_status, newest, or oldest.
Request
curl "$BASE_URL/v1/products?sort=progress_desc&versionStatus=in_progress" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
GET /v1/products/:slug
Public read, bearer for privileged access

Fetch one product by slug.

  • Private products return 404 unless a bearer token is present.
  • The response includes publicUrl for the live site path and supportUrl when the workspace Support page is enabled.
  • The response includes the same product planning fields, planning object, and update-age fields as the list endpoint.
  • updatePriorityNumber is the numeric queue rank to show in apps. It mirrors updatePriorityRank for older clients.
  • Use includePriority=0, includeLatestUpdates=0, or includeSupport=0 to skip optional enrichment.
  • Authenticated callers can pass includePlanner=1 to include private planner items grouped by working version, future versions, and unassigned backlog.
Query Parameters
Field Type Required Description
includePlanner 0 | 1 No Auth only. Include grouped private planner items for this product.
includePriority 0 | 1 No Defaults to 1. Set to 0 to skip update-priority ranking.
includeLatestUpdates 0 | 1 No Defaults to 1. Set to 0 to skip latestUpdateAt and daysSinceLastUpdate unless priority is included.
includeSupport 0 | 1 No Defaults to 1. Set to 0 to skip supportUrl lookup.
Request
curl "$BASE_URL/v1/products/atlas?includePlanner=1" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
POST /v1/products
Bearer auth

Create a product.

  • Only one studio product is allowed.
  • Slug is generated from name when omitted and must stay unique.
  • If iconKey is provided without iconUrl, the API stores /assets/:iconKey.
JSON Body
Field Type Required Description
name string Yes Human-readable product name.
slug string No Optional custom slug.
type app | web | studio No Product kind.
description string No Optional description.
primaryColor string No Hex brand accent.
secondaryColor string No Hex support color.
templateKey string No Landing template key.
appStoreUrl string No App Store link.
websiteUrl string No Marketing or product URL.
status string No Defaults to active.
visibility public | private No Defaults to private.
productNumber string No Optional manual product identifier.
workingVersion string No Manually tracked next version.
workingVersionStatus string No Manual App Store/release workflow status.
workingVersionProgress number No Manual percent complete from 0 to 100.
platforms string[] No Supported platform labels.
metadata object No String-only metadata bag.
iconKey string No Stored asset key.
iconUrl string No Explicit public icon URL.
iconVariants object No String-only icon variant map.
Request
curl -X POST "$BASE_URL/v1/products" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Relay Notes",
    "slug": "relay-notes",
    "type": "app",
    "description": "Launch notes and private planning in one place.",
    "status": "active",
    "productNumber": "7",
    "workingVersion": "1.2",
    "workingVersionStatus": "in_progress",
    "workingVersionProgress": 40,
    "visibility": "private",
    "platforms": ["iphone", "ipad", "mac"],
    "metadata": { "tagline": "A calmer shipping surface" }
  }'
GET /v1/products/:slug/posts
Public read, bearer for privileged access

List posts for one product.

  • status defaults to published.
  • Any non-published status requires a bearer token.
  • Each returned post includes productSlug, productName, productUrl, and publicUrl.
Query Parameters
Field Type Required Description
status string No Defaults to published.
type string No Optional post type filter.
limit number No Max 100.
offset number No Pagination offset.
Request
curl "$BASE_URL/v1/products/atlas/posts?type=release&limit=10"
GET /v1/products/:slug/promo-code/status
Public

Check whether promo-code claiming is enabled and whether inventory is available.

Request
curl "$BASE_URL/v1/products/atlas/promo-code/status"
Response
{
  "product": {
    "slug": "atlas",
    "name": "Atlas"
  },
  "promoCodes": {
    "enabled": true,
    "available": true,
    "cooldownHours": 24,
    "buttonLabel": "Get Promo Code",
    "description": "Limited App Store promo codes are available for this app.",
    "turnstileRequired": true
  }
}
POST /v1/products/:slug/promo-code/claim
Public

Claim one App Store promo code when the product allows it.

  • Returns 400 when Turnstile verification fails or is missing.
  • Returns 404 when promo claiming is disabled for the product.
  • Returns 409 when no codes remain.
  • Returns 429 during the cooldown window.
JSON Body
Field Type Required Description
turnstileToken string No Accepted for JSON requests.
cf-turnstile-response string No Accepted for form submissions too.
Request
curl -X POST "$BASE_URL/v1/products/atlas/promo-code/claim" \
  -H "Content-Type: application/json" \
  -d '{"turnstileToken":"<CF_TURNSTILE_TOKEN>"}'
Response
{
  "code": "ABCD-1234-EFGH",
  "product": {
    "slug": "atlas",
    "name": "Atlas",
    "appStoreUrl": "https://apps.apple.com/..."
  }
}
PATCH /v1/products/:slug
Bearer auth

Patch product metadata.

JSON Body
Field Type Required Description
name string No Optional updated name.
description string No Optional updated description.
primaryColor string No Updated brand accent.
secondaryColor string No Updated support color.
templateKey string No Updated landing template.
appStoreUrl string No Updated App Store URL.
websiteUrl string No Updated product URL.
status string No Updated status.
visibility string No Updated public/private visibility.
productNumber string No Updated manual product identifier.
workingVersion string No Updated manually tracked next version.
workingVersionStatus string No Updated manual App Store/release workflow status.
workingVersionProgress number No Updated manual percent complete from 0 to 100.
platforms string[] No Updated platforms.
metadata object No Updated string metadata bag.
iconKey string No Updated asset key.
iconVariants object No Updated variant map.
Request
curl -X PATCH "$BASE_URL/v1/products/atlas" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"description":"Updated description","status":"active"}'
Reference

Posts

Release notes, updates, metrics, and post image attachments.

GET /v1/posts
Public read, bearer for privileged access

List posts across the studio or for one product.

  • status defaults to published.
  • Non-published statuses require a bearer token.
  • Each returned post includes productSlug, productName, productUrl, and publicUrl.
Query Parameters
Field Type Required Description
product string No Product slug.
type string No Post type filter.
status string No Defaults to published.
limit number No Max 100, default 50.
offset number No Pagination offset.
Request
curl "$BASE_URL/v1/posts?product=atlas&type=release&limit=5"
GET /v1/posts/:productSlug/:postSlug
Public read, bearer for privileged access

Get one post by the same slug shape used by the public site.

  • Published posts are public.
  • Draft posts are hidden from unauthenticated callers.
  • The response includes both post.publicUrl and product.publicUrl.
GET /v1/posts/:id
Public read, bearer for privileged access

Legacy id-based detail read kept for backward compatibility.

  • Prefer /v1/posts/:productSlug/:postSlug for new clients.
GET /v1/posts/:id/metrics
Public read, bearer for privileged access

Read just the metrics for one post.

  • Draft post metrics require bearer auth.
Request
curl "$BASE_URL/v1/posts/post_abc123/metrics"
POST /v1/posts
Bearer auth

Create a post.

  • productSlug is the main field, and sourceSlug is accepted as an alias.
JSON Body
Field Type Required Description
productSlug string Yes Target product slug.
sourceSlug string No Alias for productSlug.
type string Yes Post type.
title string Yes Post title.
summary string No Short summary.
body string No Long-form body.
version string No Optional version label.
status string No Defaults to published.
publishedAt string No ISO timestamp.
visibility everywhere | app_only No Visibility surface.
ctaLabel string No Optional CTA label.
ctaUrl string No Optional CTA target.
availabilityStart string No Optional release window start.
availabilityEnd string No Optional release window end.
availabilityLabel string No Optional availability label.
displayConfig object No Display settings for structured layouts.
metrics array No Optional metrics array to seed on create.
images array No Optional image attachment array to seed on create.
Request
curl -X POST "$BASE_URL/v1/posts" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "productSlug": "atlas",
    "type": "release",
    "title": "Atlas 1.1 - Tidal Alerts",
    "summary": "Get notified when conditions hit your saved thresholds.",
    "body": "Full release notes here.",
    "version": "1.1.0",
    "visibility": "everywhere",
    "publishedAt": "2026-03-06T10:00:00Z"
  }'
PATCH /v1/posts/:id
Bearer auth

Patch a post.

JSON Body
Field Type Required Description
type string No Updated post type.
title string No Updated title.
summary string No Updated summary.
body string No Updated body.
version string No Updated version label.
status string No Updated status.
publishedAt string No Updated ISO timestamp.
visibility string No Updated visibility.
ctaLabel string No Updated CTA label.
ctaUrl string No Updated CTA URL.
availabilityStart string No Updated availability start.
availabilityEnd string No Updated availability end.
availabilityLabel string No Updated availability label.
displayConfig object No Updated display config.
DELETE /v1/posts/:id
Bearer auth

Delete a post.

POST /v1/posts/:id/metrics
Bearer auth

Replace every metric row for a post.

JSON Body
Field Type Required Description
metrics array Yes Full replacement array.
Request
curl -X POST "$BASE_URL/v1/posts/post_abc123/metrics" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"metrics":[{"label":"Downloads","value":5000},{"label":"MAU","value":1204}]}'
POST /v1/posts/:id/images
Bearer auth

Replace every image attachment for a post.

JSON Body
Field Type Required Description
images array Yes Full replacement array of asset references.
Request
curl -X POST "$BASE_URL/v1/posts/post_abc123/images" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "images": [
      { "assetId": "asset_1", "platform": "iphone", "display": "feature", "caption": "Hero screen" },
      { "assetId": "asset_2", "platform": "ipad", "display": "gallery" }
    ]
  }'
Reference

Requests and Planner

Public roadmap items and private internal planning all share one request model.

GET /v1/requests
Public read, bearer for privileged access

List public roadmap items or, with auth, private planner items too.

  • Public reads only include visibility=public.
  • Public reads hide waiting_review and archived by default.
  • Authenticated reads can filter by visibility and origin.
  • ShipyardKit Roadmap screens should render cachedItems() first, then call normal pullRoadmapDaily() and update visible groups only when fresh content is returned. Ordinary opens must not use force: true.
Query Parameters
Field Type Required Description
product string No Product slug.
status string No Status filter.
visibility public | private No Auth only.
origin user | internal No Auth only.
Request
curl "$BASE_URL/v1/requests?product=atlas&status=open"
POST /v1/requests
Public create, bearer for privileged fields

Create a public user request or, with auth, an internal planner item.

  • Public callers create waiting_review user requests.
  • Public request creation is intended for first-party browser flows and enforces same-origin checks.
  • When Turnstile is configured, public callers must include a valid turnstileToken or cf-turnstile-response value.
  • Anonymous mobile apps can submit by using a scoped bearer token from POST /v1/auth/mobile/public-session.
  • Authenticated callers can create private/internal planner items directly.
  • Public callers must provide productSlug or sourceSlug so the request can resolve to a product.
  • sourceSlug is accepted as an alias for productSlug.
JSON Body
Field Type Required Description
productSlug string No Preferred product slug field. Required for public callers unless sourceSlug is provided.
sourceSlug string No Alias for productSlug. Required for public callers when productSlug is omitted.
productId string No Useful for authenticated planner writes.
title string Yes Request title.
description string No Optional description.
status string No Auth only.
visibility public | private No Auth only.
origin user | internal No Auth only.
notes string No Auth only.
developerResponse string No Auth only. Public developer response text, preserving line breaks and bullets.
developerResponsePublic boolean No Auth only. When true, exposes developerResponse publicly.
itemType feature | bugfix No Public/mobile callers may choose feature or bugfix; authenticated callers may use planner item types.
releaseVersion string No Auth only.
sortOrder number No Auth only.
targetDate string No Auth only.
productName string No Auth only.
productSlugHint string No Auth only.
linkedPostId string No Auth only.
turnstileToken string No Public/browser flow when Turnstile is enabled.
cf-turnstile-response string No Public/browser alias for Turnstile.
Request
curl -X POST "$BASE_URL/v1/requests" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "productSlug": "atlas",
    "title": "Tighten onboarding spacing",
    "status": "open",
    "visibility": "private",
    "origin": "internal",
    "itemType": "bugfix",
    "releaseVersion": "Next"
  }'
GET /v1/requests/:id
Public read, bearer for privileged access

Read one request or planner item.

  • Private, waiting_review, and archived items are hidden from unauthenticated callers.
Request
curl "$BASE_URL/v1/requests/req_abc123"
POST /v1/requests/bulk
Bearer auth

Bulk-create planner items.

  • Accepts either titles as an array or lines as a newline-delimited string.
JSON Body
Field Type Required Description
titles string[] No One title per item.
lines string No One line per item.
productSlug string No Optional product slug.
productId string No Optional product id.
visibility public | private No Defaults to private.
status string No Defaults to open.
itemType string No Defaults to update.
releaseVersion string No Optional release label.
targetDate string No Optional target date.
Request
curl -X POST "$BASE_URL/v1/requests/bulk" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "productSlug": "atlas",
    "visibility": "private",
    "status": "open",
    "itemType": "update",
    "releaseVersion": "Next",
    "lines": "Fix bottom sheet spacing\nTweak roadmap copy\nReview promo button wording"
  }'
PATCH /v1/requests/bulk
Bearer auth

Bulk-update planner or request items by id.

JSON Body
Field Type Required Description
ids string[] Yes Item ids to update.
status string No Bulk status update.
visibility string No Bulk visibility update.
itemType string No Bulk item type update.
releaseVersion string No Bulk release version update.
targetDate string No Bulk target date update.
PATCH /v1/requests/:id
Bearer auth

Patch one request or planner item.

JSON Body
Field Type Required Description
title string No Updated title.
description string No Updated description.
status string No Updated status.
linkedPostId string No Optional related post id.
visibility string No Updated visibility.
origin string No Updated origin.
notes string No Updated internal notes.
developerResponse string No Updated public developer response text.
developerResponsePublic boolean No Publishes or hides the developer response.
itemType string No Updated item type.
releaseVersion string No Updated release version.
sortOrder number No Updated order.
targetDate string No Updated target date.
productName string No Updated product name snapshot.
productSlugHint string No Updated slug hint.
productId string No Updated product link.
GET /v1/requests/:id/tasks
Bearer auth

List checklist tasks for one planner item.

POST /v1/requests/:id/tasks
Bearer auth

Create one checklist task.

JSON Body
Field Type Required Description
title string Yes Task title.
PATCH /v1/requests/:id/tasks/:taskId
Bearer auth

Update one checklist task.

JSON Body
Field Type Required Description
title string No Updated title.
isDone boolean No Completion state.
orderIndex number No Task order.
DELETE /v1/requests/:id/tasks/:taskId
Bearer auth

Delete one checklist task.

POST /v1/requests/:id/vote
Public

Vote or unvote on a public roadmap item.

  • Web clients get a jw_vote cookie.
  • Public browser callers must submit from the same origin and include a valid Turnstile token when Turnstile is configured.
  • Anonymous mobile clients can use a scoped bearer token from POST /v1/auth/mobile/public-session.
  • Native clients can pass voterToken.
  • Send {"unvote": true} to remove an existing vote.
JSON Body
Field Type Required Description
turnstileToken string No Public/browser flow when Turnstile is enabled.
cf-turnstile-response string No Public/browser alias for Turnstile.
voterToken string No Optional native-client token.
unvote boolean No Toggle to remove a vote.
Reference

Assets and Articles

Uploaded files plus help and knowledge-base article reads.

GET /v1/assets
Public

List asset records.

Query Parameters
Field Type Required Description
product string No Product slug.
type string No Asset type filter.
limit number No Max 100.
offset number No Pagination offset.
POST /v1/assets
Bearer auth

Upload an asset with multipart form data.

  • productSlug is the preferred field, and sourceSlug is accepted as an alias.
  • Either productSlug or sourceSlug is required.
Form Fields
Field Type Required Description
file File Yes Binary upload.
productSlug string No Preferred product slug. Required unless sourceSlug is provided.
sourceSlug string No Alias for productSlug. Required when productSlug is omitted.
type string No Asset kind, default cover.
title string No Optional display title.
Request
curl -X POST "$BASE_URL/v1/assets" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -F "file=@./screenshot.png" \
  -F "productSlug=atlas" \
  -F "type=screenshot"
GET /v1/assets/:id
Public

Read one asset record.

GET /v1/articles
Public read, bearer for privileged access

List published articles, or with auth include unpublished ones.

  • Public callers only receive published=true articles.
  • Authenticated callers can filter by published=true or published=false.
Query Parameters
Field Type Required Description
product string No Product slug.
published true | false No Auth only when filtering unpublished content.
limit number No Max 100.
offset number No Pagination offset.
GET /v1/articles/:slug
Public read, bearer for privileged access

Read one article by slug.

  • Unpublished articles return 404 unless a bearer token is present.
Reference

Waitlist

Public waitlist signup plus authenticated signup management and invite generation.

GET /v1/waitlist
Bearer auth

List waitlist signups.

  • Supports filtering by productSlug, productId, and status.
  • Returns count for the current page and totalCount for the full matching result set.
  • Each signup includes productSlug, productName, inviteTargetLabel, inviteDestinationUrl, and inviteUrl when present.
Query Parameters
Field Type Required Description
productSlug string No Filter by product slug.
productId string No Filter by product id.
status new | contacted | invited | converted No Optional status filter.
limit number No Defaults to 100, max 1000.
offset number No Pagination offset.
Request
curl "$BASE_URL/v1/waitlist?productSlug=atlas&status=new&limit=50" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
Response
{
  "signups": [
    {
      "id": "wl_abc123",
      "productId": "prod_atlas",
      "email": "captain@example.com",
      "name": "Captain Nora",
      "status": "invited",
      "source": "api",
      "productSlug": "atlas",
      "productName": "Atlas",
      "inviteTargetLabel": "TestFlight",
      "inviteDestinationUrl": "https://testflight.apple.com/join/abc123",
      "inviteUrl": "https://app.startshipyard.com/waitlist/invite/wlinv_123"
    }
  ],
  "count": 1,
  "totalCount": 14
}
POST /v1/waitlist
Public create, bearer for privileged fields

Create or refresh a product waitlist signup.

  • Public browser submissions are intended for first-party flows and enforce same-origin checks.
  • When Turnstile is configured, public submissions must include a valid turnstileToken or cf-turnstile-response value.
  • Bearer-authenticated callers can create signups without same-origin or Turnstile requirements.
  • Duplicate emails per product are upserted rather than inserted again.
JSON Body
Field Type Required Description
productSlug string Yes Target product slug.
email string Yes Signup email address.
name string No Optional person name.
notes string No Optional freeform notes or custom answer.
company string No Honeypot field. Should stay empty.
turnstileToken string No Public/browser flow when Turnstile is enabled.
cf-turnstile-response string No Public/browser alias for Turnstile.
Request
curl -X POST "$BASE_URL/v1/waitlist" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "productSlug": "atlas",
    "email": "captain@example.com",
    "name": "Captain Nora",
    "notes": "Interested in the beta."
  }'
Response
{
  "ok": true,
  "id": "wl_abc123"
}
POST /v1/waitlist/:id/invite
Bearer auth

Create or refresh an invite link for one signup.

  • Returns 400 when the product does not yet have a public destination such as TestFlight, App Store, website, or public product page.
Request
curl -X POST "$BASE_URL/v1/waitlist/wl_abc123/invite" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
PATCH /v1/waitlist/:id
Bearer auth

Update one waitlist signup status.

JSON Body
Field Type Required Description
status new | contacted | invited | converted Yes Next signup status.
Request
curl -X PATCH "$BASE_URL/v1/waitlist/wl_abc123" \
  -H "Authorization: Bearer $SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"contacted"}'
DELETE /v1/waitlist/:id
Bearer auth

Delete one waitlist signup.

Request
curl -X DELETE "$BASE_URL/v1/waitlist/wl_abc123" \
  -H "Authorization: Bearer $SERVICE_API_KEY"
Reference

Roadmap Feed

A simplified public roadmap surface and authenticated planner grouping powered by the same request model.

GET /v1/roadmap
Public read, bearer for privileged access

List the public roadmap feed or authenticated planner groups.

  • Returns both roadmap and requests keys with the same rows.
  • Only public items are returned by default.
  • waiting_review and archived are hidden by default.
  • Authenticated planning=1 returns private/internal planner fields and hides only archived items by default.
  • grouped=1 returns a planner object grouped by current working version, future versions, and backlog.
Query Parameters
Field Type Required Description
product string No Product slug.
status string No Optional status filter.
visibility public | private | all No Auth only.
planning 0 | 1 No Auth only. Include public and private planner items.
grouped 0 | 1 No Return planner release groups.
group releaseVersion No Alternative way to request release-version grouping.
limit number No Max 100.
offset number No Pagination offset.
Request
curl "$BASE_URL/v1/roadmap?product=atlas&planning=1&visibility=all&grouped=1" \
  -H "Authorization: Bearer $SERVICE_API_KEY"