{"openapi":"3.1.0","info":{"title":"MSP Navigator Public API","version":"1.0.0","description":"JSON API over the MSP Navigator software catalog for Managed Service Providers (MSPs). Most operations (published products, active vendors, the category tree, search) are public, read-only, and unauthenticated — no API key is required, every such endpoint is GET, CORS-open, and returns JSON including on error. The /api/v1/me/** and /api/v1/vendor/** operations are the exception: they act on one signed-in user's own account and require a personal API token (see the bearerToken security scheme) created under Settings → Connected agents. Intended for both human developers and AI agents / LLM tool-calling — see each operation's description for when to use it, and its `x-required-scope` for the token scope it needs. Fair use: this is a small catalog app, please cache responses and avoid high-frequency polling.","contact":{"url":"https://www.msp-navigator.com/contact"},"license":{"name":"Proprietary — see Terms of Service","url":"https://www.msp-navigator.com/terms"}},"servers":[{"url":"https://www.msp-navigator.com","description":"Production"}],"security":[],"tags":[{"name":"Meta","description":"API discovery."},{"name":"Products","description":"Software products in the catalog."},{"name":"Vendors","description":"Software vendors (companies) in the catalog."},{"name":"Categories","description":"The product category tree."},{"name":"Search","description":"Unified cross-entity search."},{"name":"Me","description":"The authenticated caller's own profile, MSP company profile, and their company's shared product stack. Requires a personal API token."},{"name":"Vendor","description":"Managing the authenticated caller's own claimed vendor's products. Requires a personal API token with write:vendor."}],"paths":{"/api/v1":{"get":{"operationId":"getApiIndex","summary":"Get the API index","description":"Returns the API's name, version, links to documentation and the OpenAPI spec, and a list of available endpoints. Call this first to discover what this API can do before calling any other endpoint.","tags":["Meta"],"responses":{"200":{"description":"API index.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"description":{"type":"string"},"docs":{"type":"string","format":"uri"},"openapi":{"type":"string","format":"uri"},"endpoints":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"},"description":{"type":"string"}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/products":{"get":{"operationId":"listProducts","summary":"List and search published software products","description":"Use this to browse or search the MSP software catalog: find products by name/tagline text search, filter by category, vendor, PSA integration, or AI/automation capability (hasAi, hasMcp, hasCli, hasApi), and sort or paginate the results. Call this before getProduct when you only have a name or a filter, not a slug. Only PUBLISHED products are returned.","tags":["Products"],"parameters":[{"name":"q","in":"query","description":"Case-insensitive substring search over product name and tagline.","schema":{"type":"string","example":"backup"}},{"name":"category","in":"query","description":"Category slug (see GET /api/v1/categories). Matches products in that category or any of its direct subcategories.","schema":{"type":"string","example":"backup-disaster-recovery"}},{"name":"vendor","in":"query","description":"Vendor slug (see GET /api/v1/vendors) to restrict results to one vendor's products.","schema":{"type":"string","example":"datto"}},{"name":"hasAi","in":"query","description":"Filter to products with (true) or without (false) AI features.","schema":{"type":"boolean"}},{"name":"hasMcp","in":"query","description":"Filter to products that expose (true) or don't expose (false) a Model Context Protocol (MCP) interface.","schema":{"type":"boolean"}},{"name":"hasCli","in":"query","description":"Filter to products with (true) or without (false) a command-line interface.","schema":{"type":"boolean"}},{"name":"hasApi","in":"query","description":"Filter to products with (true) or without (false) a documented public API.","schema":{"type":"boolean"}},{"name":"psa","in":"query","description":"Filter to products integrating with a specific PSA platform.","schema":{"type":"string","enum":["HALO_PSA","CONNECTWISE_PSA","AUTOTASK_PSA"]}},{"name":"sort","in":"query","description":"Sort order: \"name\" (alphabetical, default), \"updated\" (most recently updated first), or \"featured\" (featured products first, then alphabetical).","schema":{"type":"string","enum":["name","updated","featured"],"default":"name"}},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"A page of matching products.","content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProductSummary"}},"meta":{"$ref":"#/components/schemas/Pagination"},"links":{"$ref":"#/components/schemas/PaginationLinks"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/products/{slug}":{"get":{"operationId":"getProduct","summary":"Get full detail for one published product","description":"Use this once you have a product's slug (from listProducts or search) and need its full description, pricing tiers, features, PSA integrations, AI/automation capabilities, and vendor info. Returns 404 if the slug doesn't exist or the product isn't published.","tags":["Products"],"parameters":[{"name":"slug","in":"path","description":"URL-safe slug identifying the product, as returned in its \"slug\" field by the corresponding list endpoint.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The product.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Product"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/vendors":{"get":{"operationId":"listVendors","summary":"List active vendors","description":"Use this to browse or search the vendors (software companies) in the catalog, with their published product counts. Call this before getVendor when you only have a name, not a slug.","tags":["Vendors"],"parameters":[{"name":"q","in":"query","description":"Case-insensitive substring search over vendor name and description.","schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort order: \"name\" (alphabetical, default) or \"products\" (most published products first).","schema":{"type":"string","enum":["name","products"],"default":"name"}},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"A page of matching vendors.","content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorSummary"}},"meta":{"$ref":"#/components/schemas/Pagination"},"links":{"$ref":"#/components/schemas/PaginationLinks"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/vendors/{slug}":{"get":{"operationId":"getVendor","summary":"Get full detail for one vendor","description":"Use this once you have a vendor's slug (from listVendors or search) and need its company profile (headquarters, founding year, employee count, social links), its full list of published products, and its most recent news. Returns 404 if the slug doesn't exist or the vendor isn't active.","tags":["Vendors"],"parameters":[{"name":"slug","in":"path","description":"URL-safe slug identifying the vendor, as returned in its \"slug\" field by the corresponding list endpoint.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The vendor.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Vendor"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/categories":{"get":{"operationId":"listCategories","summary":"Get the full category tree","description":"Use this to see how the catalog is organized: every active top-level category with its direct subcategories and published-product counts. Good for building a menu, or for resolving a free-text category name to a slug before calling listProducts with a category filter.","tags":["Categories"],"responses":{"200":{"description":"The active category tree.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/categories/{slug}":{"get":{"operationId":"getCategory","summary":"Get one category, its children, and its first page of products","description":"Use this once you have a category's slug (from listCategories) and want its description plus a first page of the published products in it (same shape as listProducts). For more than one page, call listProducts with the category filter and your own page/limit.","tags":["Categories"],"parameters":[{"name":"slug","in":"path","description":"URL-safe slug identifying the category, as returned in its \"slug\" field by the corresponding list endpoint.","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","description":"1-based page number for the nested products list.","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","description":"Items per page for the nested products list, up to 100.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"The category.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Category"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/search":{"get":{"operationId":"search","summary":"Unified search across products, vendors, and categories","description":"Use this when you don't know whether a query term refers to a product, a vendor, or a category — it searches all three at once and returns the top matches of each. For exhaustive or filtered product search, prefer listProducts instead. Requires q to be at least 2 characters.","tags":["Search"],"parameters":[{"name":"q","in":"query","description":"Search text, minimum 2 characters.","required":true,"schema":{"type":"string","minLength":2},"example":"connectwise"}],"responses":{"200":{"description":"Top matches across products, vendors, and categories.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/SearchResult"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/me":{"get":{"operationId":"getMe","summary":"Get the authenticated caller's own profile","description":"Use this first when acting with a personal API token: returns the caller's user record, their MSP company (if any), the vendor page they've claimed (if any), and the authenticating token's own name/scopes/expiry. Requires a personal API token (Settings → Connected agents) with at least the \"read\" scope.","tags":["Me"],"security":[{"bearerToken":[]}],"x-required-scope":"read","responses":{"200":{"description":"The caller's profile.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Me"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/me/company":{"get":{"operationId":"getMyCompany","summary":"Get the authenticated caller's MSP company profile","description":"Returns the caller's editable MSP company profile (description, website, headquarters, verticals/regions/languages/certifications, social links). 404 if the user isn't linked to a company — check getMe's \"company\" field first. Requires \"read\".","tags":["Me"],"security":[{"bearerToken":[]}],"x-required-scope":"read","responses":{"200":{"description":"The company profile.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CompanyProfile"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"patch":{"operationId":"updateMyCompany","summary":"Update the authenticated caller's MSP company profile","description":"Partially updates the caller's MSP company profile. name/slug/domain are read-only and rejected if sent. Returns the updated profile plus a `changes` map of only the fields that actually changed (empty object if the request changed nothing). 404 if the user isn't linked to a company. Requires \"write:profile\".","tags":["Me"],"security":[{"bearerToken":[]}],"x-required-scope":"write:profile","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyPatchInput"}}}},"responses":{"200":{"description":"The updated company profile.","content":{"application/json":{"schema":{"type":"object","required":["data","changes"],"properties":{"data":{"$ref":"#/components/schemas/CompanyProfile"},"changes":{"$ref":"#/components/schemas/Changes"}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/me/stack":{"get":{"operationId":"getMyStack","summary":"List the authenticated caller's MSP company's product stack","description":"Returns every product in the caller's MSP company's shared stack — the same stack every user in that company reads and writes, not a per-user list — with each item's status, notes, and isPrimary flag, plus total and per-status counts. 404 if the caller isn't linked to an MSP company. Requires \"read\".","tags":["Me"],"security":[{"bearerToken":[]}],"x-required-scope":"read","responses":{"200":{"description":"The caller's stack.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/StackItem"}},"meta":{"type":"object","required":["total","byStatus"],"properties":{"total":{"type":"integer","minimum":0},"byStatus":{"type":"object","description":"Count of stack items per status, e.g. { \"CURRENT\": 3 }.","additionalProperties":{"type":"integer","minimum":0}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/me/stack/{productSlug}":{"put":{"operationId":"upsertMyStackItem","summary":"Add or update one product in the authenticated caller's company stack","description":"Idempotent upsert into the caller's MSP company's shared stack: creates the entry with the given status/notes/isPrimary if it doesn't exist yet, otherwise replaces it. 404 if the caller isn't linked to an MSP company, or if productSlug doesn't exist or isn't a published product. Requires \"write:stack\".","tags":["Me"],"security":[{"bearerToken":[]}],"x-required-scope":"write:stack","parameters":[{"name":"productSlug","in":"path","description":"The product's slug (see getMyStack's \"product.slug\", or GET /api/v1/products for valid values).","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StackItemInput"}}}},"responses":{"200":{"description":"The stack item already existed and was updated. `changes` lists only the fields that changed (empty object when nothing changed).","content":{"application/json":{"schema":{"type":"object","required":["data","changes","created"],"properties":{"data":{"$ref":"#/components/schemas/StackItem"},"changes":{"type":"object","description":"Per-field diff: { field: { from, to } }.","additionalProperties":{"type":"object","properties":{"from":{},"to":{}}}},"created":{"type":"boolean","enum":[false]}}}}}},"201":{"description":"The product was newly added to the company stack. `changes` has `from: null` for every field.","content":{"application/json":{"schema":{"type":"object","required":["data","changes","created"],"properties":{"data":{"$ref":"#/components/schemas/StackItem"},"changes":{"type":"object","additionalProperties":{"type":"object","properties":{"from":{},"to":{}}}},"created":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"operationId":"deleteMyStackItem","summary":"Remove one product from the authenticated caller's company stack","description":"Idempotent: returns 204 whether the product was in the caller's MSP company's stack, wasn't, or productSlug doesn't exist at all. 404 if the caller isn't linked to an MSP company. Requires \"write:stack\".","tags":["Me"],"security":[{"bearerToken":[]}],"x-required-scope":"write:stack","parameters":[{"name":"productSlug","in":"path","description":"The product's slug (see getMyStack's \"product.slug\", or GET /api/v1/products for valid values).","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed (or already absent) — no response body."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/vendor/products":{"get":{"operationId":"listMyVendorProducts","summary":"List the authenticated vendor's own products","description":"Returns every product belonging to the vendor page the caller has claimed, including unpublished DRAFT/ARCHIVED products — unlike the public listProducts, which only returns PUBLISHED ones. 404 if the caller hasn't claimed a vendor page. Requires \"read\".","tags":["Vendor"],"security":[{"bearerToken":[]}],"x-required-scope":"read","responses":{"200":{"description":"The vendor's products.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorManagedProduct"}},"meta":{"type":"object","required":["total","byStatus"],"properties":{"total":{"type":"integer","minimum":0},"byStatus":{"type":"object","description":"Count of products per status, e.g. { \"PUBLISHED\": 4, \"DRAFT\": 1 }.","additionalProperties":{"type":"integer","minimum":0}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/v1/vendor/products/{slug}":{"patch":{"operationId":"updateMyVendorProduct","summary":"Update one of the authenticated vendor's own products","description":"Partially updates a product belonging to the vendor the caller has claimed: tagline, description, links, pricing, PSA integrations, and AI/automation capability flags. name, slug, status, isFeatured, and vendorId are not editable here. 403 unless the caller's VendorProfile owns this product's vendor and canManageProducts is true. Sets lastEditedBy to VENDOR. Returns the updated product plus a `changes` map. Requires \"write:vendor\".","tags":["Vendor"],"security":[{"bearerToken":[]}],"x-required-scope":"write:vendor","parameters":[{"name":"slug","in":"path","description":"URL-safe slug identifying the product, as returned in its \"slug\" field by the corresponding list endpoint.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorProductPatchInput"}}}},"responses":{"200":{"description":"The updated product.","content":{"application/json":{"schema":{"type":"object","required":["data","changes"],"properties":{"data":{"$ref":"#/components/schemas/VendorManagedProduct"},"changes":{"$ref":"#/components/schemas/Changes"}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}},"components":{"schemas":{"Error":{"type":"object","description":"Standard error envelope returned by every non-2xx response from this API, including unknown routes.","required":["error"],"properties":{"error":{"type":"object","required":["code","message","docs"],"properties":{"code":{"type":"string","description":"Machine-readable error category. unauthorized/forbidden/validation_error/rate_limited are only returned by the token-authenticated /api/v1/me and /api/v1/vendor endpoints.","enum":["bad_request","not_found","internal_error","unauthorized","forbidden","validation_error","rate_limited"]},"message":{"type":"string","description":"Human-readable description of what went wrong."},"hint":{"type":["string","null"],"description":"Optional suggestion for how to fix the request."},"docs":{"type":"string","format":"uri","description":"Link to API documentation.","example":"https://www.msp-navigator.com/developers"},"details":{"type":"array","description":"Only present when code is \"validation_error\": one entry per failed field.","items":{"type":"object","properties":{"path":{"type":"string","description":"Dot-separated field path, or \"(body)\" for a whole-body error."},"message":{"type":"string"}}}}}}}},"Pagination":{"type":"object","description":"Pagination state for a list response.","required":["page","limit","total","totalPages"],"properties":{"page":{"type":"integer","minimum":1,"description":"Current 1-based page number."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Items per page."},"total":{"type":"integer","minimum":0,"description":"Total number of matching items."},"totalPages":{"type":"integer","minimum":0,"description":"Total number of pages."}}},"PaginationLinks":{"type":"object","description":"Navigation links for a paginated list response.","required":["self"],"properties":{"self":{"type":"string","format":"uri","description":"URL of the current page."},"next":{"type":"string","format":"uri","description":"URL of the next page, if any."},"prev":{"type":"string","format":"uri","description":"URL of the previous page, if any."}}},"ProductSummary":{"type":"object","description":"A product as it appears in a list (search results, a products list page, or a category's product list). Use /api/v1/products/{slug} for full detail.","required":["slug","name","vendor","url"],"properties":{"slug":{"type":"string","description":"URL-safe unique identifier, used in GET /api/v1/products/{slug}."},"name":{"type":"string"},"tagline":{"type":["string","null"],"description":"One-line description."},"logoUrl":{"type":["string","null"],"format":"uri"},"pricingModel":{"type":["string","null"],"description":"e.g. \"subscription\", \"one-time\", \"usage-based\", \"free\"."},"startingPrice":{"type":["number","null"],"description":"Lowest advertised price, in pricingCurrency."},"pricingCurrency":{"type":["string","null"],"example":"USD"},"isFeatured":{"type":"boolean"},"capabilities":{"type":"object","description":"AI / automation capability signals detected by the catalog's enrichment agent. Use these to filter or to answer questions like 'does this tool have an MCP server' or 'is there a public API'.","properties":{"hasAiFeatures":{"type":"boolean","description":"Product embeds or supports AI features."},"hasMcpSupport":{"type":"boolean","description":"Product exposes a Model Context Protocol (MCP) interface."},"hasCli":{"type":"boolean","description":"Product has a command-line interface."},"hasPublicApi":{"type":"boolean","description":"Product has a documented public API."},"aiCapabilities":{"type":["string","null"],"description":"Short free-text summary of the product's AI/automation capabilities. Only present on product detail."},"aiCapabilitiesUpdatedAt":{"type":["string","null"],"format":"date-time","description":"When aiCapabilities was last assessed. Only present on product detail."}}},"vendor":{"type":"object","description":"Minimal vendor reference attached to a product.","properties":{"slug":{"type":"string"},"name":{"type":"string"}}},"categories":{"type":"array","items":{"type":"object","description":"Minimal category reference attached to a product.","properties":{"slug":{"type":"string"},"name":{"type":"string"}}}},"url":{"type":"string","format":"uri","description":"Canonical HTML page for this product."},"updatedAt":{"type":["string","null"],"format":"date-time"}}},"Product":{"type":"object","description":"Full detail for a single published product, as returned by GET /api/v1/products/{slug}.","required":["id","slug","name","vendor","url"],"properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"tagline":{"type":["string","null"]},"description":{"type":["string","null"]},"logoUrl":{"type":["string","null"],"format":"uri"},"websiteUrl":{"type":["string","null"],"format":"uri"},"documentationUrl":{"type":["string","null"],"format":"uri"},"pricingUrl":{"type":["string","null"],"format":"uri"},"pricingModel":{"type":["string","null"]},"startingPrice":{"type":["number","null"]},"pricingCurrency":{"type":["string","null"],"example":"USD"},"isFeatured":{"type":"boolean"},"capabilities":{"type":"object","description":"AI / automation capability signals detected by the catalog's enrichment agent. Use these to filter or to answer questions like 'does this tool have an MCP server' or 'is there a public API'.","properties":{"hasAiFeatures":{"type":"boolean","description":"Product embeds or supports AI features."},"hasMcpSupport":{"type":"boolean","description":"Product exposes a Model Context Protocol (MCP) interface."},"hasCli":{"type":"boolean","description":"Product has a command-line interface."},"hasPublicApi":{"type":"boolean","description":"Product has a documented public API."},"aiCapabilities":{"type":["string","null"],"description":"Short free-text summary of the product's AI/automation capabilities. Only present on product detail."},"aiCapabilitiesUpdatedAt":{"type":["string","null"],"format":"date-time","description":"When aiCapabilities was last assessed. Only present on product detail."}}},"psaIntegrations":{"type":"array","description":"PSA platforms this product integrates with.","items":{"type":"string","enum":["HALO_PSA","CONNECTWISE_PSA","AUTOTASK_PSA"]}},"categories":{"type":"array","items":{"allOf":[{"type":"object","description":"Minimal category reference attached to a product.","properties":{"slug":{"type":"string"},"name":{"type":"string"}}},{"type":"object","properties":{"url":{"type":"string","format":"uri"}}}]}},"features":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":["string","null"]}}}},"pricingTiers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","example":"Professional"},"description":{"type":["string","null"]},"price":{"type":["number","null"]},"currency":{"type":"string","example":"USD"},"billingPeriod":{"type":["string","null"],"example":"monthly"},"features":{"type":"array","items":{"type":"string"}},"isPopular":{"type":"boolean"}}}},"vendor":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"websiteUrl":{"type":["string","null"],"format":"uri"}}},"url":{"type":"string","format":"uri"},"updatedAt":{"type":["string","null"],"format":"date-time"}}},"VendorSummary":{"type":"object","description":"A vendor as it appears in a list. Use /api/v1/vendors/{slug} for full detail.","required":["slug","name","url"],"properties":{"slug":{"type":"string"},"name":{"type":"string"},"logoUrl":{"type":["string","null"],"format":"uri"},"productCount":{"type":"integer","minimum":0,"description":"Number of published products from this vendor."},"url":{"type":"string","format":"uri","description":"Canonical HTML page for this vendor."},"updatedAt":{"type":["string","null"],"format":"date-time"}}},"Vendor":{"type":"object","description":"Full detail for a single active vendor, as returned by GET /api/v1/vendors/{slug}.","required":["slug","name","url"],"properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"website":{"type":["string","null"],"format":"uri"},"logoUrl":{"type":["string","null"],"format":"uri"},"headquarters":{"type":["string","null"]},"foundedYear":{"type":["integer","null"]},"employeeCount":{"type":["integer","null"]},"industry":{"type":["string","null"]},"acquiredBy":{"type":["string","null"],"description":"Company that acquired this vendor, if applicable."},"linkedIn":{"type":["string","null"],"format":"uri"},"twitter":{"type":["string","null"],"format":"uri"},"isVerified":{"type":"boolean"},"products":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"tagline":{"type":["string","null"]},"url":{"type":"string","format":"uri"}}}},"recentNews":{"type":"array","description":"Up to 10 most recent published news items about this vendor.","items":{"type":"object","properties":{"title":{"type":"string"},"url":{"type":"string","format":"uri"},"publishedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":["string","null"],"format":"date-time"}}}},"url":{"type":"string","format":"uri"},"updatedAt":{"type":["string","null"],"format":"date-time"}}},"Category":{"type":"object","description":"A category node. GET /api/v1/categories returns the full active tree (top-level categories with their children); GET /api/v1/categories/{slug} returns one category plus its first page of products.","required":["slug","name","url"],"properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"productCount":{"type":"integer","minimum":0},"parent":{"oneOf":[{"type":"null"},{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","format":"uri"}}}],"description":"Only present on GET /api/v1/categories/{slug}; null for top-level categories."},"children":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"productCount":{"type":"integer","minimum":0},"url":{"type":"string","format":"uri"}}}},"products":{"description":"Only present on GET /api/v1/categories/{slug}: the first page of published products in this category, in the same shape as GET /api/v1/products.","type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProductSummary"}},"meta":{"$ref":"#/components/schemas/Pagination"},"links":{"$ref":"#/components/schemas/PaginationLinks"}}},"url":{"type":"string","format":"uri"},"updatedAt":{"type":["string","null"],"format":"date-time"}}},"SearchResult":{"type":"object","properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/ProductSummary"},"description":"Top 10 matching products."},"vendors":{"type":"array","items":{"$ref":"#/components/schemas/VendorSummary"},"description":"Top 5 matching vendors."},"categories":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"productCount":{"type":"integer","minimum":0},"url":{"type":"string","format":"uri"}}},"description":"Top 5 matching categories."}}},"Me":{"type":"object","description":"Response payload for getMe: the caller's own user, company, vendor claim, and token info.","required":["user","company","vendor","token"],"properties":{"user":{"type":"object","description":"The authenticated user — the owner of the ApiToken used to call this API.","required":["id","email","role"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"email":{"type":"string","format":"email"},"image":{"type":["string","null"],"format":"uri"},"role":{"type":"string","enum":["SUPER_ADMIN","VENDOR","MSP","MSP_VERIFIED","MSP_PRODUCER"]},"createdAt":{"type":"string","format":"date-time"}}},"company":{"oneOf":[{"type":"null"},{"type":"object","description":"Minimal MSP company reference embedded in getMe. Call getMyCompany for the full editable profile.","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"domain":{"type":"string"},"website":{"type":["string","null"],"format":"uri"},"isVerified":{"type":"boolean"}}}],"description":"null if the user isn't linked to an MSP company."},"vendor":{"oneOf":[{"type":"null"},{"type":"object","description":"The vendor page this user has claimed, if any.","properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"websiteUrl":{"type":["string","null"],"format":"uri"},"canEditInfo":{"type":"boolean"},"canManageProducts":{"type":"boolean","description":"Must be true for this user's token to call updateMyVendorProduct on this vendor's products."}}}],"description":"null if the user hasn't claimed a vendor page."},"token":{"type":"object","description":"The ApiToken that authenticated this request.","properties":{"name":{"type":"string","description":"User-chosen label, e.g. \"Claude Desktop\"."},"scopes":{"type":"array","items":{"type":"string","description":"\"read\": Read your profile, company and stack. \"write:profile\": Update your user and company profile. \"write:stack\": Add, update and remove products in your company stack. \"write:vendor\": Edit products of the vendor you have claimed.","enum":["read","write:profile","write:stack","write:vendor"]}},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time","description":"null means the token never expires."}}}}},"CompanyProfile":{"type":"object","description":"The caller's MSP company profile — returned by getMyCompany and as the updated \"data\" of updateMyCompany. name/slug/domain are read-only: present here, but rejected if sent to updateMyCompany.","required":["id","slug","name","domain"],"properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"domain":{"type":"string"},"description":{"type":["string","null"]},"website":{"type":["string","null"],"format":"uri"},"headquarters":{"type":["string","null"]},"foundedYear":{"type":["integer","null"]},"employeeCount":{"type":["integer","null"],"minimum":0},"verticals":{"type":"array","items":{"type":"string"}},"regions":{"type":"array","items":{"type":"string"}},"languages":{"type":"array","items":{"type":"string"}},"certifications":{"type":"array","items":{"type":"string"}},"linkedIn":{"type":["string","null"],"format":"uri"},"twitter":{"type":["string","null"],"format":"uri"},"facebook":{"type":["string","null"],"format":"uri"},"isVerified":{"type":"boolean"},"onboardingCompleted":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"CompanyPatchInput":{"type":"object","description":"All fields optional, but at least one is required. Partial update: an omitted field is left unchanged; an explicit null clears a nullable field. name/slug/domain are not accepted here (read-only).","properties":{"description":{"type":["string","null"],"maxLength":5000},"website":{"type":["string","null"],"format":"uri"},"headquarters":{"type":["string","null"],"maxLength":200},"foundedYear":{"type":["integer","null"],"minimum":1900,"description":"1900 through the current year."},"employeeCount":{"type":["integer","null"],"minimum":0},"verticals":{"type":"array","items":{"type":"string","maxLength":80},"maxItems":25,"description":"Up to 25 items, each up to 80 characters."},"regions":{"type":"array","items":{"type":"string","maxLength":80},"maxItems":25,"description":"Up to 25 items, each up to 80 characters."},"languages":{"type":"array","items":{"type":"string","maxLength":80},"maxItems":25,"description":"Up to 25 items, each up to 80 characters."},"certifications":{"type":"array","items":{"type":"string","maxLength":80},"maxItems":25,"description":"Up to 25 items, each up to 80 characters."},"linkedIn":{"type":["string","null"],"format":"uri"},"twitter":{"type":["string","null"],"format":"uri"},"facebook":{"type":["string","null"],"format":"uri"}},"additionalProperties":false},"Changes":{"type":"object","description":"Map of changed field name -> { from, to }, for only the fields whose value actually changed (an update that changes nothing returns an empty object).","additionalProperties":{"type":"object","properties":{"from":{},"to":{}}}},"StackItem":{"type":"object","description":"One entry in the caller's MSP company's shared product stack — every user in the company reads and writes the same set of products, it is not a per-user list.","required":["product","status"],"properties":{"product":{"$ref":"#/components/schemas/ProductSummary"},"status":{"type":"string","enum":["CURRENT","EVALUATING","PLANNED","PAST"]},"notes":{"type":["string","null"]},"isPrimary":{"type":"boolean","description":"Whether this is the company's primary tool in its category."},"addedAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"StackItemInput":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["CURRENT","EVALUATING","PLANNED","PAST"]},"notes":{"type":["string","null"],"maxLength":2000},"isPrimary":{"type":"boolean","default":false}},"additionalProperties":false},"VendorManagedProduct":{"type":"object","description":"A product belonging to the vendor the caller has claimed, including unpublished DRAFT/ARCHIVED products (unlike the public Product/ProductSummary schemas, which are PUBLISHED-only).","required":["id","slug","name","status"],"properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"tagline":{"type":["string","null"]},"description":{"type":["string","null"]},"status":{"type":"string","enum":["DRAFT","PUBLISHED","ARCHIVED"]},"isFeatured":{"type":"boolean"},"websiteUrl":{"type":["string","null"],"format":"uri"},"documentationUrl":{"type":["string","null"],"format":"uri"},"pricingUrl":{"type":["string","null"],"format":"uri"},"pricingModel":{"type":["string","null"],"enum":["subscription","one-time","usage-based","free",null]},"startingPrice":{"type":["number","null"]},"pricingCurrency":{"type":["string","null"],"example":"USD"},"psaIntegrations":{"type":"array","items":{"type":"string","enum":["HALO_PSA","CONNECTWISE_PSA","AUTOTASK_PSA"]}},"capabilities":{"type":"object","description":"AI / automation capability signals detected by the catalog's enrichment agent. Use these to filter or to answer questions like 'does this tool have an MCP server' or 'is there a public API'.","properties":{"hasAiFeatures":{"type":"boolean","description":"Product embeds or supports AI features."},"hasMcpSupport":{"type":"boolean","description":"Product exposes a Model Context Protocol (MCP) interface."},"hasCli":{"type":"boolean","description":"Product has a command-line interface."},"hasPublicApi":{"type":"boolean","description":"Product has a documented public API."},"aiCapabilities":{"type":["string","null"],"description":"Short free-text summary of the product's AI/automation capabilities. Only present on product detail."},"aiCapabilitiesUpdatedAt":{"type":["string","null"],"format":"date-time","description":"When aiCapabilities was last assessed. Only present on product detail."}}},"lastEditedBy":{"type":["string","null"],"enum":["VENDOR","AI_AGENT","ADMIN",null]},"lastEditedAt":{"type":["string","null"],"format":"date-time"},"url":{"type":"string","format":"uri"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"VendorProductPatchInput":{"type":"object","description":"All fields optional, but at least one is required. name, slug, status, isFeatured, and vendorId are not editable via this endpoint.","properties":{"tagline":{"type":["string","null"],"maxLength":160},"description":{"type":["string","null"],"maxLength":10000},"websiteUrl":{"type":["string","null"],"format":"uri"},"documentationUrl":{"type":["string","null"],"format":"uri"},"pricingUrl":{"type":["string","null"],"format":"uri"},"pricingModel":{"type":["string","null"],"enum":["subscription","one-time","usage-based","free",null]},"startingPrice":{"type":["number","null"],"minimum":0},"pricingCurrency":{"type":["string","null"],"description":"3-letter ISO 4217 code, e.g. \"USD\"."},"psaIntegrations":{"type":"array","items":{"type":"string","enum":["HALO_PSA","CONNECTWISE_PSA","AUTOTASK_PSA"]},"maxItems":10},"hasAiFeatures":{"type":"boolean"},"hasMcpSupport":{"type":"boolean"},"hasCli":{"type":"boolean"},"hasPublicApi":{"type":"boolean"},"aiCapabilities":{"type":["string","null"],"maxLength":2000}},"additionalProperties":false}},"parameters":{"page":{"name":"page","in":"query","description":"1-based page number to fetch.","required":false,"schema":{"type":"integer","minimum":1,"default":1}},"limit":{"name":"limit","in":"query","description":"Number of items per page, up to 100.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}},"responses":{"BadRequest":{"description":"The request was malformed (e.g. a missing/too-short search query).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No resource was found matching the given identifier.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalError":{"description":"An unexpected server error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"The request is missing a bearer token, or the token is invalid, expired, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The token's scopes don't permit this operation, or the caller doesn't own the resource being modified (e.g. a product belonging to a different vendor).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ValidationError":{"description":"The request body failed validation; see error.details for per-field messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests for this token. Limit: 120 requests/minute per token. Retry after the Retry-After header's value, in seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"securitySchemes":{"bearerToken":{"type":"http","scheme":"bearer","bearerFormat":"mspn_<48 hex chars>","description":"Personal API token for the token-authenticated endpoints below (/api/v1/me/**, /api/v1/vendor/**). Create one while signed in, under Settings → Connected agents at https://www.msp-navigator.com/settings, then send it as \"Authorization: Bearer mspn_...\". Scopes: \"read\" (Read your profile, company and stack), \"write:profile\" (Update your user and company profile), \"write:stack\" (Add, update and remove products in your company stack), \"write:vendor\" (Edit products of the vendor you have claimed). Any \"write:*\" scope implies \"read\". All other operations in this API are public and need no token."}}}}