{"openapi":"3.1.0","info":{"title":"NerdLevelTech Agent API (read-only discovery surfaces)","version":"0.1.0","summary":"Live, unauthenticated read surfaces for AI agents over the bilingual (EN/AR) open-learning catalog.","description":"These endpoints are open (no API key) and rate-limited per IP. They describe the discovery surfaces available today. The tier-gated REST data API with bearer-key auth is in development (Phase AE-C) and is not yet covered here. Citation requires attribution — see /llms.txt and /.well-known/agent.json.","contact":{"name":"NerdLevelTech Agents","email":"agents@nerdleveltech.com","url":"https://nerdleveltech.com/for-agents"},"license":{"name":"Attribution required (see /llms.txt)","url":"https://nerdleveltech.com/llms.txt"}},"servers":[{"url":"https://nerdleveltech.com"}],"tags":[{"name":"discovery","description":"Machine-readable indexes and capability descriptors."},{"name":"models","description":"AI model facts, prices, context windows and provenance links."},{"name":"mcp","description":"Model Context Protocol server."},{"name":"a2a","description":"Agent2Agent (A2A) protocol endpoint and Agent Card."},{"name":"markdown","description":"Markdown twins of posts and guides."},{"name":"signup","description":"Commercial-tier waitlist."}],"paths":{"/posts/index.json":{"get":{"tags":["discovery"],"operationId":"getPostsIndex","summary":"Full blog-post index","description":"Every published post with metadata and bilingual (EN/AR) canonical URLs.","responses":{"200":{"description":"Posts index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostsIndex"}}}}}}},"/courses/index.json":{"get":{"tags":["discovery"],"operationId":"getCoursesIndex","summary":"Full course catalog","description":"Every published course with its modules and their lessons, learning outcomes, the enrollment price in credits (credits_required, 0 = free) and the URL of each language it is served in.","responses":{"200":{"description":"Courses index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoursesIndex"}}}}}}},"/models/index.json":{"get":{"tags":["models","discovery"],"operationId":"getModelsIndex","summary":"AI model index","description":"Current AI models with vendor-sourced API prices (USD per 1M tokens), context and output limits, licence and open-weight status, modalities, parameters, memory, provenance links (maker, pricing, docs, Hugging Face, Ollama), videos, related posts and the editorial picks. Each entry carries a verified date; no benchmark score is published.","responses":{"200":{"description":"Model index.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/models-rss.xml":{"get":{"tags":["models","discovery"],"operationId":"getModelsFeed","summary":"AI model change feed","description":"RSS 2.0 feed of the current AI models, newest first, plus the editorial picks. Each item links its model page and states the price, context window and licence.","responses":{"200":{"description":"Model feed.","content":{"application/rss+xml":{"schema":{"type":"string"}}}}}}},"/.well-known/agent.json":{"get":{"tags":["discovery"],"operationId":"getAgentCard","summary":"Agent capability descriptor","description":"Capability descriptor: endpoints, supported MCP tools, attribution and training policy.","responses":{"200":{"description":"NerdLevelTech capability descriptor (site-specific JSON, schema_version 0.1). Not an A2A protocol Agent Card."}}}},"/api/mcp":{"get":{"tags":["mcp"],"operationId":"getMcpManifest","summary":"MCP discovery manifest","description":"Returns server info, protocol version, and the available tool list. The MCP protocol itself runs over POST (JSON-RPC 2.0).","responses":{"200":{"description":"MCP server manifest.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpManifest"}}}}}},"post":{"tags":["mcp"],"operationId":"callMcp","summary":"MCP JSON-RPC endpoint","description":"JSON-RPC 2.0 over HTTP. Protocol version 2024-11-05. Methods: initialize, tools/list, tools/call. Open (no API key), rate-limited to 10 requests/minute per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"}}}},"responses":{"200":{"description":"JSON-RPC response."},"204":{"description":"Acknowledged notification (no response body)."},"429":{"description":"Rate limit exceeded (per-IP)."}}}},"/.well-known/agent-card.json":{"get":{"tags":["a2a"],"operationId":"getA2aAgentCard","summary":"A2A Agent Card","description":"The A2A protocol Agent Card: the /api/a2a endpoint (A2A 1.0 `supportedInterfaces`, and the 0.3 `protocolVersion`, `url` and `preferredTransport` fields), no authentication, and one skill per read-only MCP tool with the same arguments.","responses":{"200":{"description":"Agent Card.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/a2a":{"get":{"tags":["a2a"],"operationId":"getA2aManifest","summary":"A2A discovery summary","description":"Protocol versions, the Agent Card URL and the skill list. The A2A protocol itself runs over POST (JSON-RPC 2.0).","responses":{"200":{"description":"A2A discovery summary.","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["a2a"],"operationId":"callA2a","summary":"A2A JSON-RPC endpoint","description":"JSON-RPC 2.0 over HTTP, A2A 1.0 and 0.3 (the A2A-Version header picks one; without it the method name does: `SendMessage` is 1.0, `message/send` is 0.3). Send one data part {\"skill\": \"<id>\", \"arguments\": {...}} where <id> is a skill of the Agent Card (the MCP tool names). The reply is a completed Task whose artifact is the tool result; an unknown slug is a failed Task, bad input a JSON-RPC error. No tasks are stored: GetTask answers TaskNotFound, streaming and push notifications are unsupported. Open (no API key), rate-limited to 10 requests/minute per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/A2aRequest"}}}},"responses":{"200":{"description":"JSON-RPC response: a result (a Task) or an error."},"400":{"description":"Invalid JSON, or not a JSON-RPC 2.0 request object (batches are not supported)."},"413":{"description":"Body larger than 64 KiB."},"429":{"description":"Rate limit exceeded (per-IP)."}}}},"/{slug}.md":{"get":{"tags":["markdown"],"operationId":"getEnglishPostMarkdown","summary":"English Markdown twin of a post","description":"The English post as Markdown: a YAML header (title, description, dates, the page url, attribution) and the text. Only published pages have a twin; any other slug is a 404. Served with `Link: <page URL>; rel=\"canonical\"` and `X-Robots-Tag: noindex`: cite the page URL, not this one. Slugs are case-sensitive.","parameters":[{"name":"slug","in":"path","required":true,"description":"The post slug, as in its page URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"}}],"responses":{"200":{"description":"The Markdown document.","headers":{"Link":{"description":"The HTML page this twin mirrors, as rel=\"canonical\".","schema":{"type":"string"}},"X-Robots-Tag":{"description":"noindex","schema":{"type":"string"}},"Content-Language":{"description":"Language of the text: en or ar.","schema":{"type":"string","enum":["en","ar"]}}},"content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No published post has this slug."}}}},"/ar/{slug}.md":{"get":{"tags":["markdown"],"operationId":"getArabicPostMarkdown","summary":"Arabic Markdown twin of a post","description":"The Arabic post as Markdown: a YAML header (title, description, dates, the page url, attribution) and the text. Only published pages have a twin; any other slug is a 404. Served with `Link: <page URL>; rel=\"canonical\"` and `X-Robots-Tag: noindex`: cite the page URL, not this one. The text is the stored Arabic translation; without one it is the English text, marked `language: en` in the header and `Content-Language: en`.","parameters":[{"name":"slug","in":"path","required":true,"description":"The post slug, as in its page URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"}}],"responses":{"200":{"description":"The Markdown document.","headers":{"Link":{"description":"The HTML page this twin mirrors, as rel=\"canonical\".","schema":{"type":"string"}},"X-Robots-Tag":{"description":"noindex","schema":{"type":"string"}},"Content-Language":{"description":"Language of the text: en or ar.","schema":{"type":"string","enum":["en","ar"]}}},"content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No published post has this slug."}}}},"/guides/{slug}.md":{"get":{"tags":["markdown"],"operationId":"getEnglishGuideMarkdown","summary":"English Markdown twin of a guide","description":"The English guide as Markdown: a YAML header (title, description, dates, the page url, attribution) and the text. Only published pages have a twin; any other slug is a 404. Served with `Link: <page URL>; rel=\"canonical\"` and `X-Robots-Tag: noindex`: cite the page URL, not this one. Slugs are case-sensitive.","parameters":[{"name":"slug","in":"path","required":true,"description":"The guide slug, as in its page URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"}}],"responses":{"200":{"description":"The Markdown document.","headers":{"Link":{"description":"The HTML page this twin mirrors, as rel=\"canonical\".","schema":{"type":"string"}},"X-Robots-Tag":{"description":"noindex","schema":{"type":"string"}},"Content-Language":{"description":"Language of the text: en or ar.","schema":{"type":"string","enum":["en","ar"]}}},"content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No published guide has this slug."}}}},"/ar/guides/{slug}.md":{"get":{"tags":["markdown"],"operationId":"getArabicGuideMarkdown","summary":"Arabic Markdown twin of a guide","description":"The Arabic guide as Markdown: a YAML header (title, description, dates, the page url, attribution) and the text. Only published pages have a twin; any other slug is a 404. Served with `Link: <page URL>; rel=\"canonical\"` and `X-Robots-Tag: noindex`: cite the page URL, not this one. The text is the stored Arabic translation; without one it is the English text, marked `language: en` in the header and `Content-Language: en`.","parameters":[{"name":"slug","in":"path","required":true,"description":"The guide slug, as in its page URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"}}],"responses":{"200":{"description":"The Markdown document.","headers":{"Link":{"description":"The HTML page this twin mirrors, as rel=\"canonical\".","schema":{"type":"string"}},"X-Robots-Tag":{"description":"noindex","schema":{"type":"string"}},"Content-Language":{"description":"Language of the text: en or ar.","schema":{"type":"string","enum":["en","ar"]}}},"content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No published guide has this slug."}}}},"/api/agents/waitlist":{"post":{"tags":["signup"],"operationId":"joinWaitlist","summary":"Join the commercial Agent API waitlist","description":"Register interest in API-key auth and higher-volume tiers (Phase AE-C). 5 requests/hour per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Added to the waitlist.","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","enum":[true]}}}}}},"400":{"description":"Invalid email (code \"invalid_email\"), or an address the signup guard rejects (for example a disposable inbox or a domain with no mail server).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistError"}}}},"405":{"description":"Method not allowed (only POST). The Allow header lists POST.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistError"}}}},"429":{"description":"Rate limit exceeded: 5 requests per hour per IP. Retry after the number of seconds in the Retry-After header (also in retryAfter).","headers":{"Retry-After":{"description":"Seconds until the limit resets.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["error","retryAfter"],"properties":{"error":{"type":"string","example":"Too many requests"},"retryAfter":{"type":"integer","minimum":1,"description":"Seconds until the limit resets."}}}}}},"502":{"description":"The email provider rejected the signup or could not be reached. Nothing was saved; try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistError"}}}},"503":{"description":"The waitlist is not configured on this deployment. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistError"}}}}}}}},"components":{"schemas":{"WaitlistError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"English error message."},"errorAr":{"type":"string","description":"Arabic error message (signup-guard rejections only)."},"code":{"type":"string","description":"Machine-readable reason, when there is one (for example \"invalid_email\")."}}},"PostsIndex":{"type":"object","required":["schema_version","generated_at","base_url","count","posts"],"properties":{"schema_version":{"type":"string"},"generated_at":{"type":"string","format":"date-time"},"base_url":{"type":"string","format":"uri"},"languages_available":{"type":"array","items":{"type":"string"}},"count":{"type":"integer"},"usage_terms":{"type":"string","format":"uri"},"attribution_format":{"type":"string"},"posts":{"type":"array","items":{"$ref":"#/components/schemas/PostIndexEntry"}}}},"PostIndexEntry":{"type":"object","required":["slug","title","date","urls"],"properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"date":{"type":"string"},"category":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}},"featured":{"type":"boolean"},"cover_image":{"type":["string","null"]},"urls":{"$ref":"#/components/schemas/BilingualUrls"}}},"CoursesIndex":{"type":"object","required":["schema_version","generated_at","base_url","count","courses"],"properties":{"schema_version":{"type":"string"},"generated_at":{"type":"string","format":"date-time"},"base_url":{"type":"string","format":"uri"},"languages_available":{"type":"array","items":{"type":"string"}},"count":{"type":"integer"},"usage_terms":{"type":"string","format":"uri"},"attribution_format":{"type":"string"},"courses":{"type":"array","items":{"$ref":"#/components/schemas/CourseIndexEntry"}}}},"CourseIndexEntry":{"type":"object","required":["slug","title","urls"],"properties":{"slug":{"type":"string"},"title":{"$ref":"#/components/schemas/BilingualText"},"description":{"$ref":"#/components/schemas/BilingualText"},"duration_minutes":{"type":["integer","null"]},"difficulty":{"type":["string","null"]},"category":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"learning_outcomes":{"type":"object","properties":{"en":{"type":"array","items":{"type":"string"}},"ar":{"type":"array","items":{"type":"string"}}}},"cover_image":{"type":["string","null"],"description":"course.yaml cover_image; null when the course sets none."},"created_at":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"published_at":{"type":["string","null"]},"is_published":{"type":"boolean"},"is_beta":{"type":"boolean"},"allow_guest_access":{"type":"boolean","description":"true: every lesson is open to read without an account. Otherwise the first lesson is open and the others need a signed-in account and an enrollment."},"credits_required":{"type":"integer","minimum":0,"description":"Credits enrolling costs (course.yaml credits_required); 0 = free."},"course_type":{"type":"string","description":"course.yaml course_type: \"standard\" (default) or \"premium\"."},"module_count":{"type":"integer"},"lesson_count":{"type":"integer"},"modules":{"type":"array","description":"Modules in site order.","items":{"$ref":"#/components/schemas/CourseModuleEntry"}},"languages":{"type":"array","items":{"type":"string","enum":["en","ar"]},"description":"Languages the course page is served in. Arabic-only courses list only \"ar\"."},"urls":{"$ref":"#/components/schemas/BilingualUrls","description":"Canonical URL per served language; a language the course is not served in has no key."}}},"CourseModuleEntry":{"type":"object","required":["slug","directory","lesson_count"],"properties":{"slug":{"type":"string","description":"module.yaml slug, else the directory name. Lesson URLs use the slug without a leading \"NN-\" sort prefix."},"directory":{"type":"string","description":"Module directory name, with its sort prefix (e.g. \"01-what-are-ai-agents\")."},"sort_order":{"type":["integer","null"]},"title":{"$ref":"#/components/schemas/BilingualText"},"description":{"$ref":"#/components/schemas/BilingualText"},"lesson_count":{"type":"integer"},"lessons":{"type":"array","description":"The module lessons in site order. Lesson page: {course url}/learn/{module slug}/{lesson slug}.","items":{"$ref":"#/components/schemas/CourseLessonEntry"}}}},"CourseLessonEntry":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","description":"Lesson slug used in the lesson URL (the file name without its \"NN-\" prefix)."},"file":{"type":"string","description":"Lesson file name without \".md\" (e.g. \"01-from-chatbots-to-agents\")."},"sort_order":{"type":["integer","null"]},"title":{"$ref":"#/components/schemas/BilingualText"}}},"BilingualUrls":{"type":"object","properties":{"en":{"type":"string","format":"uri"},"ar":{"type":"string","format":"uri"}}},"BilingualText":{"type":"object","properties":{"en":{"type":["string","null"]},"ar":{"type":["string","null"]}}},"McpManifest":{"type":"object","properties":{"server":{"type":"object"},"protocol_version":{"type":"string"},"transport":{"type":"string"},"tools":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"A2aRequest":{"type":"object","required":["jsonrpc","id","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","number"]},"method":{"type":"string","enum":["SendMessage","message/send"],"description":"SendMessage in A2A 1.0, message/send in A2A 0.3."},"params":{"type":"object","description":"A2A SendMessageRequest: a `message` whose parts carry the skill call.","properties":{"message":{"type":"object"},"configuration":{"type":"object"},"metadata":{"type":"object"}}}}},"JsonRpcRequest":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","number","null"]},"method":{"type":"string","enum":["initialize","tools/list","tools/call"]},"params":{"type":"object"}}}}},"x-a2a":{"agent_card":"https://nerdleveltech.com/.well-known/agent-card.json","protocol_versions":["1.0","0.3"],"skills":["search_posts","get_post","search_guides","get_guide","search_courses","get_course","get_lesson","list_topics","get_credential_info","search_models","get_model"]},"x-mcp":{"server":{"name":"nerdlevel-learn","version":"0.2.0","description":"NerdLevelTech's MCP server — search and read the bilingual (EN/AR) open-learning catalog and the AI model index. Citation-rigorous tech content on AI/ML, cloud, DevOps, programming, plus vendor-sourced model prices, context windows and licences."},"protocol_version":"2024-11-05","tools":[{"name":"search_posts","description":"Full-text search across NerdLevelTech blog posts. Matches title, description, tags, keywords, and category. Returns results sorted by relevance with URL-resolvable citations. Titles/descriptions are the stored English source; lang localizes the URLs (Arabic titles live on the /ar pages). For fully bilingual results, use search_guides."},{"name":"get_post","description":"Fetch a single post by slug. Returns metadata + canonical URL + optional body excerpt (at most 2000 chars including the continue-reading link). Full content is served at the URL with attribution requirements per /llms.txt. An unknown slug returns isError."},{"name":"search_guides","description":"Full-text search across NerdLevelTech guides (long-form step-by-step tutorials, Layer 1). Bilingual: lang localizes the returned title, description and URLs. Filter by query and category. Returns results sorted by relevance with URL-resolvable citations and the Markdown twin URL."},{"name":"get_guide","description":"Fetch a single guide by slug. Returns bilingual metadata (title and description in the requested language), canonical URL, Markdown twin URL and an optional body excerpt (at most 2000 chars including the continue-reading link). Full content is served at the URL with attribution per /llms.txt. An unknown slug returns isError."},{"name":"search_courses","description":"Search the course catalog. Filter by query, category, and difficulty. Returns courses with module/lesson counts, the enrollment price in credits (credits_required: 0 = free, null when unknown) and canonical URLs."},{"name":"get_course","description":"Fetch a single course by slug, including its module list, lesson counts, learning outcomes and the enrollment price in credits (credits_required: 0 = free, null when unknown). An unknown slug returns isError."},{"name":"get_lesson","description":"Fetch metadata about a course module and, with lesson_slug, a link to that lesson page: titles, lesson counts, links (lesson_url, course_url, continue_url) and the access rules with the enrollment price in credits (credits_required, 0 = free). Never the lesson text, which is served at the URL (Layer 2). An unknown course, module or lesson slug returns isError naming the missing part, with the valid module or lesson slugs."},{"name":"list_topics","description":"Return the topic taxonomy derived from post tags + course tags/categories. Useful for agents that need to discover available content areas."},{"name":"get_credential_info","description":"Return how a learner earns a NerdLevelTech course certificate (live): the requirements (sign in, complete every lesson, pass every module quiz at its passing score, and pass every lab in courses that require labs), pricing (no certificate fee; some courses cost credits to enroll), the certificate number format, the claim page and the public verification URL pattern. Pass course_slug to also get that course title and its claim URL, in the language set by lang."},{"name":"search_models","description":"Search the AI model index: published API prices in USD per 1M tokens, context windows, licences (including whether open weights are downloadable now or only announced), capabilities and editorial picks. Prices are vendor facts, picks are labelled editorial, and no benchmark score is published. lang localizes the tagline and the returned URL; limit caps results (default 10). Every fact carries a verified date; check it before quoting."},{"name":"get_model","description":"Fetch one model by slug or alias: vendor-sourced pricing, context and output limits, licence and open-weight status, modalities, parameters, memory, knowledge cutoff, provenance links (maker, pricing, docs, Hugging Face, Ollama) with what each link substantiates, plus videos, related posts and any editorial pick. lang selects the returned URL; tagline and summary keep both languages. An unknown slug returns isError."}]}}