{"openapi":"3.1.0","info":{"title":"Little Wheels Public Content API","version":"1.2.0","summary":"Read-only catalogue data for Little Wheels, a studio making offline, ad-free, one-time-purchase learning apps for children aged 2-6.","description":"Little Wheels publishes its app catalogue, vehicle catalogue and speech curriculum as public JSON so that agents can answer questions about the apps without scraping the marketing site.\n\n**When to use this API**\n\n- Answering \"which toddler apps work offline / have no subscription / collect no data\" — `getAppFeatures` carries pricing, offline behaviour and privacy posture as structured fields.\n- Looking up a vehicle by name or slug, or finding which vehicles practise a given speech sound — `listVehicles`.\n- Looking up which speech sounds (phonemes) a child typically acquires at a given age, and example words for practising them — `getCurriculum`.\n- Subscribing a parent to the email list, with their explicit consent — `subscribeToNewsletter`.\n\n**When not to use it.** There is no user data, no per-child progress, no authentication and no write access beyond the newsletter endpoint. Nothing here is personalised, so nothing here needs a key.\n\n**Access.** No authentication. No API key. No rate limit is enforced, but the read endpoints are static files that change at most daily — cache them rather than polling.\n\n**Content negotiation.** Every HTML page on this site also serves `text/markdown` when asked for it (`Accept: text/markdown`, or the `.md` suffix). See https://littlewheels.app/developers.","contact":{"name":"Little Wheels (Record Creative Co., LLC)","email":"sean@recordcreativeco.com","url":"https://littlewheels.app/contact"},"license":{"name":"CC BY 4.0","url":"https://creativecommons.org/licenses/by/4.0/"},"termsOfService":"https://littlewheels.app/terms"},"servers":[{"url":"https://littlewheels.app","description":"Production"}],"externalDocs":{"description":"Little Wheels developer and agent resources","url":"https://littlewheels.app/developers"},"tags":[{"name":"Discovery","description":"Documents that describe the API itself."},{"name":"Catalogue","description":"Read-only reference data about the apps and their educational content."},{"name":"Newsletter","description":"The one write endpoint: opt-in email subscription."},{"name":"Operations","description":"Service health."}],"paths":{"/openapi.json":{"get":{"operationId":"getOpenApiSpec","tags":["Discovery"],"summary":"Fetch this OpenAPI document","description":"Returns the OpenAPI 3.1 description of every endpoint listed here. Also available at /api/openapi.json. Start here when planning calls against this site.","responses":{"200":{"description":"The OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object","description":"An OpenAPI 3.1 document."}}}}}}},"/api/index.json":{"get":{"operationId":"getApiIndex","tags":["Discovery"],"summary":"List every machine-readable resource this site publishes","description":"A compact index of the API endpoints, the agent-facing text files (llms.txt, agents.md, llms-full.txt), the sitemap, and the LLM tool manifest. Useful as a single first request when an agent has only the domain name.","responses":{"200":{"description":"The resource index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}}}}},"/api/tools.json":{"get":{"operationId":"getToolManifest","tags":["Discovery"],"summary":"Fetch LLM function-calling definitions for these endpoints","description":"The same operations as the OpenAPI document, pre-converted into JSON Schema tool definitions in the shape used by Anthropic and OpenAI function calling, so an agent can register them without running an OpenAPI-to-tool conversion itself.","responses":{"200":{"description":"Tool definitions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolManifest"}}}}}}},"/api/features.json":{"get":{"operationId":"getAppFeatures","tags":["Catalogue"],"summary":"Get the full app catalogue as schema.org JSON-LD","description":"Returns every published Little Wheels app as a schema.org SoftwareApplication graph: name, App Store title, price, offline behaviour, age range, feature list and privacy posture. This is the authoritative answer to pricing, subscription and data-collection questions about these apps.","responses":{"200":{"description":"The app catalogue.","content":{"application/ld+json":{"schema":{"$ref":"#/components/schemas/AppCatalogue"}},"application/json":{"schema":{"$ref":"#/components/schemas/AppCatalogue"}}}}}}},"/api/apps.json":{"get":{"operationId":"getAppIdentity","tags":["Catalogue"],"summary":"Map the Little Wheels brand to its App Store listings","description":"Returns which App Store listing is which Little Wheels app. None of the apps carries the brand name in its App Store title — \"Talk & Listen\" ships as \"Learn to Talk: First Words\" and \"Create & Play\" ships as \"Toddler Art: Coloring Games\" — so match on `appId` or on any entry in `aliases`, never by expecting \"Little Wheels\" in the store listing. Also names the one unreleased app, so it is not mistaken for a purchasable one.","responses":{"200":{"description":"The brand-to-store identity mapping.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppIdentity"}}}}}}},"/api/vehicles.json":{"get":{"operationId":"listVehicles","tags":["Catalogue"],"summary":"List every vehicle in the Talk & Listen catalogue","description":"Returns all vehicles used across the apps and the site, each with a stable id and slug, its display name and emoji, its category, the speech-sound difficulty tier it belongs to, the phonemes it is used to practise, and the vocabulary theme (\"word world\") it anchors, when it has one. The slug is the path segment used by https://littlewheels.app/vehicles/{slug}.","responses":{"200":{"description":"The vehicle catalogue.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Vehicle"}}}}}}}},"/api/curriculum.json":{"get":{"operationId":"getCurriculum","tags":["Catalogue"],"summary":"Get the speech curriculum: phonemes, example words and typical ages","description":"Returns the speech-sound curriculum behind Talk & Listen: each phoneme with its label, a plain description, example practice words, the age range at which it is typically acquired, and a tip for the adult practising with the child.","responses":{"200":{"description":"The curriculum.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Curriculum"}}}}}}},"/api/subscribe":{"post":{"operationId":"subscribeToNewsletter","tags":["Newsletter"],"summary":"Subscribe an email address to the Little Wheels newsletter","description":"Adds an address to the Little Wheels mailing list. Only call this with a person's explicit, informed consent — it signs a real human up for real email. Subscribing an address that is already on the list succeeds and is a no-op.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscribeRequest"}}}},"responses":{"200":{"description":"Subscribed, or already subscribed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscribeResponse"}}}},"400":{"description":"The email address was missing or malformed (`invalid_email`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Wrong HTTP method; this endpoint accepts POST only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The upstream email provider rejected or dropped the request (`upstream_error`). Safe to retry once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Sign-ups are not configured on this deployment (`service_unavailable`). Do not retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health":{"get":{"operationId":"getApiHealth","tags":["Operations"],"summary":"Check that the API is up","description":"Liveness probe. Returns the service status, the current server time, and the process uptime in seconds.","responses":{"200":{"description":"The service is healthy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","required":["error"],"description":"Every non-2xx response from this API uses this envelope. `error.code` is a stable machine-readable string; `error.message` is human-readable and may change wording; `error.resolution` states what the caller should do next.","properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable, machine-readable error identifier. Match on this, never on `message`.","enum":["not_found","method_not_allowed","invalid_request","invalid_email","not_acceptable","service_unavailable","upstream_error","internal_error"]},"message":{"type":"string","description":"Human-readable explanation of what went wrong."},"resolution":{"type":"string","description":"What the caller should do next to succeed."},"status":{"type":"integer","description":"The HTTP status code, repeated in the body for clients that only read the body."},"documentation":{"type":"string","format":"uri","description":"URL of the documentation for this endpoint or error."},"requestId":{"type":"string","description":"Opaque identifier for this request, useful when reporting a problem."}}}}},"ApiIndex":{"type":"object","required":["name","version","resources"],"properties":{"name":{"type":"string","description":"Site name."},"version":{"type":"string","description":"API version this index describes."},"description":{"type":"string","description":"One-line summary of what the site is."},"documentation":{"type":"string","format":"uri","description":"Human-readable developer docs."},"openapi":{"type":"string","format":"uri","description":"URL of the OpenAPI document."},"resources":{"type":"array","description":"Every machine-readable file this site publishes.","items":{"type":"object","required":["name","url","mediaType","description"],"properties":{"name":{"type":"string","description":"Short identifier for the resource."},"url":{"type":"string","format":"uri","description":"Absolute URL."},"mediaType":{"type":"string","description":"Content-Type the URL returns."},"description":{"type":"string","description":"What the resource contains and when to fetch it."}}}}}},"ToolManifest":{"type":"object","required":["version","tools"],"properties":{"version":{"type":"string","description":"API version these tools describe."},"documentation":{"type":"string","format":"uri"},"tools":{"type":"array","description":"Function-calling definitions, one per documented operation.","items":{"type":"object","required":["name","description","input_schema","endpoint"],"properties":{"name":{"type":"string","description":"Unique tool name; matches the OpenAPI operationId."},"description":{"type":"string","description":"What the tool does and when to reach for it."},"input_schema":{"type":"object","description":"JSON Schema for the tool arguments (Anthropic naming)."},"parameters":{"type":"object","description":"The same JSON Schema under the OpenAI-style key, so either SDK can read this file."},"endpoint":{"type":"object","required":["method","url"],"description":"How to actually perform the call.","properties":{"method":{"type":"string","enum":["GET","POST"]},"url":{"type":"string","format":"uri"}}}}}}}},"AppCatalogue":{"type":"object","description":"schema.org JSON-LD describing the studio and every published app. Generated at build time from the same source the site renders from.","required":["@context","@type","name"],"properties":{"@context":{"type":"string","const":"https://schema.org"},"@type":{"type":"string","const":"SoftwareApplication"},"name":{"type":"string","description":"Studio-level application name."},"creator":{"type":"object","description":"The person who builds the apps.","properties":{"@type":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"}}},"applications":{"type":"array","description":"One entry per published app.","items":{"$ref":"#/components/schemas/AppEntry"}}}},"AppEntry":{"type":"object","required":["@type","name"],"properties":{"@type":{"type":"string","const":"SoftwareApplication"},"name":{"type":"string","description":"App name as used on this site."},"alternateName":{"type":"string","description":"App Store title, which differs from the site name."},"applicationCategory":{"type":"string"},"operatingSystem":{"type":"string","description":"Platforms the app runs on."},"description":{"type":"string"},"offers":{"type":"object","description":"Pricing. Every app is free to download with a single one-time unlock; there is no subscription.","properties":{"@type":{"type":"string"},"price":{"type":"string"},"priceCurrency":{"type":"string"},"availability":{"type":"string","format":"uri"},"description":{"type":"string"}}},"featureList":{"type":"array","items":{"type":"string"},"description":"Capabilities of the app."}}},"AppIdentity":{"type":"object","required":["brand","apps"],"description":"Which App Store listing corresponds to which Little Wheels app, and under what other names each is known.","properties":{"meta":{"type":"object","description":"No generatedAt: this document is derived from a static registry, so it changes only when the mapping does.","properties":{"description":{"type":"string"},"publisher":{"type":"string"},"source":{"type":"string","format":"uri"}}},"brand":{"type":"object","required":["name","url"],"properties":{"name":{"type":"string","const":"Little Wheels"},"legalName":{"type":"string"},"url":{"type":"string","format":"uri"},"note":{"type":"string","description":"How to match apps to this brand, given the store titles do not contain it."}}},"apps":{"type":"array","description":"Every released app.","items":{"$ref":"#/components/schemas/AppIdentityEntry"}},"unreleased":{"type":"array","description":"Apps that exist on this site but are not on the App Store. Never present these as purchasable.","items":{"type":"object","properties":{"id":{"type":"string"},"siteName":{"type":"string"},"siteUrl":{"type":"string","format":"uri"},"status":{"type":"string"},"note":{"type":"string"}}}}}},"AppIdentityEntry":{"type":"object","required":["id","appId","appStoreTitle","siteName","appStoreUrl","siteUrl"],"properties":{"id":{"type":"string","description":"Internal key, matches the site path segment."},"appId":{"type":"string","description":"Apple's numeric App ID. The stable identity — slugs change, this does not."},"appStoreTitle":{"type":"string","description":"The exact title shown in the App Store. Contains no brand name."},"siteName":{"type":"string","description":"What the app is called on littlewheels.app."},"appStoreUrl":{"type":"string","format":"uri","description":"Canonical App Store listing."},"siteUrl":{"type":"string","format":"uri","description":"Canonical page on littlewheels.app."},"trackedInstallUrl":{"type":"string","format":"uri","description":"Internal redirect that fronts install clicks. Prefer appStoreUrl when citing."},"platform":{"type":"string","enum":["iOS"]},"formerAppStoreTitles":{"type":"array","items":{"type":"string"},"description":"Titles the app previously shipped under. Still valid ways to refer to it."},"aliases":{"type":"array","items":{"type":"string"},"description":"Every name this app answers to, App Store title first."}}},"Vehicle":{"type":"object","required":["id","name","slug","category"],"properties":{"id":{"type":"string","description":"Stable identifier, e.g. \"vehicle-002\"."},"name":{"type":"string","description":"Display name, e.g. \"Fire Truck\"."},"slug":{"type":"string","description":"URL segment, e.g. \"fire-truck\". The page is /vehicles/{slug}.","pattern":"^[a-z0-9-]+$"},"emoji":{"type":"string","description":"Single emoji used as the vehicle glyph."},"category":{"type":"string","description":"Vehicle category display name, e.g. \"Wee-Ooo Wheels\"."},"tier":{"type":"integer","minimum":1,"maximum":4,"description":"Speech-sound difficulty tier of this vehicle's primary phoneme. 1 is earliest-acquired (/b/ /p/ /m/ /w/), 4 is latest (/r/ /th/ /sh/ /ch/). Absent when the vehicle is not mapped to a practice phoneme."},"practicePhonemes":{"type":"array","items":{"type":"string","description":"Phoneme in slashes, e.g. \"/f/\"."},"description":"Speech sounds this vehicle is used to practise."},"wordWorld":{"type":"object","description":"The vocabulary theme this vehicle anchors, when it has one.","required":["name","category","summary"],"properties":{"name":{"type":"string"},"category":{"type":"string"},"summary":{"type":"string"}}}}},"Curriculum":{"type":"object","required":["meta","phonemes"],"properties":{"meta":{"type":"object","properties":{"generatedAt":{"type":"string","format":"date-time"},"description":{"type":"string"}}},"phonemes":{"type":"object","description":"Map of phoneme key to its curriculum entry.","additionalProperties":{"$ref":"#/components/schemas/Phoneme"}}}},"Phoneme":{"type":"object","required":["label","description","words"],"properties":{"label":{"type":"string","description":"How the sound is written, e.g. \"/a/ (short)\"."},"description":{"type":"string","description":"Plain-language description with an example word."},"words":{"type":"array","items":{"type":"string"},"description":"Practice words containing the sound."},"typicalAge":{"type":"string","description":"Age range at which this sound is typically acquired, e.g. \"2-3 years\"."},"parentTip":{"type":"string","description":"How an adult can practise this sound with a child."}}},"SubscribeRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","maxLength":254,"description":"The address to subscribe. Must be a real address belonging to the person consenting."},"groupId":{"type":"string","description":"Optional MailerLite group to add the subscriber to."},"source":{"type":"string","maxLength":120,"description":"Optional free-text label recording where the sign-up came from."}}},"SubscribeResponse":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean","const":true},"message":{"type":"string","description":"Human-readable confirmation."}}},"Health":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["healthy"]},"timestamp":{"type":"string","format":"date-time"},"uptime":{"type":"number","description":"Process uptime in seconds."}}}}}}