{"version":1,"name":"AI Hero Public API Discovery","baseUrl":"https://www.aihero.dev","formats":{"html":"text/html","markdown":"text/markdown","json":"application/json"},"discovery":{"api":"/api","apiCatalog":"/.well-known/api-catalog","openapi":"/api/openapi.json","courseSyncOpenapi":"/v1/course-sync/openapi.json","sitemap":"/sitemap.xml","sitemapMarkdown":"/sitemap.md","llms":"/llms.txt"},"resources":[{"name":"posts-and-lists","htmlPattern":"/:slug","markdownPattern":"/:slug.md","visibility":"public"},{"name":"workshop-lessons","htmlPattern":"/workshops/:module/:lesson","markdownPattern":"/workshops/:module/:lesson.md","visibility":"public/free"},{"name":"tutorial-lessons","htmlPattern":"/tutorials/:module/:lesson","markdownPattern":"/tutorials/:module/:lesson.md","visibility":"public"},{"name":"workshop-landing-pages","htmlPattern":"/workshops/:module","markdownPattern":"/workshops/:module.md","visibility":"public"},{"name":"products","htmlPattern":"/products/:slug","markdownPattern":"/products/:slug.md","visibility":"public"},{"name":"cohorts","htmlPattern":"/cohorts/:slug","markdownPattern":"/cohorts/:slug.md","visibility":"public"},{"name":"events","htmlPattern":"/events/:slug","markdownPattern":"/events/:slug.md","visibility":"public"},{"name":"skills","htmlPattern":"/skills","markdownPattern":"/skills.md","visibility":"public"},{"name":"ai-coding-dictionary","htmlPattern":"/ai-coding-dictionary","visibility":"public"},{"name":"ai-coding-dictionary-entries","htmlPattern":"/ai-coding-dictionary/:slug","visibility":"public"},{"name":"course-sync-openapi","api":"/v1/course-sync/openapi.json","visibility":"public"},{"name":"search","api":"/api/search?q=:query","visibility":"public"},{"name":"resource-lookup","api":"/api/resources?slugOrId=:slug&type=:type","visibility":"public"}],"authenticated":{"note":"Bearer credentials may be scoped aih_pat_* agent tokens or admin device tokens. A 403 means the credential is valid but excluded from the operation. A 401 usually means invalid, expired, or revoked; legacy content reads may also return 401 when a valid token lacks Content read ability.","capabilities":[{"name":"calendar-attendees","auth":"device token (Authorization: Bearer) with `update` on Content","description":"Manage the Google Calendar guest list for an event — addressed by slug OR id. Add emails the guest a calendar invite; remove emails a cancellation. 409 if the event isn't synced to Google Calendar yet.","endpoints":[{"method":"GET","path":"/api/calendar/attendees?slugOrId=","description":"List the current attendees of an event ({ email, displayName?, responseStatus? }[]). Read-only."},{"method":"POST","path":"/api/calendar/attendees","description":"Add a person to the guest list ({ slugOrId, email }); Google emails them a calendar invite. 409 if already an attendee."},{"method":"DELETE","path":"/api/calendar/attendees","description":"Remove a person from the guest list ({ slugOrId, email }); Google emails them a cancellation. Idempotent — returns { removed: false } if they were not on the list."}]},{"name":"list-membership","auth":"bearer with `update` on Content, or an aih_pat_* token with content:relations","description":"Edit what a list holds — addressed by the list's slug OR id. Sections are ordinary resources: create one with POST /api/resources (type: section), add it here, then nest items under it with parentId. Editing a section's own title or description is PUT /api/resources?id=<sectionId>.","endpoints":[{"method":"POST","path":"/api/lists/<listIdOrSlug>/resources","description":"Add a resource to the list ({ resourceId, parentId?, metadata? }); parentId nests it under that section. Appends after the last sibling. 409 if it is already there."},{"method":"PUT","path":"/api/lists/<listIdOrSlug>/resources","description":"Reorder and/or move items in one transaction ({ items: [{ resourceId, parentId?, position }] }). Omit parentId to reorder in place; pass the list id to pull an item out of a section."},{"method":"DELETE","path":"/api/lists/<listIdOrSlug>/resources?resourceId=","description":"Remove a resource from the list, wherever in the tree it sits. 404 if the list does not hold it."}]}]},"agentTokens":{"whatTheyAre":"Agent tokens are scoped aih_pat_* bearer credentials minted by an admin for non-human callers. Their ability comes only from scopes, never from the owner roles.","docs":"https://www.aihero.dev/api","openapi":"https://www.aihero.dev/api/openapi.json","presentation":{"header":"Authorization: Bearer AGENT_TOKEN","note":"Send the token in the Authorization header. Never put it in a URL or query string."},"tokenKinds":[{"kind":"scoped agent token","shape":"aih_pat_*","use":"Approved scope-derived reads and narrow writes. No PAT can mint, list, or revoke PATs."},{"kind":"admin device token","shape":"opaque bearer token","use":"Administrative abilities, including minting, listing, and revoking the caller’s agent tokens."}],"management":{"requires":"An admin device token with manage all ability. Operations are mint-for-self and owned-token-only.","mint":{"method":"POST","path":"/api/personal-access-tokens","note":"The 201 response returns the complete token exactly once. Store it immediately."},"list":{"method":"GET","path":"/api/personal-access-tokens","note":"Returns safe metadata only; raw tokens and token hashes are never listed."},"revoke":{"method":"DELETE","path":"/api/personal-access-tokens/{id}","note":"Idempotent immediate kill switch for an owned token."},"curl":{"mint":"curl -X POST 'https://www.aihero.dev/api/personal-access-tokens' -H 'Authorization: Bearer ADMIN_DEVICE_TOKEN' -H 'Content-Type: application/json' --data '{\"name\":\"content-reader\",\"scopes\":[\"content:read\"]}'","mintWithExpiry":"curl -X POST 'https://www.aihero.dev/api/personal-access-tokens' -H 'Authorization: Bearer ADMIN_DEVICE_TOKEN' -H 'Content-Type: application/json' --data '{\"name\":\"temporary-content-reader\",\"scopes\":[\"content:read\"],\"expiresAt\":\"2099-01-01T00:00:00Z\"}'","list":"curl 'https://www.aihero.dev/api/personal-access-tokens' -H 'Authorization: Bearer ADMIN_DEVICE_TOKEN'","revoke":"curl -X DELETE 'https://www.aihero.dev/api/personal-access-tokens/TOKEN_ID' -H 'Authorization: Bearer ADMIN_DEVICE_TOKEN'","read":"curl 'https://www.aihero.dev/api/posts' -H 'Authorization: Bearer AGENT_TOKEN'"}},"scopes":[{"name":"content:read","status":"active","grants":"Privileged read of approved CMS content, including draft, unpublished, private, and unlisted content."},{"name":"content:write","status":"active","grants":"Create draft posts and Skills Changelog entries, and update existing draft posts, lessons, and pages. It cannot publish or change relations."},{"name":"content:publish","status":"active","grants":"Publish posts, lessons, and pages, and create published Skills Changelog entries. Pair with content:write for create-and-publish jobs."},{"name":"content:relations","status":"active","grants":"Create tags, attach or detach tags, and attach media relations during supported content writes."},{"name":"media:upload","status":"active","grants":"Create and complete multipart uploads, mint multipart part URLs, and trigger media processing."},{"name":"shortlinks:manage","status":"active","grants":"List and manage shortlinks. Shortlink attribution and click analytics remain admin-only."},{"name":"analytics:read","status":"reserved","grants":"Accepted when minting, but currently grants no endpoint access. Do not assume analytics access."},{"name":"analytics:chat","status":"reserved","grants":"Accepted when minting, but currently grants no endpoint access. Do not assume analytics chat access."}],"privilegedRead":{"scope":"content:read","includes":"Approved posts, lessons, lesson solutions, sanitized resources, product structure, search results, and survey definitions, including draft, unpublished, private, and unlisted content.","excludes":[{"capability":"Unscoped CMS and administrative writes","why":"Write PATs grant only named draft, publish, relation, media, and shortlink operations. Products, surveys, users, purchases, and token administration stay excluded."},{"capability":"Raw video and Mux payloads","why":"They expose playable identifiers and media internals. Use the sanitized content projections instead."},{"capability":"Legacy signed upload URLs","why":"The legacy signed-URL route stays excluded. Multipart upload and processing routes are granted by the media:upload scope."},{"capability":"Support memory","why":"It is a separate data domain and may contain customer context."},{"capability":"Survey and enrollment analytics","why":"They contain responses, commerce aggregates, or personally identifying fields rather than CMS content."}]},"lifecycle":{"expiresAt":"Optional at mint and must be a future RFC 3339 timestamp. Omit it for no automatic expiry.","revocation":"Revoke by token id for an immediate kill switch. Revoked and expired tokens authenticate as invalid.","lastUsedAt":"Successful verification updates lastUsedAt so admins can audit whether a token is active."},"errors":{"401":"The bearer credential is missing, malformed, invalid, expired, or revoked. Some legacy content-read handlers also use 401 when a valid token lacks Content read ability; if the token is known-valid, inspect its scopes before retrying.","403":"The credential is valid but its scopes or abilities exclude this operation. Add only the specific scope named by the route contract; PATs never gain user, purchase, survey-response, support-memory, or PAT-administration access.","docsField":"Auth-related error bodies point back to /api when their existing envelope permits it."}},"nextActions":["Read /api/openapi.json for the complete bearer-auth and per-operation scope contract.","Read /sitemap.md for a markdown-oriented discovery index.","Read /llms.txt for a short operator-oriented summary.","Use explicit .md twins for low-token public content retrieval.","Use /api/search or /api/resources for structured JSON reads."]}