{"openapi":"3.1.0","info":{"title":"Skills + SOUL Marketplace API","version":"1.2.0","description":"REST API for browsing approved agent skills and SOUL markdown assets with anonymous access, IP-based minute limits, and optional API key daily quotas on metered public endpoints."},"servers":[{"url":"/","description":"Current deployment"}],"tags":[{"name":"Skills","description":"Public skill discovery endpoints"},{"name":"Souls","description":"Public SOUL discovery and download endpoints"}],"components":{"securitySchemes":{"BearerApiKey":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"Optional API key from the dashboard, e.g. Bearer sk_live_xxx. Valid API keys raise the per-minute IP limit and still enforce daily quota."}},"schemas":{"Skill":{"type":"object","required":["id","slug","name","description","tags","status","filePath","downloads","createdAt","updatedAt","authorId"],"properties":{"id":{"type":"string","example":"cm0abc123xyz"},"slug":{"type":"string","example":"csv-pipeline"},"name":{"type":"string","example":"CSV Pipeline"},"description":{"type":"string","example":"Process, transform, and analyze CSV datasets."},"category":{"type":["string","null"],"example":"data"},"tags":{"type":"array","items":{"type":"string"},"example":["csv","etl","analysis"]},"status":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"example":"APPROVED"},"filePath":{"type":"string","example":"skills/csv-pipeline/SKILL.md"},"frontmatter":{"description":"Raw frontmatter JSON when available.","nullable":true},"frontmatterRaw":{"type":["string","null"],"example":"name: CSV Pipeline"},"downloads":{"type":"integer","example":128},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"authorId":{"type":"string","example":"user_123"}}},"Soul":{"type":"object","required":["id","slug","name","description","category","status","filePath","downloads","createdAt","updatedAt","authorId"],"properties":{"id":{"type":"string","example":"cm0soul123xyz"},"slug":{"type":"string","example":"writing-mentor"},"name":{"type":"string","example":"Writing Mentor"},"description":{"type":"string","example":"A concise editorial SOUL for structured long-form writing."},"category":{"type":"string","enum":["assistant","workflow","coding","writing","research","roleplay","business","learning"],"example":"writing"},"status":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"example":"APPROVED"},"filePath":{"type":"string","example":"souls/writing-mentor/SOUL.md"},"frontmatter":{"description":"Raw frontmatter JSON when available.","nullable":true},"frontmatterRaw":{"type":["string","null"],"example":"name: Writing Mentor\ndescription: Editorial SOUL\ncategory: writing"},"downloads":{"type":"integer","example":32},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"authorId":{"type":"string","example":"user_123"}}},"SkillSearchResponse":{"type":"object","required":["success","data","page","limit","total","totalPages","filters"],"properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Skill"}},"page":{"type":"integer","example":1},"limit":{"type":"integer","example":24},"total":{"type":"integer","example":42},"totalPages":{"type":"integer","example":2},"filters":{"type":"object","properties":{"q":{"type":["string","null"],"example":"csv"},"category":{"type":["string","null"],"example":"data"},"tag":{"type":["string","null"],"example":"analysis"},"sortBy":{"type":"string","enum":["popular","recent"],"example":"popular"}}},"note":{"type":"string","nullable":true}}},"SoulSearchResponse":{"type":"object","required":["success","data","page","limit","total","totalPages","filters"],"properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Soul"}},"page":{"type":"integer","example":1},"limit":{"type":"integer","example":24},"total":{"type":"integer","example":18},"totalPages":{"type":"integer","example":1},"filters":{"type":"object","properties":{"q":{"type":["string","null"],"example":"mentor"},"category":{"type":["string","null"],"example":"writing"},"sortBy":{"type":"string","enum":["recent","name"],"example":"recent"}}}}},"SoulDetailResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"$ref":"#/components/schemas/Soul"}}},"SoulCategoriesResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"type":"object","required":["value","label"],"properties":{"value":{"type":"string","enum":["assistant","workflow","coding","writing","research","roleplay","business","learning"],"example":"writing"},"label":{"type":"string","example":"写作"}}}}}},"ErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["MINUTE_RATE_LIMIT_EXCEEDED","DAILY_QUOTA_EXCEEDED"],"example":"MINUTE_RATE_LIMIT_EXCEEDED"},"message":{"type":"string","example":"Per-minute IP rate limit exceeded"}}}}}},"parameters":{"Query":{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Optional keyword query. Matches name, description, category, and tags when supported by the endpoint."},"Category":{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Optional category filter."},"Tag":{"name":"tag","in":"query","required":false,"schema":{"type":"string"},"description":"Optional exact tag filter for skills."},"Page":{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1},"description":"1-based page number."},"Limit":{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":24},"description":"Page size. Values above 100 are clamped to 100."},"SkillSortBy":{"name":"sortBy","in":"query","required":false,"schema":{"type":"string","enum":["recent","popular"]},"description":"Skill sort order. `recent` sorts by update time descending. Any other value falls back to popularity."},"SoulSortBy":{"name":"sortBy","in":"query","required":false,"schema":{"type":"string","enum":["recent","name"]},"description":"SOUL sort order. `recent` sorts by update time descending. `name` sorts alphabetically."},"Slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Unique public slug."}},"responses":{"QuotaExceeded":{"description":"Request rejected because the IP minute limit or API key daily quota is exhausted.","headers":{"X-RateLimit-Tier":{"schema":{"type":"string"},"description":"Applied minute-limit tier: `anonymous` or `api-key`."},"X-RateLimit-Minute-Limit":{"schema":{"type":"string"},"description":"Per-minute request limit for the resolved IP tier."},"X-RateLimit-Minute-Remaining":{"schema":{"type":"string"},"description":"Remaining requests in the current minute window for the resolved IP tier."},"X-RateLimit-Minute-Reset":{"schema":{"type":"string"},"description":"Unix timestamp in seconds when the current minute window resets."},"X-RateLimit-Daily-Limit":{"schema":{"type":"string"},"description":"Configured daily quota limit for a valid API key request."},"X-RateLimit-Daily-Remaining":{"schema":{"type":"string"},"description":"Remaining successful requests for the current UTC day on a valid API key request."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFoundText":{"description":"Resource not found or not publicly accessible.","content":{"text/plain":{"schema":{"type":"string","example":"Not found"}}}}}},"paths":{"/api/v1/skills/search":{"get":{"tags":["Skills"],"summary":"Browse approved skills","description":"Browse approved skills with optional keyword, category, tag, paging, and sorting parameters. Anonymous requests are allowed and limited to 25 requests per minute per IP. Valid API keys raise the same IP bucket to 35 requests per minute and still enforce daily quota. Invalid API keys are treated as anonymous requests.","security":[{},{"BearerApiKey":[]}],"parameters":[{"$ref":"#/components/parameters/Query"},{"$ref":"#/components/parameters/Category"},{"$ref":"#/components/parameters/Tag"},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/SkillSortBy"}],"responses":{"200":{"description":"Search result page.","headers":{"X-RateLimit-Tier":{"schema":{"type":"string"},"description":"Applied minute-limit tier: `anonymous` or `api-key`."},"X-RateLimit-Minute-Limit":{"schema":{"type":"string"},"description":"Per-minute request limit for the resolved IP tier."},"X-RateLimit-Minute-Remaining":{"schema":{"type":"string"},"description":"Remaining requests in the current minute window for the resolved IP tier."},"X-RateLimit-Minute-Reset":{"schema":{"type":"string"},"description":"Unix timestamp in seconds when the current minute window resets."},"X-RateLimit-Daily-Limit":{"schema":{"type":"string"},"description":"Configured daily quota limit for a valid API key request."},"X-RateLimit-Daily-Remaining":{"schema":{"type":"string"},"description":"Remaining successful requests for the current UTC day on a valid API key request."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkillSearchResponse"}}}},"429":{"$ref":"#/components/responses/QuotaExceeded"}}}},"/api/v1/skills/ai-search":{"get":{"tags":["Skills"],"summary":"AI-assisted skill search","description":"Experimental alias for search. Anonymous requests are allowed and limited to 25 requests per minute per IP. Valid API keys raise the same IP bucket to 35 requests per minute and still enforce daily quota. Invalid API keys are treated as anonymous requests.","security":[{},{"BearerApiKey":[]}],"parameters":[{"$ref":"#/components/parameters/Query"},{"$ref":"#/components/parameters/Category"},{"$ref":"#/components/parameters/Tag"},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/SkillSortBy"}],"responses":{"200":{"description":"AI search result page.","headers":{"X-RateLimit-Tier":{"schema":{"type":"string"},"description":"Applied minute-limit tier: `anonymous` or `api-key`."},"X-RateLimit-Minute-Limit":{"schema":{"type":"string"},"description":"Per-minute request limit for the resolved IP tier."},"X-RateLimit-Minute-Remaining":{"schema":{"type":"string"},"description":"Remaining requests in the current minute window for the resolved IP tier."},"X-RateLimit-Minute-Reset":{"schema":{"type":"string"},"description":"Unix timestamp in seconds when the current minute window resets."},"X-RateLimit-Daily-Limit":{"schema":{"type":"string"},"description":"Configured daily quota limit for a valid API key request."},"X-RateLimit-Daily-Remaining":{"schema":{"type":"string"},"description":"Remaining successful requests for the current UTC day on a valid API key request."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkillSearchResponse"}}}},"429":{"$ref":"#/components/responses/QuotaExceeded"}}}},"/api/v1/souls/search":{"get":{"tags":["Souls"],"summary":"Browse approved SOUL assets","description":"Browse approved SOUL markdown assets with optional keyword, category, paging, and sorting parameters. Anonymous requests are allowed and limited to 25 requests per minute per IP. Valid API keys raise the same IP bucket to 35 requests per minute and still enforce daily quota. Invalid API keys are treated as anonymous requests.","security":[{},{"BearerApiKey":[]}],"parameters":[{"$ref":"#/components/parameters/Query"},{"$ref":"#/components/parameters/Category"},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/SoulSortBy"}],"responses":{"200":{"description":"SOUL search result page.","headers":{"X-RateLimit-Tier":{"schema":{"type":"string"},"description":"Applied minute-limit tier: `anonymous` or `api-key`."},"X-RateLimit-Minute-Limit":{"schema":{"type":"string"},"description":"Per-minute request limit for the resolved IP tier."},"X-RateLimit-Minute-Remaining":{"schema":{"type":"string"},"description":"Remaining requests in the current minute window for the resolved IP tier."},"X-RateLimit-Minute-Reset":{"schema":{"type":"string"},"description":"Unix timestamp in seconds when the current minute window resets."},"X-RateLimit-Daily-Limit":{"schema":{"type":"string"},"description":"Configured daily quota limit for a valid API key request."},"X-RateLimit-Daily-Remaining":{"schema":{"type":"string"},"description":"Remaining successful requests for the current UTC day on a valid API key request."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoulSearchResponse"}}}},"429":{"$ref":"#/components/responses/QuotaExceeded"}}}},"/api/v1/souls/categories":{"get":{"tags":["Souls"],"summary":"List SOUL categories","description":"Return the fixed top-level category list used by the SOUL marketplace.","responses":{"200":{"description":"SOUL category list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoulCategoriesResponse"}}}}}}},"/api/v1/souls/{slug}":{"get":{"tags":["Souls"],"summary":"Get a SOUL by slug","description":"Return a single SOUL record by slug. Approved SOUL entries are public. Unapproved SOUL entries return 404 unless accessed through the authenticated site owner/admin flow.","parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"description":"SOUL detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoulDetailResponse"}}}},"404":{"description":"Resource not found or not publicly accessible.","content":{"application/json":{"schema":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Not found"}}}}}}}}}}},"/api/v1/souls/{slug}/download":{"get":{"tags":["Souls"],"summary":"Download an approved SOUL markdown file","description":"Download the raw `SOUL.md` content for a public SOUL by slug. Anonymous requests are allowed and limited to 25 requests per minute per IP. Valid API keys raise the same IP bucket to 35 requests per minute and consume `SOUL/DOWNLOAD` daily quota. Unapproved SOUL entries return 404 unless accessed through the authenticated site owner/admin flow.","security":[{},{"BearerApiKey":[]}],"parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"description":"Raw markdown file.","headers":{"X-RateLimit-Tier":{"schema":{"type":"string"},"description":"Applied minute-limit tier: `anonymous` or `api-key`."},"X-RateLimit-Minute-Limit":{"schema":{"type":"string"},"description":"Per-minute request limit for the resolved IP tier."},"X-RateLimit-Minute-Remaining":{"schema":{"type":"string"},"description":"Remaining requests in the current minute window for the resolved IP tier."},"X-RateLimit-Minute-Reset":{"schema":{"type":"string"},"description":"Unix timestamp in seconds when the current minute window resets."},"X-RateLimit-Daily-Limit":{"schema":{"type":"string"},"description":"Configured daily quota limit for a valid API key request."},"X-RateLimit-Daily-Remaining":{"schema":{"type":"string"},"description":"Remaining successful requests for the current UTC day on a valid API key request."},"Content-Disposition":{"schema":{"type":"string"},"description":"Attachment filename, e.g. `writing-mentor.md`."}},"content":{"text/markdown":{"schema":{"type":"string","example":"---\nname: Writing Mentor\ndescription: Editorial SOUL\ncategory: writing\n---\n\nYou are a concise editorial mentor."}}}},"404":{"$ref":"#/components/responses/NotFoundText"},"429":{"$ref":"#/components/responses/QuotaExceeded"}}}}}}