{"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":"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"}}}}}}},"/.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_courses","get_course","get_lesson","list_topics","get_credential_info"]},"x-mcp":{"server":{"name":"nerdlevel-learn","version":"0.1.0","description":"NerdLevelTech's MCP server — search and read the bilingual (EN/AR) open-learning catalog. Citation-rigorous tech content on AI/ML, cloud, DevOps, programming."},"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."},{"name":"get_post","description":"Fetch a single post by slug. Returns metadata + canonical URL + optional body excerpt (up to 2000 chars). Full content is served at the URL with attribution requirements 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."}]}}