{ "openapi": "3.1.0", "info": { "title": "Hanzo Cloud API", "description": "Package tools is everything your org can call, in one list: connector actions, functions, agents, skills and your own MCP servers.", "version": "v1" }, "servers": [ { "url": "https://api.hanzo.ai" } ], "tags": [ { "name": "mcp" }, { "name": "plugins" }, { "name": "skills" }, { "name": "tools" } ], "paths": { "/v1/mcp/servers": { "get": { "operationId": "get_v1_mcp_servers", "summary": "Lists the external MCP servers the caller's org has registered.", "description": "Lists the external MCP servers the caller's org has registered.\nEach record carries the URL and the name of the header its credential is\ninjected into; the credential VALUE lives only in KMS and is never returned,\nso hasSecret is the whole of what this surface says about it.", "tags": [ "mcp" ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/mcpServerList" } } }, "description": "ok" } } }, "post": { "operationId": "post_v1_mcp_servers", "summary": "Gives the caller's org one more external MCP server, so its tools join the org's tool plane and the fleet's MCP door.", "description": "Gives the caller's org one more external MCP server, so its tools\njoin the org's tool plane and the fleet's MCP door. It is the ONE way an org\ngains a server, whether it typed the URL in or enabled a catalog listing: both\nwrite the SAME record, and `source` says which it was. A second registration\npath would be a second place for a server to exist, and then a second place to\nforget to check the credential.\n\nThe credential VALUE is sealed in KMS under a per-org ref; the row keeps only\nthe URL, the header name to inject it into, and a has-secret flag — so a secret\nwith no KMS configured is refused 503 rather than stored in the clear. The URL\nis SSRF-validated here and re-checked by the dialer at connect time, which is\nthe DNS-rebinding defense.\n\nEnabling a listing the org already enabled REVISES that server rather than\nadding a near-duplicate beside it, so a retried enable is the same one server.\nAnswers 201 with the stored record.", "tags": [ "mcp" ], "requestBody": { "content": { "application/json": { "example": { "authHeader": "Authorization", "listing": "com.stripe_mcp", "secret": "Bearer …" }, "schema": { "$ref": "#/components/schemas/createServerReq" } } }, "required": true }, "responses": { "201": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MCPServer" } } }, "description": "created" } } } }, "/v1/mcp/servers/{id}": { "delete": { "operationId": "delete_v1_mcp_servers_by_id", "summary": "Deregisters one of the caller org's external MCP servers, so its tools leave the registry.", "description": "Deregisters one of the caller org's external MCP servers, so its\ntools leave the registry. Scoped to the caller's org, so an id belonging to\nanother tenant is a 404 and not a delete. Answers 204 with no body; a server\nthis org does not have is 404.", "tags": [ "mcp" ], "parameters": [ { "name": "id", "in": "path", "required": true, "description": "ID is the server to deregister, from the path.", "schema": { "type": "string" } } ], "responses": { "204": { "description": "no content" } } } }, "/v1/plugins": { "get": { "operationId": "get_v1_plugins", "summary": "Reports what this deployment actually mounted: every subsystem the composition root declared and whether it is switched on.", "description": "Reports what this deployment actually mounted: every subsystem the\ncomposition root declared and whether it is switched on. A plugin here is\nMOUNTED CODE that extends the deployment's own surface — not a tool an agent\ncalls — so this is an inventory and not a tool source. It is read off the same\nboot snapshot every traced request resolves its subsystem label against, so it\ncannot drift from what is serving. Enabled-only by default, because a caller\nasking what this deployment can do wants what is running; ?all=true adds the\nconfigured-but-off ones.", "tags": [ "plugins" ], "parameters": [ { "name": "all", "in": "query", "required": false, "description": "All includes the configured-but-disabled subsystems too, but only when it is\nexactly the string \"true\". Otherwise only the running ones are reported.", "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/pluginMountList" } } }, "description": "ok" } } } }, "/v1/plugins/authored": { "get": { "operationId": "get_v1_plugins_authored", "summary": "Lists the plugins the caller's org BUILT, newest first, each with the TypeScript as authored.", "description": "Lists the plugins the caller's org BUILT, newest first,\neach with the TypeScript as authored. That is a different set with a different\nlifecycle from GET /v1/plugins, which reports the subsystems this deployment\nmounted. The bundled CommonJS the runtime executes is never included, and\nneither is any credential — a plugin names the connectors provider it needs and\nreads the credential from ctx.auth at run time.", "tags": [ "plugins" ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/authoredPluginList" } } }, "description": "ok" } } } }, "/v1/plugins/authored/{id}": { "delete": { "operationId": "delete_v1_plugins_authored_by_id", "summary": "Removes one of the caller org's built plugins, so the runtime can no longer load it.", "description": "Removes one of the caller org's built plugins, so the\nruntime can no longer load it. Scoped to the caller's org, so an id belonging\nto another tenant answers 404 and is not deleted.", "tags": [ "plugins" ], "parameters": [ { "name": "id", "in": "path", "required": true, "description": "ID is the plugin to remove, from the path.", "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/pluginDeleted" } } }, "description": "ok" } } } }, "/v1/plugins/build": { "post": { "operationId": "post_v1_plugins_build", "summary": "Build a plugin for your org from TypeScript, or from an API spec a model writes it from", "description": "Builds one plugin for the caller's org and answers 201 with the bundle's size, whether a model wrote the source, and the plugin as stored. Post `source` to build TypeScript as-is, or `spec` — an OpenAPI document or plain prose describing the endpoints — to have one generated; the generated source comes back in the answer, so a caller reads what will run before it runs. Exactly one of the two, and `name` must be one lowercase path segment; both or neither is 400.\n\nCOMPILING IS THE GATE. The source goes through the same pipeline the committed connectors do — esbuild to one CommonJS program, then compiled in the goja runtime that will actually execute it — and anything that fails is rejected and NEVER stored. So a plugin in the store is one this deployment has already loaded once, not one a model claimed was fine. A failed build answers 422 carrying the diagnostics a caller needs to fix it: the bundler's error, the source that failed, and whether the model wrote it — a body outside the declared success shape.\n\nCREDENTIALS ARE NOT PART OF A PLUGIN. A plugin names the connectors `provider` it needs and reads that credential from `ctx.auth` at run time, under KMS custody. Source that contains something shaped like a key is REFUSED rather than silently scrubbed, so a caller who pasted one finds out instead of shipping it — register it as a connector instead.\n\nRequires a validated principal; 403 without one. The plugin is stored under that principal's org and is what `/v1/plugins/authored` lists — never `/v1/plugins`, which is this deployment's mounted-subsystem inventory. Source over 512 KiB or a spec over 256 KiB is refused. Posting a `spec` to a deployment with no AI client configured is 503, and a generation that fails upstream is 502.", "tags": [ "plugins" ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/buildRequest" } } } }, "responses": { "2XX": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/buildOut" } } } } } } }, "/v1/skills": { "get": { "operationId": "get_v1_skills", "summary": "Lists the skills the caller's org can reach — the brand's embedded catalogue plus the org's own authored ones — with each one's activation flag.", "description": "Lists the skills the caller's org can reach — the brand's embedded\ncatalogue plus the org's own authored ones — with each one's activation flag.\nA skill is discovery and activation metadata attached to an agent, never called\ndirectly, so every entry here is non-dispatchable. It is GET /v1/tools narrowed\nto one source, not a second store: a name a caller sees here is the same entry,\nwith the same activation state, that discovery reports.", "tags": [ "skills" ], "parameters": [ { "name": "activated", "in": "query", "required": false, "description": "Activated keeps only the tools activated for the caller's org and project,\nand only when it is exactly the string \"true\".", "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/sourceToolList" } } }, "description": "ok" } } }, "post": { "operationId": "post_v1_skills", "summary": "Adds or revises one of the caller org's own skills, and answers 201 with the stored record.", "description": "Adds or revises one of the caller org's own skills, and answers 201\nwith the stored record. The id is derived from the name, so writing the same\nname again REVISES that skill rather than accumulating near-duplicates that\nwould then collide in the registry. An org's skills are private to it by\nconstruction — they live in a different store from the brand's embedded\ncatalogue and have no path into the public gallery — and a brand skill always\nwins a name collision against an org's.", "tags": [ "skills" ], "requestBody": { "content": { "application/json": { "example": { "content": "# Triage\n…", "description": "how we triage", "name": "triage" }, "schema": { "$ref": "#/components/schemas/skillIn" } } }, "required": true }, "responses": { "201": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/skillWritten" } } }, "description": "created" } } } }, "/v1/skills/authored": { "get": { "operationId": "get_v1_skills_authored", "summary": "Lists the caller org's OWN skills with their SKILL.md bodies.", "description": "Lists the caller org's OWN skills with their SKILL.md\nbodies. GET /v1/skills is the registry view — the brand's catalogue plus this\norg's, with activation flags and no bodies; this is the EDITABLE set, so it\ncarries the content that view omits and nothing the org did not write.", "tags": [ "skills" ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/authoredSkillList" } } }, "description": "ok" } } } }, "/v1/skills/{id}": { "delete": { "operationId": "delete_v1_skills_by_id", "summary": "Removes one of the caller org's authored skills.", "description": "Removes one of the caller org's authored skills. Scoped to the\ncaller's org, so an id belonging to another tenant is never reached. Removing\nwhat is not there is not an error — the caller's intent is \"gone\", and it is.", "tags": [ "skills" ], "parameters": [ { "name": "id", "in": "path", "required": true, "description": "ID is the skill to remove, from the path. It is the skill's name.", "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/skillDeleted" } } }, "description": "ok" } } } }, "/v1/tools": { "get": { "operationId": "get_v1_tools", "summary": "Lists every tool the caller's org and project can reach, from every source, each flagged with whether it is activated.", "description": "Lists every tool the caller's org and project can reach, from every\nsource, each flagged with whether it is activated. This is the discovery\nsurface: one flat set of names spanning connector actions, user functions,\nzap-service routes, agents, skills and the org's own external MCP servers,\ndeduplicated by name so the highest-precedence source wins a collision. It\nlists; it does not call — dispatch is POST /v1/tools/call.", "tags": [ "tools" ], "parameters": [ { "name": "source", "in": "query", "required": false, "description": "Source keeps only tools from one source — connector, function, zap-service,\nagent, skill or mcp. Empty keeps every source.", "schema": { "type": "string" } }, { "name": "activated", "in": "query", "required": false, "description": "Activated keeps only the tools activated for the caller's org and project,\nand only when it is exactly the string \"true\".", "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/toolList" } } }, "description": "ok" } } } }, "/v1/tools/activation": { "get": { "operationId": "get_v1_tools_activation", "summary": "Reports which tools are switched on for the caller's org and project.", "description": "Reports which tools are switched on for the caller's org and\nproject. Activation is what makes a tool dispatchable and what makes it visible\nto an agent, so this is the set the MCP tool list is drawn from — every other\ntool in the registry is discoverable but refused at call time.", "tags": [ "tools" ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/activationSet" } } }, "description": "ok" } } }, "put": { "operationId": "put_v1_tools_activation", "summary": "Switches tools on and off for the caller's org and project, and answers with the resulting activated set.", "description": "Switches tools on and off for the caller's org and project, and\nanswers with the resulting activated set. It is the ONE write path that turns\nskills, plugins and connectors into callable tools — an unactivated tool is\nlisted by discovery but refused 403 at dispatch. Activate is applied before\nDeactivate, so a name in both lists ends up off. More than 256 toggles in one\nrequest is refused 413.", "tags": [ "tools" ], "requestBody": { "content": { "application/json": { "example": { "activate": [ "cloud_get_ping" ], "deactivate": [] }, "schema": { "$ref": "#/components/schemas/activationReq" } } }, "required": true }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/activationSet" } } }, "description": "ok" } } } }, "/v1/tools/call": { "post": { "operationId": "post_v1_tools_call", "summary": "Runs one of the caller's activated tools and answers with its output.", "description": "Runs one of the caller's activated tools and answers with its output.\n\nThis is the door onto the tool plane's DYNAMIC half — the half no build-time\ncatalogue can hold, because it is per-tenant: an org's connected connector\nactions, its authored skills, its agents and functions, and the tools of every\nexternal MCP server it registered. A tool's existence, its price and its\nactivation are all rows, not code, so they cannot be known until the caller is.\n\nOne policy, the registry's: resolve by precedence, refuse an unactivated tool\n403, settle a priced one through the x402 seam or fail closed 402, then\ndispatch to the winning source bound to the caller's own (org, project). One\nmetered unit, one audit record. A caller can only ever dispatch its own tools.\n\nDiscovery is GET /v1/tools — ?activated=true for the callable set.", "tags": [ "tools" ], "requestBody": { "content": { "application/json": { "example": { "arguments": { "channel": "#general", "text": "hi" }, "name": "slack_post_message" }, "schema": { "$ref": "#/components/schemas/toolCall" } } }, "required": true }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/toolResult" } } }, "description": "ok" } } } }, "/v1/tools/catalog": { "get": { "operationId": "get_v1_tools_catalog", "summary": "Lists the MCP servers the public registries publish, as we hold them: our canonical copy of registry.modelcontextprotocol.io, plus what we decided about each entry.", "description": "Lists the MCP servers the public registries publish, as we hold\nthem: our canonical copy of registry.modelcontextprotocol.io, plus what we\ndecided about each entry.\n\nThis is the SHELF an org picks from. A listing with a streamable-http endpoint\ncan be enabled as-is — POST /v1/mcp/servers with its id — and its tools then\njoin the org's tool plane and the fleet's MCP door. A listing that only ships a\nstdio package needs a process to run it, which is why the transports are on\nevery entry rather than implied.\n\nHidden entries are absent: they are the ones we took off the shelf. A platform\nSuperAdmin sees them, because the same query answers \"what is on the shelf\" and\n\"what is in the catalog\" and two queries would drift apart.\n\nIt is PAGED — 50 by default, 200 at most. The public registry publishes tens of\nthousands of servers, so an unbounded answer is a twenty-megabyte response and a\nstorefront that renders in a minute. total is the whole match, not the page.", "tags": [ "tools" ], "parameters": [ { "name": "q", "in": "query", "required": false, "description": "Q matches the name, title or description, case-insensitively.", "schema": { "type": "string" } }, { "name": "featured", "in": "query", "required": false, "description": "Featured keeps only the listings we put on the front of the shelf, and only\nwhen it is exactly the string \"true\".", "schema": { "type": "string" } }, { "name": "official", "in": "query", "required": false, "description": "Official keeps only the vendors' OWN servers — not third-party copies of\nthem — and only when it is exactly the string \"true\".", "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "description": "Limit bounds the page: default 50, maximum 200. A value that is not a\npositive integer reads as the default.", "schema": { "type": "integer" } }, { "name": "offset", "in": "query", "required": false, "description": "Offset skips that many listings.", "schema": { "type": "integer" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/mcpCatalog" } } }, "description": "ok" } } } }, "/v1/tools/catalog/sync": { "post": { "operationId": "post_v1_tools_catalog_sync", "summary": "Pulls the public MCP registry into our canonical copy and reports what changed.", "description": "Pulls the public MCP registry into our canonical copy and reports\nwhat changed. SuperAdmin only; every other caller is refused.\n\nIt is IDEMPOTENT: a listing is keyed by the publisher's own reverse-DNS name,\nso a second pass over an unchanged registry rewrites the same rows and reports\nadded=0, updated=0. It never deletes — a listing that vanishes upstream may be\none an org has already enabled, and dropping its description would not drop its\nserver. And it never touches CURATION: hidden, featured, an admin-set official\nand a logo survive every sync, because the write does not name those columns.", "tags": [ "tools" ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/mcpCatalogSync" } } }, "description": "ok" } } } }, "/v1/tools/catalog/{id}": { "get": { "operationId": "get_v1_tools_catalog_by_id", "summary": "Returns one catalog entry in full: the publisher's description, its repository and site, every package form with the runtime that launches it, and every hosted endpoint.", "description": "Returns one catalog entry in full: the publisher's description, its\nrepository and site, every package form with the runtime that launches it, and\nevery hosted endpoint. It is what a branding page renders, and what tells a\ncaller whether the listing can be enabled here and now (a streamable-http\nremote) or needs somewhere to run first (a stdio package).\n\nA HIDDEN listing is not served to an org — a shelf that renders what it does\nnot list would be a way around the shelf — but is served to a SuperAdmin, who\nis the one deciding whether to put it back.", "tags": [ "tools" ], "parameters": [ { "name": "id", "in": "path", "required": true, "description": "ID is the listing, from the path. It is the publisher's reverse-DNS name\nwith its one slash written as an underscore — \"com.stripe_mcp\".", "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MCPListing" } } }, "description": "ok" } } }, "patch": { "operationId": "patch_v1_tools_catalog_by_id", "summary": "Sets what WE say about one catalog entry — hidden, featured, official, logo — and answers with the stored listing.", "description": "Sets what WE say about one catalog entry — hidden, featured,\nofficial, logo — and answers with the stored listing. SuperAdmin only; every\nother caller is refused.\n\nCuration is the half of a catalog row a sync cannot write, and this is the only\nthing that writes it. The upstream half is never editable here: a description\nthat disagreed with the publisher's would be a fork of their listing, and the\nnext sync would silently undo it.", "tags": [ "tools" ], "parameters": [ { "name": "id", "in": "path", "required": true, "description": "ID is the listing to curate, from the path.", "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "example": { "featured": true, "official": false }, "schema": { "$ref": "#/components/schemas/curateReq" } } }, "required": true }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MCPListing" } } }, "description": "ok" } } } } }, "components": { "schemas": { "AuthoredPlugin": { "properties": { "createdAt": { "description": "CreatedAt is when the plugin was last built, Unix seconds.", "type": "integer" }, "id": { "description": "ID is the plugin's id within the org, and the id a delete addresses.", "type": "string" }, "name": { "description": "Name is the plugin's name: one lowercase path segment, the id it runs by.", "type": "string" }, "org": { "description": "Org is the org that built the plugin — the validated caller's.", "type": "string" }, "provider": { "description": "Provider is the connectors provider whose credential this plugin uses at\nrun time. Absent for a plugin that needs none. The credential itself is\nnever here — it stays under KMS custody in the connectors plane.", "type": "string" }, "source": { "description": "Source is the TypeScript as authored (or as generated from a spec).", "type": "string" } }, "type": "object" }, "MCPListing": { "properties": { "description": { "description": "Description is the publisher's one-line summary.", "type": "string" }, "featured": { "description": "Featured puts the listing on the front of the shelf. Curation.", "type": "boolean" }, "hidden": { "description": "Hidden keeps the listing out of the org-visible catalog. Curation: a sync\nnever changes it. Only a SuperAdmin sets it, and only a SuperAdmin sees a\nhidden entry listed.", "type": "boolean" }, "id": { "description": "ID addresses the listing in a URL. It is the reverse-DNS NAME with its one\nslash written as an underscore — reversible, because a namespace never\ncontains an underscore — so the id is readable and stable rather than a\nhash that means nothing to whoever reads a link.", "type": "string" }, "logo": { "description": "Logo is the brand mark to render for the listing — the publisher's icon when\nthe entry carries one, or the one an admin set. Curation.", "type": "string" }, "name": { "description": "Name is the publisher's reverse-DNS name, e.g. \"com.stripe/mcp\".", "type": "string" }, "official": { "description": "Official is whether this is the vendor's OWN server rather than someone\nelse's copy of it. Derived on every sync (see isOfficial) until a\nSuperAdmin sets it explicitly, after which the admin's answer stands.", "type": "boolean" }, "packages": { "description": "Packages are the runnable package forms — npm, pypi, oci — each with the\nruntime that launches it and the transport it then speaks.", "items": { "$ref": "#/components/schemas/MCPPackage" }, "type": "array" }, "registry": { "description": "Registry is the upstream this row was synced from.", "type": "string" }, "remotes": { "description": "Remotes are the hosted endpoints the publisher serves the server at.", "items": { "$ref": "#/components/schemas/MCPRemote" }, "type": "array" }, "repo": { "description": "Repo is the source repository URL, when the entry names one.", "type": "string" }, "site": { "description": "Site is the project's homepage, when the entry names one.", "type": "string" }, "synced": { "description": "Synced is when this row was last confirmed against upstream, Unix seconds.", "type": "integer" }, "title": { "description": "Title is the human-readable display name, when the entry carries one.", "type": "string" }, "transports": { "description": "Transports are the distinct transports this server can be reached over,\nsorted: some of \"stdio\", \"streamable-http\", \"sse\". A listing with\n\"streamable-http\" is one an org can enable here and now; a listing that is\nonly \"stdio\" needs a process to run it.", "items": { "type": "string" }, "type": "array" }, "vendor": { "description": "Vendor is the namespace half of Name — the publisher, e.g. \"com.stripe\".", "type": "string" }, "version": { "description": "Version is the published version of this listing.", "type": "string" } }, "type": "object" }, "MCPPackage": { "properties": { "identifier": { "description": "Identifier is the package name or download URL.", "type": "string" }, "registry": { "description": "Registry is where the package is fetched from: npm, pypi, oci, nuget, mcpb.", "type": "string" }, "runtime": { "description": "Runtime is the publisher's hint for what launches it: npx, uvx, docker.", "type": "string" }, "transport": { "description": "Transport is what the launched process speaks: usually \"stdio\".", "type": "string" }, "version": { "description": "Version is the exact published package version.", "type": "string" } }, "type": "object" }, "MCPRemote": { "properties": { "transport": { "description": "Transport is \"streamable-http\" or \"sse\".", "type": "string" }, "url": { "description": "URL is the endpoint.", "type": "string" } }, "type": "object" }, "MCPServer": { "properties": { "authHeader": { "description": "AuthHeader is the request header the KMS-held credential is injected into,\ne.g. \"Authorization\". Absent when the server needs no credential.", "type": "string" }, "createdAt": { "description": "CreatedAt is when the server was registered, Unix seconds.", "type": "integer" }, "hasSecret": { "description": "HasSecret is whether a credential is sealed in KMS for this server. The\nVALUE is never returned by any route.", "type": "boolean" }, "id": { "description": "ID is the server's id within the org. It also PREFIXES every tool name the\nserver contributes, which is what keeps two servers' \"search\" apart.", "type": "string" }, "listing": { "description": "Listing is the catalog entry this server was enabled from, when it was.\nEmpty means the org typed the URL in itself.", "type": "string" }, "name": { "description": "Name is the org's label for the server.", "type": "string" }, "org": { "description": "Org is the org that registered the server — the validated caller's.", "type": "string" }, "source": { "description": "Source is where the registration came from: \"catalog\" when it was enabled\noff the shelf, \"org\" when the org registered the URL itself. It is DERIVED\nfrom Listing rather than stored, because two columns for one fact is two\nchances to disagree.", "type": "string" }, "url": { "description": "URL is the server's JSON-RPC endpoint. Always a public http(s) host: the\nregistration boundary and the dialer both refuse anything else.", "type": "string" } }, "type": "object" }, "Price": { "properties": { "amount": { "description": "Amount is what ONE call costs, EXACTLY: an 18-decimal USD value, so a\nper-call price of $0.0025 is $0.0025 and not a cent-floored zero. Cents\ncannot hold a per-token price, and a tool plane is where per-token prices\nlive." }, "currency": { "description": "Currency is the ISO 4217 code, e.g. \"USD\". Empty means USD.", "type": "string" }, "recipient": { "description": "Recipient is the payout wallet ref the marketplace seller is paid at.", "type": "string" } }, "type": "object" }, "Skill": { "properties": { "content": { "description": "Content is the SKILL.md body, markdown.", "type": "string" }, "createdAt": { "description": "CreatedAt is when the skill was last written, Unix seconds.", "type": "integer" }, "description": { "description": "Description is the one-line summary discovery shows for the skill.", "type": "string" }, "id": { "description": "ID is the skill's id within the org. It is DERIVED from Name, so writing\nthe same name again revises that skill rather than adding another.", "type": "string" }, "name": { "description": "Name is the skill's name: one lowercase path segment (a-z0-9, _ or -).", "type": "string" }, "org": { "description": "Org is the org that authored the skill — the validated caller's, never a\nvalue the body supplied.", "type": "string" } }, "type": "object" }, "Tool": { "properties": { "activated": { "description": "Activated is filled by the registry from the activation store for the\nrequesting (org,project); providers leave it zero. An unactivated tool is\ndiscoverable but refused 403 at dispatch.", "type": "boolean" }, "description": { "description": "Description is the prose a model reads to decide whether to call the tool.", "type": "string" }, "dispatchable": { "description": "Dispatchable is whether the tool can be CALLED. False for a listing-only\nentry: a skill is activated and attached to an agent, never called.", "type": "boolean" }, "inputSchema": { "description": "Schema is the JSON Schema of the call arguments — the MCP inputSchema.\nAbsent for a tool that takes none." }, "name": { "description": "Name is the tool's id in the flat, fleet-wide tool namespace — the value a\ntools/call passes. Unique across sources: a collision is resolved by source\nprecedence before the caller ever sees it.", "type": "string" }, "price": { "$ref": "#/components/schemas/Price", "description": "Price is what a call costs and who is paid, absent for a free tool.\nEnforcement is the x402 settlement seam; this is the declaration." }, "source": { "description": "Source is where the tool comes from: connector, function, zap-service,\nagent, skill or mcp.", "type": "string" } }, "type": "object" }, "activationReq": { "properties": { "activate": { "description": "Activate switches these tool names on for the caller's org and project.", "items": { "type": "string" }, "type": "array" }, "deactivate": { "description": "Deactivate switches these tool names off.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "activationSet": { "properties": { "enabled": { "description": "Enabled is every tool name activated for the caller's org and project.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "authoredPluginList": { "properties": { "plugins": { "description": "Plugins is every plugin this org built, newest first, each carrying the\nTypeScript as authored. The bundled artifact is never rendered.", "items": { "$ref": "#/components/schemas/AuthoredPlugin" }, "type": "array" } }, "type": "object" }, "authoredSkillList": { "properties": { "skills": { "description": "Skills is every skill this org authored, each with its SKILL.md content.", "items": { "$ref": "#/components/schemas/Skill" }, "type": "array" } }, "type": "object" }, "buildOut": { "type": "object", "properties": { "bytes": { "type": "integer" }, "generated": { "type": "boolean" }, "plugin": { "$ref": "#/components/schemas/AuthoredPlugin" } } }, "buildRequest": { "type": "object", "properties": { "name": { "type": "string" }, "provider": { "type": "string" }, "source": { "type": "string" }, "spec": { "type": "string" } } }, "createServerReq": { "properties": { "authHeader": { "description": "AuthHeader is the request header the credential is injected into, e.g.\n\"Authorization\". Empty means the server needs no credential.", "type": "string" }, "listing": { "description": "Listing enables a CATALOG entry instead — the id from GET /v1/tools/catalog.\nThe endpoint is the listing's own streamable-http remote, so a listing that\nonly ships a stdio package is refused: there is nothing to reach yet.", "type": "string" }, "name": { "description": "Name labels the server for the org. Required with URL; with Listing it\ndefaults to the listing's own title.", "type": "string" }, "secret": { "description": "Secret is the credential VALUE. It is sealed into KMS under a per-org ref\nand never stored in SQLite, never listed, and never returned.", "type": "string" }, "url": { "description": "URL is the server's JSON-RPC endpoint. It must be an http(s) URL naming a\nPUBLIC host: loopback, link-local, private and cloud-metadata addresses are\nrefused here and again when the dialer connects.", "type": "string" } }, "type": "object" }, "curateReq": { "properties": { "featured": { "description": "Featured puts the listing on the front of the shelf, or takes it off.", "type": "boolean" }, "hidden": { "description": "Hidden takes the listing off the org-visible shelf, or puts it back.", "type": "boolean" }, "id": { "description": "ID is the listing to curate, from the path.", "type": "string" }, "logo": { "description": "Logo is the brand mark to render, an https URL. Empty clears ours and lets\nthe next sync adopt the publisher's own icon again.", "type": "string" }, "official": { "description": "Official overrides the derivation: setting it makes this answer FINAL, so\nno later sync re-derives over it. That is the difference between a default\nand a decision — the derivation can only tell that a domain-verified\npublisher serves the endpoint, not that the product is theirs.", "type": "boolean" } }, "type": "object" }, "mcpCatalog": { "properties": { "catalog": { "description": "Catalog is this page of listings, featured first, then by name.", "items": { "$ref": "#/components/schemas/MCPListing" }, "type": "array" }, "limit": { "description": "Limit is the page size that was actually applied — the default or the clamp,\nwhen the request asked for neither or for too much.", "type": "integer" }, "offset": { "description": "Offset is where this page started, so a caller pages from what the server\ndid rather than from what it asked for.", "type": "integer" }, "total": { "description": "Total is how many listings the filter matched, which is more than this page\nholds whenever there is a next one.", "type": "integer" } }, "type": "object" }, "mcpCatalogSync": { "properties": { "added": { "description": "Added is how many listings the catalog did not have before.", "type": "integer" }, "registry": { "description": "Registry is the upstream this pass read.", "type": "string" }, "total": { "description": "Total is how many listings the catalog holds now.", "type": "integer" }, "updated": { "description": "Updated is how many the publisher has changed since we last looked.", "type": "integer" } }, "type": "object" }, "mcpServerList": { "properties": { "servers": { "description": "Servers is every external MCP server this org has registered. No secret\nVALUE is ever included — only whether one is set.", "items": { "$ref": "#/components/schemas/MCPServer" }, "type": "array" } }, "type": "object" }, "pluginDeleted": { "properties": { "deleted": { "description": "Deleted is the plugin id that is now gone.", "type": "string" } }, "type": "object" }, "pluginMount": { "properties": { "enabled": { "description": "Enabled is whether this subsystem is switched on in this deployment.", "type": "boolean" }, "name": { "description": "Name is the subsystem's name, the same label a traced request resolves to.", "type": "string" }, "prefixes": { "description": "Prefixes are the URL prefixes this subsystem serves.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "pluginMountList": { "properties": { "plugins": { "description": "Plugins is every subsystem the composition root declared, filtered to the\nenabled ones unless all=true.", "items": { "$ref": "#/components/schemas/pluginMount" }, "type": "array" } }, "type": "object" }, "skillDeleted": { "properties": { "deleted": { "description": "Deleted is the skill id that is now gone.", "type": "string" } }, "type": "object" }, "skillIn": { "properties": { "content": { "description": "Content is the SKILL.md body. Required, at most 256 KiB.", "type": "string" }, "description": { "description": "Description is the one-line summary discovery shows for the skill.", "type": "string" }, "name": { "description": "Name is the skill's id within the org: one lowercase path segment\n(a-z0-9, _ or -). Writing an existing name REVISES that skill.", "type": "string" } }, "type": "object" }, "skillWritten": { "properties": { "skill": { "$ref": "#/components/schemas/Skill", "description": "Skill is the skill as stored, with its derived id and creation time." } }, "type": "object" }, "sourceToolList": { "properties": { "source": { "description": "Source is the source these tools came from.", "type": "string" }, "tools": { "description": "Tools is the caller's tools from that source. Never null.", "items": { "$ref": "#/components/schemas/Tool" }, "type": "array" } }, "type": "object" }, "toolCall": { "properties": { "arguments": { "additionalProperties": { "type": "object" }, "description": "Arguments is the tool's own input object, passed through verbatim to\nwhichever source owns it.", "type": "object" }, "name": { "description": "Name is the tool to run, exactly as GET /v1/tools reports it.", "type": "string" } }, "type": "object" }, "toolList": { "properties": { "tools": { "description": "Tools is every tool the caller may see, deduplicated by name with source\nprecedence applied.", "items": { "$ref": "#/components/schemas/Tool" }, "type": "array" } }, "type": "object" }, "toolResult": { "properties": { "name": { "description": "Name is the tool that ran.", "type": "string" }, "result": { "description": "Result is the tool's own output, verbatim — its shape is the tool's, not\nthis plane's.", "type": "object" } }, "type": "object" } } } }