mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-31 20:53:15 +08:00
* fix(v2): close four validation holes in the logs and billing surfaces
Each of these answered a caller-supplied value with a 500 or a silently
wrong result instead of a 400.
- `GET /api/v2/logs` accepted any string as `startDate`/`endDate`. The
route constructs a `Date` from it, so `?startDate=abc` reached the
driver's timestamp mapper as an `Invalid Date` and 500'd. Both bounds
now carry `.datetime()`, matching the sibling run list so one timestamp
works on both collections. This narrows the accepted set: a date without
a time and an offset-bearing timestamp are now rejected, and the field
descriptions say "UTC ISO 8601" rather than overpromising "ISO 8601".
- `v2BillingStatusQuerySchema` was the only non-strict query schema in its
family, so a mis-cased `workspaceID` was stripped and the caller got
account-scope billing in place of the workspace scope it asked for — a
wrong answer about money, served as a 200.
- An unresolvable `cursor` on `/api/v2/billing/logs` applied no cursor
condition and restarted the sequence at page 1 while still reporting
`hasMore`, so a pager holding a cursor across a deploy loops over the
first page and counts the same credits on every lap. It is now a 400.
The message does not reuse `INVALID_CURSOR_MESSAGE`, which names
`sortBy`/`sortOrder` params this collection does not accept.
- The logs `status` field disagrees with the run resources for the same
run: the run projection overlays `paused` from `paused_executions`,
so an ordinary human-in-the-loop pause reads `paused` there and
`pending` here. Reconciling would mean joining `paused_executions` in
this read and silently moving live runs between two buckets of a
shipped field, so the divergence is documented on the contract instead.
* feat(v2): expose the MCP tool plane and page the MCP server list
Registering an MCP server through v2 dead-ended: nothing on the public
surface ever ran tool discovery, so connectionStatus, toolCount, lastError,
and lastToolsRefresh stayed at their registration defaults and there was no
way to read a server's tools without opening the UI.
Adds GET /api/v2/mcp-servers/{id}/tools over a thin use case composed from
the existing mcp_servers.tools.discover operation, resolveServerContext, and
mcpService.discoverServerTools. It is personal-API-key-only — discovery
resolves the acting user's own OAuth credentials, which a workspace key
cannot supply — and the contract says so rather than letting callers meet an
unexplained 403. Discovery failures are classified instead of collapsing
into a 500: an unreachable or cooling-down server is a retryable 503, a
stale OAuth grant is a 401.
Also pages GET /api/v2/mcp-servers. It was the one unbounded list on the v2
surface, classified full-set on a bounded-by-construction rationale that
only holds for folder lists; nothing caps how many servers a workspace
registers.
* feat(v2/tables): strict row bodies, a filtered row count, and round-trippable required columns
Three tables gaps from the v2 capability evaluation.
Strictness. Every v2 tables request body is now `.strict()`. The row family
was the whole hole: `POST /query` sent v1's `filter` key answered 200 with a
fully unfiltered page, because Zod strips unknown keys unless told not to. The
same laxity covered the row create/update/delete/upsert/find bodies, the
run and cancel-runs bodies, the enrichment body, and — outside the row family
but the same class — the column delete, view create/update, and export bodies.
A contract sweep now walks every body-bearing tables contract and fails if one
of them stops rejecting an unrecognized key.
Filtered row count. `POST /api/v2/tables/{tableId}/query/count` answers the
question v1's `includeTotal`/`totalCount` answered and the `{data, nextCursor}`
envelope has nowhere to put: how many rows a predicate matches. It binds the
existing `queryTableRows` use case with `includeTotal: true, limit: 1` — no new
domain logic and the same `tables.rows.query` read policy. The use case types
`totalCount` as nullable because paged callers can decline it; this route always
asks for it, so a null is treated as a broken invariant rather than presented as
a fabricated zero.
Required columns. `required` is accepted on create-table, add-column, and
update-column, matching v1. v2 emitted the flag on every read while stripping it
from every write, so a column could not round-trip. Enforcement was already
complete: turning it on over rows with null, missing, or empty cells is rejected
by the domain.
* test(skills): pin the workspace-API-key split as structural, not accidental
A workspace API key can create a skill it can then never update or delete,
which no sibling resource does — so the asymmetry reads like an oversight
worth widening. It is not. Skill edits are authorized by the per-skill
editor row belonging to the acting user, which is why update/upsert/delete
declare a 'read' floor rather than 'write': workspace role is not the
authority. A workspace key carries no user subject, so allowing one replaces
a 403 with an unclassified PrincipalSubjectUserRequiredError that the v2
surface renders as a caller-reachable 500.
Records the reason on the registry and pins it, so the next reader finds the
argument instead of flipping the flag.
* feat(v2): read deployment state, and undo a file delete
Two v2 reads that existed only as a side effect of a mutation.
`GET /api/v2/workflows/{id}/deployment` publishes the state the deploy,
undeploy, and rollback responses carry, plus `needsRedeployment` — which
those responses structurally cannot carry, because they answer at the
moment the draft and the live version are equal. A caller that lost the
mutation response, or that polls from another process, had no way to ask.
Reuses `readWorkflowDeploymentStatus` behind `workflows.read`, the same
use case the internal status and deploy GETs already adapt.
`DELETE /api/v2/files/{fileId}` was a soft delete with no way to see what
it archived and no way to reverse it. `GET /api/v2/files?scope=archived`
pages the archived set and `deletedAt` on the file resource dates each
one; `POST /api/v2/files/{fileId}/restore` reverses the delete through
the existing `files.restore` operation. Restore is not a pure undo — it
returns the file to the root and renames it on a collision — so the use
case now reads the file back and both the response and the OpenAPI
description say what actually came back rather than what was deleted.
`scope=all` is rejected on the list for the reason the internal contract
already gives: it drops the `deleted_at` predicate and cannot use the
partial index. `scope=archived` combined with `folderPath` 404s when the
containing folder was archived too, which the contract documents.
* fix(v2): keep the unresolvable-cursor rejection a 400 on every surface
The cursor rejection lived in shared billing core but was an OrchestrationError
only, which the session-only GET /api/users/me/usage-logs cannot project: that
route is raw withRouteHandler and readTypedError matches instanceof HttpError,
so any signed-in caller typing ?cursor=x got a 500. UnknownUsageCursorError is
an HttpError carrying the OrchestrationError as its cause, so the v2 route still
renders BAD_REQUEST off the cause chain and the internal route answers 400.
Also closes the other half of the run-list parity: an inverted window on
GET /api/v2/logs is now a 400 instead of a silently empty page.
* fix(v2/tables): sweep union bodies per member and name the shapes on a rows 400
Review follow-ups on the strictness work.
The sweep was vacuous on the one union body it covers. Parsing
`{ notAContractField: true }` against `v2CreateTableRowsBodySchema` and looking
for `unrecognized_keys` anywhere in the issue tree is satisfied by either member
alone, so dropping `.strict()` from the single-row branch shipped green —
reproduced, 36/36 passing with the regression in place. The sweep now flattens a
union body into its members and asserts each one separately; removing `.strict()`
from either branch now fails a case that names it.
`POST /rows` answered an unknown key with `Invalid input`, the exact message the
v2 conventions name as failing the actionable-error rule, because a union
surfaces `invalid_union` first. The union now carries a message naming both
accepted shapes; the per-member failures still ride along in `details`.
Two TSDoc corrections. The `required` docstring claimed the domain rejects
turning the flag on over rows with empty cells — true of the update path, false
of add-column, which applies the flag as given (the same shape `unique` already
had here). And `.strict()` binds the top level only, so the view `config` object
and the shared sort-spec elements still strip unknown keys; both docstrings now
say so instead of implying full coverage.
* fix(v2): classify MCP discovery failures by type, not by substring
The tool-discovery error policy consumed categorizeError's status, whose
fallback is a substring match on the upstream message. Three consequences,
all caller-visible:
- A ZodError from the builder's own response `.parse` contains `invalid_type`,
so a Sim-side response-schema defect answered 400 "Invalid request
parameters" and suppressed the builder's 500 and its unhandled-error log.
- An upstream `Invalid params` or `not found` became the caller's 400/404 on a
request the contract had already validated.
- A stale OAuth grant to the third-party server answered 401, the status this
surface reserves for a missing or invalid Sim API key, so a client would
rotate a credential that was never the problem.
The policy now dispatches on the MCP error families and returns null for
anything else. Reauthorization is a 409 carrying
`details.code: MCP_SERVER_REAUTHORIZATION_REQUIRED`; an unreachable, slow, or
cooling-down server is a 503 with a constant message.
Also: widen the shared server path-param description now that it covers tool
listing, map the list query explicitly so no undeclared `cursor` reaches the
use-case input, and document the endpoint's write side effects.
* merge: bring in the MCP tool plane workstream
* feat(v2): make knowledge tags usable and let documents be updated
v2 accepted tag slots on upload and filtered search by tag display name,
but no response ever returned a tag value and nothing listed the
vocabulary, so a shipped feature dead-ended in the public API. A document
that failed processing could only be deleted and re-uploaded, and
retiring 500 documents cost 500 requests.
- GET /api/v2/knowledge/{id}/tags returns the vocabulary (display name,
slot, field type) as a full-set list.
- Document list and detail responses carry `tags`, keyed by display name
exactly as search keys its result metadata. Writes stay slot-keyed; the
tags endpoint is the mapping and the contract documents the split.
- PATCH /api/v2/knowledge/{id}/documents/{documentId} renames, enables,
disables, retags, or requeues processing. Derived indexing state is not
writable: asserting `processingStatus` on an unindexed document would
corrupt search. A retry may not ride along with field updates.
- PATCH /api/v2/knowledge/{id}/documents bulk-enables or bulk-disables.
Bulk delete is deliberately absent — that operation records no semantic
audit, and a public bulk delete would empty a knowledge base leaving no
DOCUMENT_DELETED entries.
- The document list accepts the same name-based `tagFilters` as search;
the name-to-slot resolver moves out of search into a shared helper, and
the filters are stamped into the offset cursor scope so a replayed
cursor cannot cross a filter change.
- Search accepts `rerankerEnabled`, `rerankerModel`, `rerankerInputCount`
and returns `rerankerScore`; `rerankerApiKey` and `skipUsageBilling`
stay unexposed. Every result now names its `knowledgeBaseId`.
knowledge.tags.list flips from workspaceApiKey 'deny' to 'allow' (and
gains the workspace_api_key principal kind) so it matches the sibling
reads knowledge.documents.list / read / search. The vocabulary is
required input for two operations a workspace key can already perform.
Every tag write stays human-delegated.
* fix(v2): name every 403 cause, unfork boolean params, close nested strictness holes
Four cross-cutting consistency gaps on the v2 public surface.
**403s now carry a machine-readable cause.** The conventions skill mandated
`error.details.code` on 403 and nothing emitted one, so a client had to
string-match prose to tell "raise this member's role" from "this workspace
refuses personal keys" from "buy an enterprise plan" — four different
remedies behind one status, and every message reword a silent break. The
vocabulary is a closed set, `FORBIDDEN_DETAIL_CODES`, with a `Record` of
descriptions beside it that the generated OpenAPI 403 description is built
from, so a code cannot reach the wire unpublished. Refusals throw
`ForbiddenOperationError` in the domain and `v2CaughtOrchestrationError` —
the function every v2 error policy falls through to — attaches the code, so a
route cannot forget it. The audit-log resolver distinguished four causes and
collapsed them into one; it now names each.
Cross-tenant refusals deliberately get no code: they are concealed as 404 and
naming their cause would hand back the existence signal the concealment
withholds.
**Two boolean query params rejoin the majority.** `?includeDeparted` and
`?includeOutput` were `'true'`/`'false'` string enums inherited from the
internal shapes they reused, while four sibling params were real booleans.
Both move to `booleanQueryFlagSchema`, which still coerces both strings — a
strict widening, so an existing caller is unaffected, and the spec stops
telling callers to send a string.
**Two nested strictness holes close.** `.strict()` binds the top level only,
so `sort: [{ field, direction, nulls: 'last' }]` was answered 200 with the
null-ordering request dropped, and an unknown key inside a saved view's
`config` was accepted and discarded — the headline `filter` bug one level
down. `sortSpecSchema`'s element and both view-config schemas are now strict.
Safe on the read side because `normalizeStoredViewConfig` projects the
schemaless stored blob onto the declared keys first, so a legacy row cannot
turn into a 500.
The two sort dialects stay as they are. `/logs` and `/workflows/{id}/runs`
have one sortable column, so there is no `sortBy` to pair with; renaming
`order` breaks every caller and an alias is a second spelling of one thing
with undefined precedence. Both contracts and the skill now state the rule.
* style: format the files the workspace-scoped lint gate does not reach
`turbo run lint:check` runs `biome check .` per workspace, so `scripts/` at the
repo root is outside the graph and four changed files were unformatted — one of
them a merge artifact from reconciling the route baseline across branches.
* fix(v2): collapse the four knowledge document projections onto one null-tolerant summary
Extracts toV2DocumentSummary in app/api/v2/knowledge/utils.ts and composes the
list, upload-acknowledgement and detail presenters from it. toV2TaggedDocument
serialized uploadedAt with a bare .toISOString(), so a document with no upload
timestamp threw where every sibling returned null and the contract declares the
field nullable.
Also consolidates the two Zod strictness walkers onto one shared introspection
helper that unwraps wrappers and expands unions, closing the hole where a
union-shaped schema answered null and was skipped by the pagination sweep.
* fix(v2): stop HEAD driving MCP discovery, and unbreak the updatedAt keyset page
B1: Next aliases HEAD onto GET, which RFC 9110 permits only because GET is safe.
The MCP tool-discovery GET is not: it opens a live connection to the registered
endpoint and writes the outcome onto the server row. The v2 JSON builder gains a
headSafe option, default true, and the discovery route declares itself unsafe —
a HEAD is authenticated and rate-limited, then answered bodiless.
B2: a discovery status write stamped updatedAt, which this branch added as a
keyset sort, so any concurrent discovery duplicated and skipped servers across a
caller's pages. Discovery liveness already has lastConnected, lastToolsRefresh,
lastError and statusConfig.
B4: a public refresh now skips the positive cache but keeps the failure cooldown,
so it cannot be used to drive a connection attempt per request at a failing
endpoint. An explicit user action on their own server keeps the full bypass.
B6: the consecutive-failure counter is incremented SQL-side rather than read,
incremented and written back, and the success branch carries the same workspace,
liveness and staleness guard the failure branch already had.
* fix(v2): bound the bulk update echo, close the search leak, and make the docs true
B3: a selectAll bulk document update echoed every changed identifier, which the
request does not bound — a 100k-document knowledge base produced a multi-megabyte
array, materialized and then element-wise validated. The use case now reports
whether the selection was unbounded and the presenter omits the echo.
A1: the knowledge search presenter spread the whole use-case result, which also
carries userId, workspaceId, a cost breakdown and a live secret-trace registry.
Only Zod's default key-stripping kept them off the wire. Projected explicitly.
P1-a: GET /knowledge/{id}/tags advertised all 17 slots while the document PATCH
accepted only the seven text ones. The writer already coerces every slot type,
so the PATCH now takes all 17 in their declared types, with a 400 where a
malformed value used to silently clear the tag.
P1-b: both new PATCHes deny workspace API keys and now say so.
P1-c: the two table query reads declare maxBodyBytes and now document the 413.
P1-d: getWorkflowDeploymentV2 loses its legacy suffix.
C3: deletes two orchestration error mappers with no callers that mapped
'forbidden' with no details.
D2: a stored null in table_views.config survived the pick and failed the
response schema.
Also folds the six 'bounded set' paraphrases onto one FULL_SET_LIST constant,
shares the run-window date bound between the logs and runs lists so their
documented parity is enforced rather than asserted, adds the missing barrel
export for FORBIDDEN_DETAIL_CODE_DESCRIPTIONS, and strictens two response
schemas whose peers were already strict.
Migrates 40 v2 route tests onto the shared @sim/testing harness: 26 asserted a
rateLimitSubjectIds shape v2 auth never returns, 26 asserted the wrong
refillRate, 33 could not exercise their 401 path at all, and 6 hard-wired the
rollout gate to null.
* fix(mcp): bound the connect handshake, and stop the 403 description over-claiming
B5: the connect clamp was getMaxExecutionTimeout(), the workflow ceiling of
seven days, so the real bound became the server row's own timeout — which the
registration contract permits up to 300s — times the connect retries. A slow
server could hold a Node request for roughly twenty minutes. Connecting is not a
workflow run, so the handshake now shares the one-minute ceiling tools/list
already applies to itself.
C2: the generated 403 description asserted that error.details.code names the
cause on every 403. Nine domain refusals still throw a bare forbidden
OrchestrationError and reach the wire codeless, so the wording now says 'where
the cause is one a caller can act on'. Reparenting those throws is left as a
deliberate change: one of them is a cross-tenant refusal that belongs in the
codeless class and would change its status.
* chore: reconcile the route ratchet with staging
* style: sort imports and format the three files biome flagged
* fix(openapi): import the forbidden-code constants from their module, not the application barrel
The barrel also re-exports the authorized use-case layer, which loads
@sim/db at import time. That pulled a database connection into the
OpenAPI spec check, so check:audits failed wherever DATABASE_URL is
absent, including CI.
4259 lines
151 KiB
JSON
4259 lines
151 KiB
JSON
{
|
||
"openapi": "3.1.0",
|
||
"info": {
|
||
"title": "Sim API v2 — Workspace Resources",
|
||
"description": "Version 2 of the Sim REST API for workspace metadata, members, MCP servers, skills, custom tools, credentials, and write-only secrets.",
|
||
"version": "2.0.0",
|
||
"contact": {
|
||
"name": "Sim Support",
|
||
"email": "help@sim.ai",
|
||
"url": "https://www.sim.ai"
|
||
},
|
||
"license": {
|
||
"name": "Apache 2.0",
|
||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
||
}
|
||
},
|
||
"servers": [
|
||
{
|
||
"url": "https://www.sim.ai",
|
||
"description": "Production"
|
||
}
|
||
],
|
||
"tags": [
|
||
{
|
||
"name": "Workspaces",
|
||
"description": "Read workspace metadata and its effective member roster."
|
||
},
|
||
{
|
||
"name": "MCP Servers",
|
||
"description": "Register and manage Model Context Protocol servers."
|
||
},
|
||
{
|
||
"name": "Skills",
|
||
"description": "Create and manage reusable instruction documents for agents."
|
||
},
|
||
{
|
||
"name": "Custom Tools",
|
||
"description": "Create and manage code-backed tools that agents can call."
|
||
},
|
||
{
|
||
"name": "Credentials",
|
||
"description": "List OAuth and service-account connections without secret material."
|
||
},
|
||
{
|
||
"name": "Secrets",
|
||
"description": "Set and manage write-only workspace and personal secret values."
|
||
}
|
||
],
|
||
"security": [
|
||
{
|
||
"apiKey": []
|
||
}
|
||
],
|
||
"paths": {
|
||
"/api/v2/workspaces/{workspaceId}": {
|
||
"get": {
|
||
"operationId": "getWorkspace",
|
||
"summary": "Get Workspace",
|
||
"description": "Return public metadata for one accessible workspace. Governance identities, billing identities, and internal membership identifiers are intentionally omitted.",
|
||
"tags": ["Workspaces"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Workspace to retrieve.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace to retrieve."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Public workspace metadata.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/GetWorkspaceResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/workspaces/{workspaceId}/members": {
|
||
"get": {
|
||
"operationId": "listWorkspaceMembers",
|
||
"summary": "List Workspace Members",
|
||
"description": "List the workspace's effective members ordered by email. Explicit workspace grants and inherited organization-administrator grants are merged; internal membership and billing identities are omitted.",
|
||
"tags": ["Workspaces"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Workspace to retrieve.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace to retrieve."
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum members to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"schema": {
|
||
"default": 50,
|
||
"description": "Maximum members to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 100
|
||
}
|
||
},
|
||
{
|
||
"name": "cursor",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"schema": {
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"type": "string",
|
||
"minLength": 1
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "An email-ordered page of effective workspace members.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ListWorkspaceMembersResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/mcp-servers": {
|
||
"get": {
|
||
"operationId": "listMcpServers",
|
||
"summary": "List MCP Servers",
|
||
"description": "List MCP servers registered in a workspace. Request-header values and OAuth client secrets are never returned. Nothing caps how many servers a workspace registers, so this list is paginated: paginate with `limit` and `cursor`, stopping when `nextCursor` is null. `connectionStatus`, `toolCount`, `lastError`, and `lastToolsRefresh` describe the most recent tool discovery and stay at their registration defaults until one runs — call `GET /api/v2/mcp-servers/{id}/tools` to run it.",
|
||
"tags": ["MCP Servers"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the MCP server.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the MCP server."
|
||
}
|
||
},
|
||
{
|
||
"name": "search",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Case-insensitive substring match against the server name.",
|
||
"schema": {
|
||
"description": "Case-insensitive substring match against the server name.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 200
|
||
}
|
||
},
|
||
{
|
||
"name": "sortBy",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Field used to sort the result.",
|
||
"schema": {
|
||
"default": "createdAt",
|
||
"description": "Field used to sort the result.",
|
||
"type": "string",
|
||
"enum": ["name", "createdAt", "updatedAt"]
|
||
}
|
||
},
|
||
{
|
||
"name": "sortOrder",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Sort direction.",
|
||
"schema": {
|
||
"default": "desc",
|
||
"description": "Sort direction.",
|
||
"type": "string",
|
||
"enum": ["asc", "desc"]
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum MCP servers to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"schema": {
|
||
"default": 50,
|
||
"description": "Maximum MCP servers to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 100
|
||
}
|
||
},
|
||
{
|
||
"name": "cursor",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"schema": {
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"type": "string",
|
||
"minLength": 1
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "MCP servers registered in the workspace.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ListMcpServersResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"post": {
|
||
"operationId": "createMcpServer",
|
||
"summary": "Create MCP Server",
|
||
"description": "Register an MCP server in a workspace. The endpoint URL determines server identity, must be absolute HTTP or HTTPS, and cannot contain environment-variable references. Header values and OAuth client secrets are write-only. `transport`, `timeout`, `retries`, and `enabled` are applied server-side when omitted; the effective values are in the response.",
|
||
"tags": ["MCP Servers"],
|
||
"requestBody": {
|
||
"required": true,
|
||
"description": "Configuration for a new MCP server.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/CreateMcpServerRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"201": {
|
||
"description": "The MCP server was registered.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/CreateMcpServerResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"401": {
|
||
"$ref": "#/components/responses/Unauthorized"
|
||
},
|
||
"403": {
|
||
"$ref": "#/components/responses/Forbidden"
|
||
},
|
||
"404": {
|
||
"$ref": "#/components/responses/NotFound"
|
||
},
|
||
"409": {
|
||
"$ref": "#/components/responses/Conflict"
|
||
},
|
||
"429": {
|
||
"$ref": "#/components/responses/RateLimited"
|
||
},
|
||
"500": {
|
||
"$ref": "#/components/responses/InternalError"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/mcp-servers/{id}": {
|
||
"get": {
|
||
"operationId": "getMcpServer",
|
||
"summary": "Get MCP Server",
|
||
"description": "Fetch one MCP server by identifier. Request-header values and OAuth client secrets are never returned.",
|
||
"tags": ["MCP Servers"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "MCP server the operation acts on.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "MCP server the operation acts on."
|
||
}
|
||
},
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the MCP server.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the MCP server."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "The MCP server.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/GetMcpServerResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"patch": {
|
||
"operationId": "updateMcpServer",
|
||
"summary": "Update MCP Server",
|
||
"description": "Update the supplied MCP server fields. The URL is immutable because it determines server identity; delete and recreate the server to change endpoints. Two fields do not follow the omitted-fields-are-retained rule. `headers` is replaced wholesale rather than merged: sending it drops every stored header it does not repeat, and the only way to keep a header is to resend it. Changing `oauthClientId`, or sending `oauthClientSecret` as null or a new value, revokes the stored OAuth grant and forces reauthorization; switching away from OAuth authentication revokes it too.",
|
||
"tags": ["MCP Servers"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "MCP server the operation acts on.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "MCP server the operation acts on."
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"description": "MCP server fields to change; omitted fields retain their stored values.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/UpdateMcpServerRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "The updated MCP server.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/UpdateMcpServerResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"delete": {
|
||
"operationId": "deleteMcpServer",
|
||
"summary": "Delete MCP Server",
|
||
"description": "Remove an MCP server and revoke its OAuth tokens. Workflows retain blocks that referenced the server's tools, but those tools can no longer be called.",
|
||
"tags": ["MCP Servers"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "MCP server the operation acts on.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "MCP server the operation acts on."
|
||
}
|
||
},
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the MCP server.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the MCP server."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "The MCP server was deleted.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/DeleteMcpServerResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/mcp-servers/{id}/tools": {
|
||
"get": {
|
||
"operationId": "listMcpServerTools",
|
||
"summary": "List MCP Server Tools",
|
||
"description": "Connect to a registered MCP server and return the tools it exposes. Unlike most reads this one has side effects: it opens a live connection to the third-party server and writes `connectionStatus`, `toolCount`, `lastError`, and `lastToolsRefresh` on the server resource, so registering a server and then calling this completes onboarding without opening the Sim UI. Because the pass is not a safe read, a `HEAD` request is answered with an empty `200` without connecting or writing, so it reports only that the endpoint exists and the caller is authorized. Results are served from a short-lived per-workspace cache, so an uncached call reflects whichever workspace member last ran discovery; pass `refresh=true` to reconnect under your own credentials and pick up tools added since the last pass, at the cost of a live round trip to the server. The set is bounded by discovery itself — at most 1,000 tools and 5 MB of tool payload per server. The bounded set is returned in one page with `nextCursor` always null; there is no second page to fetch. An unreachable, slow, or cooling-down server is a `503`; a server whose stored OAuth grant no longer works is a `409` with `error.details.code` `MCP_SERVER_REAUTHORIZATION_REQUIRED`, meaning the registration is intact but a human must reauthorize it in Sim — your API key is fine and re-issuing it changes nothing. A workspace API key cannot call this operation and is rejected with `403`; use a personal API key. Discovery resolves the calling user's own OAuth credentials for the server, which a workspace key cannot supply — so a workspace key that can register a server cannot list its tools.",
|
||
"tags": ["MCP Servers"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "MCP server the operation acts on.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "MCP server the operation acts on."
|
||
}
|
||
},
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the MCP server.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the MCP server."
|
||
}
|
||
},
|
||
{
|
||
"name": "refresh",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Bypass the cached tool list and reconnect to the server. Slower, and the only way to pick up a tool added since the last refresh.",
|
||
"schema": {
|
||
"description": "Bypass the cached tool list and reconnect to the server. Slower, and the only way to pick up a tool added since the last refresh.",
|
||
"type": "boolean"
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Tools exposed by the MCP server.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ListMcpServerToolsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"401": {
|
||
"$ref": "#/components/responses/Unauthorized"
|
||
},
|
||
"403": {
|
||
"$ref": "#/components/responses/Forbidden"
|
||
},
|
||
"404": {
|
||
"$ref": "#/components/responses/NotFound"
|
||
},
|
||
"409": {
|
||
"$ref": "#/components/responses/Conflict"
|
||
},
|
||
"429": {
|
||
"$ref": "#/components/responses/RateLimited"
|
||
},
|
||
"500": {
|
||
"$ref": "#/components/responses/InternalError"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/skills": {
|
||
"get": {
|
||
"operationId": "listSkills",
|
||
"summary": "List Skills",
|
||
"description": "List workspace and built-in skills. Built-ins are marked read-only. The list omits skill bodies; fetch one skill to read its content. Paginate with `limit` and `cursor`, stopping when `nextCursor` is null.",
|
||
"tags": ["Skills"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the skill.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the skill."
|
||
}
|
||
},
|
||
{
|
||
"name": "search",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Case-insensitive substring match against the skill name.",
|
||
"schema": {
|
||
"description": "Case-insensitive substring match against the skill name.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 200
|
||
}
|
||
},
|
||
{
|
||
"name": "sortBy",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Field used to sort the result.",
|
||
"schema": {
|
||
"default": "createdAt",
|
||
"description": "Field used to sort the result.",
|
||
"type": "string",
|
||
"enum": ["name", "createdAt", "updatedAt"]
|
||
}
|
||
},
|
||
{
|
||
"name": "sortOrder",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Sort direction.",
|
||
"schema": {
|
||
"default": "desc",
|
||
"description": "Sort direction.",
|
||
"type": "string",
|
||
"enum": ["asc", "desc"]
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum skills to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"schema": {
|
||
"default": 50,
|
||
"description": "Maximum skills to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 100
|
||
}
|
||
},
|
||
{
|
||
"name": "cursor",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"schema": {
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"type": "string",
|
||
"minLength": 1
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Skills available in the workspace.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ListSkillsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"post": {
|
||
"operationId": "createSkill",
|
||
"summary": "Create Skill",
|
||
"description": "Create one skill in a workspace. Its kebab-case name must be unique and cannot be reserved by a built-in skill. Note that a workspace API key may create a skill but may not later update or delete it.",
|
||
"tags": ["Skills"],
|
||
"requestBody": {
|
||
"required": true,
|
||
"description": "Definition of a new skill.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/CreateSkillRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"201": {
|
||
"description": "The skill was created.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/CreateSkillResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"401": {
|
||
"$ref": "#/components/responses/Unauthorized"
|
||
},
|
||
"403": {
|
||
"$ref": "#/components/responses/Forbidden"
|
||
},
|
||
"404": {
|
||
"$ref": "#/components/responses/NotFound"
|
||
},
|
||
"409": {
|
||
"$ref": "#/components/responses/Conflict"
|
||
},
|
||
"429": {
|
||
"$ref": "#/components/responses/RateLimited"
|
||
},
|
||
"500": {
|
||
"$ref": "#/components/responses/InternalError"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/skills/{id}": {
|
||
"get": {
|
||
"operationId": "getSkill",
|
||
"summary": "Get Skill",
|
||
"description": "Fetch one workspace or built-in skill, including its full content. Built-in skills are marked read-only.",
|
||
"tags": ["Skills"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Skill to retrieve, update, or delete. Built-in skills use their name as the id.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Skill to retrieve, update, or delete. Built-in skills use their name as the id."
|
||
}
|
||
},
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the skill.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the skill."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "The skill.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/GetSkillResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"patch": {
|
||
"operationId": "updateSkill",
|
||
"summary": "Update Skill",
|
||
"description": "Update the supplied fields on a workspace skill. Omitted fields retain their stored values. Built-in skills are read-only. A workspace API key cannot call this operation and is rejected with `403`; use a personal API key.",
|
||
"tags": ["Skills"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Skill to retrieve, update, or delete. Built-in skills use their name as the id.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Skill to retrieve, update, or delete. Built-in skills use their name as the id."
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"description": "Skill fields to change; at least one editable field is required.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/UpdateSkillRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "The updated skill.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/UpdateSkillResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"401": {
|
||
"$ref": "#/components/responses/Unauthorized"
|
||
},
|
||
"403": {
|
||
"$ref": "#/components/responses/Forbidden"
|
||
},
|
||
"404": {
|
||
"$ref": "#/components/responses/NotFound"
|
||
},
|
||
"409": {
|
||
"$ref": "#/components/responses/Conflict"
|
||
},
|
||
"429": {
|
||
"$ref": "#/components/responses/RateLimited"
|
||
},
|
||
"500": {
|
||
"$ref": "#/components/responses/InternalError"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"delete": {
|
||
"operationId": "deleteSkill",
|
||
"summary": "Delete Skill",
|
||
"description": "Delete a workspace skill. Built-in skills are read-only and cannot be deleted. A workspace API key cannot call this operation and is rejected with `403`; use a personal API key.",
|
||
"tags": ["Skills"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Skill to retrieve, update, or delete. Built-in skills use their name as the id.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Skill to retrieve, update, or delete. Built-in skills use their name as the id."
|
||
}
|
||
},
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the skill.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the skill."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "The skill was deleted.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/DeleteSkillResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/custom-tools": {
|
||
"get": {
|
||
"operationId": "listCustomTools",
|
||
"summary": "List Custom Tools",
|
||
"description": "List code-backed custom tools defined in a workspace. Legacy personal tools are excluded. Paginate with `limit` and `cursor`, stopping when `nextCursor` is null.",
|
||
"tags": ["Custom Tools"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the custom tool.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the custom tool."
|
||
}
|
||
},
|
||
{
|
||
"name": "search",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Case-insensitive substring match against the tool title.",
|
||
"schema": {
|
||
"description": "Case-insensitive substring match against the tool title.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 200
|
||
}
|
||
},
|
||
{
|
||
"name": "sortBy",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Field used to sort the result.",
|
||
"schema": {
|
||
"default": "createdAt",
|
||
"description": "Field used to sort the result.",
|
||
"type": "string",
|
||
"enum": ["title", "createdAt", "updatedAt"]
|
||
}
|
||
},
|
||
{
|
||
"name": "sortOrder",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Sort direction.",
|
||
"schema": {
|
||
"default": "desc",
|
||
"description": "Sort direction.",
|
||
"type": "string",
|
||
"enum": ["asc", "desc"]
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum custom tools to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"schema": {
|
||
"default": 50,
|
||
"description": "Maximum custom tools to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 100
|
||
}
|
||
},
|
||
{
|
||
"name": "cursor",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"schema": {
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"type": "string",
|
||
"minLength": 1
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Custom tools defined in the workspace.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ListCustomToolsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"post": {
|
||
"operationId": "createCustomTool",
|
||
"summary": "Create Custom Tool",
|
||
"description": "Create a code-backed custom tool in a workspace. Its title must be unique because tools resolve by title at call time.",
|
||
"tags": ["Custom Tools"],
|
||
"requestBody": {
|
||
"required": true,
|
||
"description": "Definition and implementation of a new custom tool.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/CreateCustomToolRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"201": {
|
||
"description": "The custom tool was created.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/CreateCustomToolResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"401": {
|
||
"$ref": "#/components/responses/Unauthorized"
|
||
},
|
||
"403": {
|
||
"$ref": "#/components/responses/Forbidden"
|
||
},
|
||
"404": {
|
||
"$ref": "#/components/responses/NotFound"
|
||
},
|
||
"409": {
|
||
"$ref": "#/components/responses/Conflict"
|
||
},
|
||
"429": {
|
||
"$ref": "#/components/responses/RateLimited"
|
||
},
|
||
"500": {
|
||
"$ref": "#/components/responses/InternalError"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/custom-tools/{id}": {
|
||
"get": {
|
||
"operationId": "getCustomTool",
|
||
"summary": "Get Custom Tool",
|
||
"description": "Fetch one custom tool by identifier, scoped to its workspace.",
|
||
"tags": ["Custom Tools"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Custom tool to retrieve, update, or delete.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Custom tool to retrieve, update, or delete."
|
||
}
|
||
},
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the custom tool.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the custom tool."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "The custom tool.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/GetCustomToolResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"patch": {
|
||
"operationId": "updateCustomTool",
|
||
"summary": "Update Custom Tool",
|
||
"description": "Update the supplied custom tool fields. Omitted fields retain their stored values, and titles must remain unique within the workspace.",
|
||
"tags": ["Custom Tools"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Custom tool to retrieve, update, or delete.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Custom tool to retrieve, update, or delete."
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"description": "Custom tool fields to change; at least one editable field is required.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/UpdateCustomToolRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "The updated custom tool.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/UpdateCustomToolResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"401": {
|
||
"$ref": "#/components/responses/Unauthorized"
|
||
},
|
||
"403": {
|
||
"$ref": "#/components/responses/Forbidden"
|
||
},
|
||
"404": {
|
||
"$ref": "#/components/responses/NotFound"
|
||
},
|
||
"409": {
|
||
"$ref": "#/components/responses/Conflict"
|
||
},
|
||
"429": {
|
||
"$ref": "#/components/responses/RateLimited"
|
||
},
|
||
"500": {
|
||
"$ref": "#/components/responses/InternalError"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"delete": {
|
||
"operationId": "deleteCustomTool",
|
||
"summary": "Delete Custom Tool",
|
||
"description": "Delete a custom tool. Agent blocks retain their configuration but can no longer call the deleted tool.",
|
||
"tags": ["Custom Tools"],
|
||
"parameters": [
|
||
{
|
||
"name": "id",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Custom tool to retrieve, update, or delete.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Custom tool to retrieve, update, or delete."
|
||
}
|
||
},
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace that owns the custom tool.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the custom tool."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "The custom tool was deleted.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/DeleteCustomToolResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/credentials": {
|
||
"get": {
|
||
"operationId": "listCredentials",
|
||
"summary": "List Credentials",
|
||
"description": "List OAuth and service-account connections visible to the caller. Secret material is never returned. Credential mutations and single-resource reads are intentionally not exposed. Paginate with `limit` and `cursor`, stopping when `nextCursor` is null.",
|
||
"tags": ["Credentials"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace whose credentials should be listed.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace whose credentials should be listed."
|
||
}
|
||
},
|
||
{
|
||
"name": "type",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Restrict results to this credential type.",
|
||
"schema": {
|
||
"description": "Restrict results to this credential type.",
|
||
"type": "string",
|
||
"enum": ["oauth", "service_account"]
|
||
}
|
||
},
|
||
{
|
||
"name": "providerId",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Restrict results to credentials for this integration provider.",
|
||
"schema": {
|
||
"description": "Restrict results to credentials for this integration provider.",
|
||
"type": "string",
|
||
"minLength": 1
|
||
}
|
||
},
|
||
{
|
||
"name": "search",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Case-insensitive substring match against the credential display name.",
|
||
"schema": {
|
||
"description": "Case-insensitive substring match against the credential display name.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 200
|
||
}
|
||
},
|
||
{
|
||
"name": "sortBy",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Field used to sort the result.",
|
||
"schema": {
|
||
"default": "createdAt",
|
||
"description": "Field used to sort the result.",
|
||
"type": "string",
|
||
"enum": ["displayName", "createdAt", "updatedAt"]
|
||
}
|
||
},
|
||
{
|
||
"name": "sortOrder",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Sort direction.",
|
||
"schema": {
|
||
"default": "desc",
|
||
"description": "Sort direction.",
|
||
"type": "string",
|
||
"enum": ["asc", "desc"]
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum credentials to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"schema": {
|
||
"default": 50,
|
||
"description": "Maximum credentials to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 100
|
||
}
|
||
},
|
||
{
|
||
"name": "cursor",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"schema": {
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"type": "string",
|
||
"minLength": 1
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Credentials visible to the caller.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ListCredentialsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/secrets": {
|
||
"get": {
|
||
"operationId": "listSecrets",
|
||
"summary": "List Secrets",
|
||
"description": "List workspace and caller-owned personal secret metadata. Only names, scope, role, and timestamps are returned; secret values are never read or returned. Paginate with `limit` and `cursor`, stopping when `nextCursor` is null. A workspace API key cannot call this operation and is rejected with `403`; use a personal API key.",
|
||
"tags": ["Secrets"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace whose secret metadata should be listed.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace whose secret metadata should be listed."
|
||
}
|
||
},
|
||
{
|
||
"name": "scope",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Restrict results to one ownership scope.",
|
||
"schema": {
|
||
"description": "Restrict results to one ownership scope.",
|
||
"type": "string",
|
||
"enum": ["workspace", "personal"]
|
||
}
|
||
},
|
||
{
|
||
"name": "search",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Case-insensitive substring match against the secret name.",
|
||
"schema": {
|
||
"description": "Case-insensitive substring match against the secret name.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 200
|
||
}
|
||
},
|
||
{
|
||
"name": "sortBy",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Field used to sort the result.",
|
||
"schema": {
|
||
"default": "name",
|
||
"description": "Field used to sort the result.",
|
||
"type": "string",
|
||
"enum": ["name", "createdAt", "updatedAt"]
|
||
}
|
||
},
|
||
{
|
||
"name": "sortOrder",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Sort direction.",
|
||
"schema": {
|
||
"default": "asc",
|
||
"description": "Sort direction.",
|
||
"type": "string",
|
||
"enum": ["asc", "desc"]
|
||
}
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Maximum secrets to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"schema": {
|
||
"default": 50,
|
||
"description": "Maximum secrets to return per page. Must be a whole number from 1 to 100. Defaults to 50.",
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"maximum": 100
|
||
}
|
||
},
|
||
{
|
||
"name": "cursor",
|
||
"in": "query",
|
||
"required": false,
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"schema": {
|
||
"description": "Opaque cursor returned by the previous page.",
|
||
"type": "string",
|
||
"minLength": 1
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Secret metadata visible to the caller.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ListSecretsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/secrets/{name}": {
|
||
"put": {
|
||
"operationId": "setSecret",
|
||
"summary": "Set Secret",
|
||
"description": "Create or replace a workspace or caller-owned personal secret. The value is encrypted at rest, is write-only, and is never included in the response. A workspace API key cannot call this operation and is rejected with `403`; use a personal API key.",
|
||
"tags": ["Secrets"],
|
||
"parameters": [
|
||
{
|
||
"name": "name",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Secret to create, replace, or delete.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 255,
|
||
"pattern": "^[A-Za-z0-9_]+$",
|
||
"description": "Secret to create, replace, or delete."
|
||
}
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"description": "Ownership scope and write-only value for the secret.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/SetSecretRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "The existing secret value was replaced.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/SetSecretResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"201": {
|
||
"description": "The secret was created.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/SetSecretResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
},
|
||
"delete": {
|
||
"operationId": "deleteSecret",
|
||
"summary": "Delete Secret",
|
||
"description": "Delete a workspace or caller-owned personal secret without reading or returning its stored value. A workspace API key cannot call this operation and is rejected with `403`; use a personal API key.",
|
||
"tags": ["Secrets"],
|
||
"parameters": [
|
||
{
|
||
"name": "name",
|
||
"in": "path",
|
||
"required": true,
|
||
"description": "Secret to create, replace, or delete.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 255,
|
||
"pattern": "^[A-Za-z0-9_]+$",
|
||
"description": "Secret to create, replace, or delete."
|
||
}
|
||
},
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Workspace in which the secret is available.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace in which the secret is available."
|
||
}
|
||
},
|
||
{
|
||
"name": "scope",
|
||
"in": "query",
|
||
"required": true,
|
||
"description": "Whether the secret belongs to the workspace or the caller.",
|
||
"schema": {
|
||
"type": "string",
|
||
"enum": ["workspace", "personal"],
|
||
"description": "Whether the secret belongs to the workspace or the caller."
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "The secret was deleted.",
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"$ref": "#/components/headers/X-RateLimit-Limit"
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"$ref": "#/components/headers/X-RateLimit-Remaining"
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"$ref": "#/components/headers/X-RateLimit-Reset"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/DeleteSecretResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"$ref": "#/components/responses/BadRequest"
|
||
},
|
||
"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"
|
||
},
|
||
"503": {
|
||
"$ref": "#/components/responses/ServiceUnavailable"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"components": {
|
||
"securitySchemes": {
|
||
"apiKey": {
|
||
"type": "apiKey",
|
||
"in": "header",
|
||
"name": "X-API-Key",
|
||
"description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
|
||
}
|
||
},
|
||
"headers": {
|
||
"X-RateLimit-Limit": {
|
||
"description": "Maximum requests allowed in the current window.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 9007199254740991,
|
||
"title": "Rate limit",
|
||
"description": "Maximum requests allowed in the current window."
|
||
}
|
||
},
|
||
"X-RateLimit-Remaining": {
|
||
"description": "Requests remaining in the current window.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 9007199254740991,
|
||
"title": "Rate limit remaining",
|
||
"description": "Requests remaining in the current window."
|
||
}
|
||
},
|
||
"X-RateLimit-Reset": {
|
||
"description": "ISO 8601 timestamp when the current rate-limit window resets.",
|
||
"schema": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"title": "Rate limit reset",
|
||
"description": "ISO 8601 timestamp when the current rate-limit window resets."
|
||
}
|
||
},
|
||
"Retry-After": {
|
||
"description": "Seconds to wait before retrying. Sent on `429` (derived from the caller rate-limit window) and on `503` (a fixed transient-failure floor). Add jitter rather than retrying at exactly this offset.",
|
||
"schema": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 9007199254740991,
|
||
"title": "Retry after",
|
||
"description": "Seconds to wait before retrying. Sent on `429` (derived from the caller rate-limit window) and on `503` (a fixed transient-failure floor). Add jitter rather than retrying at exactly this offset."
|
||
}
|
||
},
|
||
"X-Run-Id": {
|
||
"description": "Identifier assigned to the workflow run.",
|
||
"schema": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"title": "Run identifier",
|
||
"description": "Identifier assigned to the workflow run."
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"BadRequest": {
|
||
"description": "The request is invalid.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Unauthorized": {
|
||
"description": "The API key is missing or invalid.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"UsageLimitExceeded": {
|
||
"description": "The workspace has exceeded its usage or billing limits.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Forbidden": {
|
||
"description": "The caller lacks the rights this operation requires. Where the cause is one a caller can act on, `error.details.code` names it, drawn from a closed set:\n- `INSUFFICIENT_WORKSPACE_ROLE` — The caller has access to the workspace but its role is below the one this operation requires.\n- `PERSONAL_API_KEYS_DISABLED` — The workspace's organization does not allow personal API keys. Use a workspace API key.\n- `WORKSPACE_KEY_OPERATION_NOT_PERMITTED` — This operation is not available to a workspace-scoped API key. Use a personal API key.\n- `PRINCIPAL_KIND_NOT_PERMITTED` — This operation does not accept the caller’s kind of API key.\n- `ORGANIZATION_MEMBERSHIP_REQUIRED` — The caller is not a member of the organization it named.\n- `ORGANIZATION_ADMIN_REQUIRED` — The caller is a member of the organization but not an admin or owner.\n- `ENTERPRISE_PLAN_REQUIRED` — The organization has no active enterprise subscription.\n- `AUDIT_LOGS_DISABLED` — Audit logging is not enabled for this deployment.\n- `SKILL_EDITOR_ACCESS_REQUIRED` — The caller can write in the workspace but is not an editor of this skill.\n- `MCP_SERVER_URL_NOT_ALLOWED` — The supplied MCP server URL is outside the allowed domains or resolves to an internal address.\nA resource in a workspace the caller cannot reach at all answers `404`, not `403`, so absence and denial are indistinguishable to a caller who was never entitled to tell them apart.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"NotFound": {
|
||
"description": "The requested resource was not found.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Conflict": {
|
||
"description": "The request conflicts with current resource state.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"RunIdConflict": {
|
||
"description": "The run cannot be started. Two causes share this status, distinguished by `error.details.code`: `RUN_ID_CONFLICT` when the supplied `X-Run-Id` is already associated with a different request, and `CALL_CHAIN_DEPTH_EXCEEDED` when the incoming `X-Sim-Via` chain has already reached the maximum workflow-to-workflow call depth.",
|
||
"headers": {
|
||
"X-Run-Id": {
|
||
"$ref": "#/components/headers/X-Run-Id"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Gone": {
|
||
"description": "The requested generated resource has expired.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"PayloadTooLarge": {
|
||
"description": "The request, or a resource collection it must materialize, exceeds the allowed size. Besides an oversized request body, this covers a generated artifact that renders past the download ceiling and a workspace folder tree too large to load in full.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"UnsupportedMediaType": {
|
||
"description": "The request uses an unsupported media type.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"Locked": {
|
||
"description": "The resource is locked and cannot be modified.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"RateLimited": {
|
||
"description": "The caller exceeded the request rate limit.",
|
||
"headers": {
|
||
"Retry-After": {
|
||
"$ref": "#/components/headers/Retry-After"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"ClientClosedRequest": {
|
||
"description": "The client closed the connection before the response was produced.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"InternalError": {
|
||
"description": "An unexpected server error occurred.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"ServiceUnavailable": {
|
||
"description": "A required service is temporarily unavailable. The condition is transient, so the response normally carries `Retry-After` with the number of seconds to wait; treat that value as a floor and add jitter before retrying. One case deliberately omits the header: when `error.details.code` is `ASYNC_ENQUEUE_AMBIGUOUS`, the run may already have started, so retrying could start and bill a second run. Reconcile against the returned run id instead of retrying.",
|
||
"headers": {
|
||
"Retry-After": {
|
||
"$ref": "#/components/headers/Retry-After"
|
||
}
|
||
},
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/V2Error"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"schemas": {
|
||
"V2Error": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": {
|
||
"type": "object",
|
||
"properties": {
|
||
"code": {
|
||
"type": "string",
|
||
"description": "Stable machine-readable error code."
|
||
},
|
||
"message": {
|
||
"type": "string",
|
||
"description": "Human-readable explanation of the error."
|
||
},
|
||
"details": {
|
||
"description": "Optional structured error details."
|
||
}
|
||
},
|
||
"required": ["code", "message"],
|
||
"additionalProperties": false,
|
||
"description": "Canonical error details."
|
||
}
|
||
},
|
||
"required": ["error"],
|
||
"additionalProperties": false,
|
||
"title": "v2 error response",
|
||
"description": "Canonical error envelope returned by the public v2 API.",
|
||
"examples": [
|
||
{
|
||
"error": {
|
||
"code": "BAD_REQUEST",
|
||
"message": "The request is invalid."
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"V2Workspace": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Unique workspace identifier."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Workspace display name."
|
||
},
|
||
"color": {
|
||
"type": "string",
|
||
"description": "Workspace color as a hexadecimal color value."
|
||
},
|
||
"logoUrl": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Workspace logo URL, or null when none is configured."
|
||
},
|
||
"memberCount": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 9007199254740991,
|
||
"description": "Number of effective members, including inherited organization administrators."
|
||
},
|
||
"createdAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the workspace was created."
|
||
},
|
||
"updatedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the workspace was last updated."
|
||
}
|
||
},
|
||
"required": ["id", "name", "color", "logoUrl", "memberCount", "createdAt", "updatedAt"],
|
||
"additionalProperties": false,
|
||
"title": "Workspace",
|
||
"description": "Public metadata for an accessible workspace."
|
||
},
|
||
"GetWorkspaceResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2Workspace"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Get workspace response",
|
||
"description": "Public metadata for one workspace.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"name": "Engineering",
|
||
"color": "#33C482",
|
||
"logoUrl": null,
|
||
"memberCount": 14,
|
||
"createdAt": "2026-01-15T10:30:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"V2WorkspaceMember": {
|
||
"type": "object",
|
||
"properties": {
|
||
"email": {
|
||
"type": "string",
|
||
"format": "email",
|
||
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
|
||
"description": "Member email address and public member identifier."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Member display name."
|
||
},
|
||
"image": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Member profile image URL, or null when absent."
|
||
},
|
||
"role": {
|
||
"type": "string",
|
||
"enum": ["admin", "write", "read"],
|
||
"description": "Effective role in the workspace."
|
||
},
|
||
"isExternal": {
|
||
"type": "boolean",
|
||
"description": "Whether the member belongs to a different organization than the workspace. True for an explicitly granted member whose own organization differs from the workspace's; false for the workspace owner and for a member sharing the workspace organization. Inherited organization-administrator access is always reported as false, so this is not a signal that access came from outside the explicit member list."
|
||
},
|
||
"joinedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when access was granted."
|
||
}
|
||
},
|
||
"required": ["email", "name", "image", "role", "isExternal", "joinedAt"],
|
||
"additionalProperties": false,
|
||
"title": "Workspace member",
|
||
"description": "An effective workspace member and their public access role."
|
||
},
|
||
"ListWorkspaceMembersResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/V2WorkspaceMember"
|
||
},
|
||
"description": "Items in the current page."
|
||
},
|
||
"nextCursor": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Opaque cursor for the next page: send it back as `cursor` to continue, and stop when it is null. Most v2 lists page, so null means the last page was reached. A few are full-set lists that return their whole bounded result in one response and therefore always report null; those say so in the operation description. Either way, null means there is nothing further to fetch — never construct a cursor yourself."
|
||
}
|
||
},
|
||
"required": ["data", "nextCursor"],
|
||
"additionalProperties": false,
|
||
"title": "List workspace members response",
|
||
"description": "A cursor-paginated page of effective workspace members.",
|
||
"examples": [
|
||
{
|
||
"data": [
|
||
{
|
||
"email": "jane@example.com",
|
||
"name": "Jane Smith",
|
||
"image": null,
|
||
"role": "admin",
|
||
"isExternal": false,
|
||
"joinedAt": "2026-01-15T10:30:00.000Z"
|
||
}
|
||
],
|
||
"nextCursor": null
|
||
}
|
||
]
|
||
},
|
||
"V2McpServer": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Unique server identifier derived from the workspace and endpoint URL."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Server display name."
|
||
},
|
||
"description": {
|
||
"description": "Optional server description.",
|
||
"type": "string"
|
||
},
|
||
"transport": {
|
||
"default": "streamable-http",
|
||
"description": "Transport used to communicate with the server.",
|
||
"type": "string",
|
||
"enum": ["streamable-http"]
|
||
},
|
||
"authType": {
|
||
"description": "Authentication method used by the server.",
|
||
"type": "string",
|
||
"enum": ["none", "headers", "oauth"]
|
||
},
|
||
"url": {
|
||
"description": "Server endpoint URL.",
|
||
"type": "string"
|
||
},
|
||
"timeout": {
|
||
"description": "Per-request timeout in milliseconds.",
|
||
"type": "number"
|
||
},
|
||
"retries": {
|
||
"description": "Number of retries attempted per request.",
|
||
"type": "number"
|
||
},
|
||
"enabled": {
|
||
"type": "boolean",
|
||
"description": "Whether the server tools are available to workflows."
|
||
},
|
||
"connectionStatus": {
|
||
"description": "Result of the most recent connection attempt.",
|
||
"type": "string",
|
||
"enum": ["connected", "disconnected", "error"]
|
||
},
|
||
"lastError": {
|
||
"description": "Message from the most recent failed connection, or null when absent.",
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
]
|
||
},
|
||
"toolCount": {
|
||
"description": "Number of tools discovered on the server.",
|
||
"type": "number"
|
||
},
|
||
"lastToolsRefresh": {
|
||
"description": "ISO 8601 timestamp of the most recent tool-list refresh.",
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
||
},
|
||
"lastConnected": {
|
||
"description": "ISO 8601 timestamp of the most recent successful connection.",
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
||
},
|
||
"createdAt": {
|
||
"description": "ISO 8601 timestamp when the server was registered.",
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
||
},
|
||
"updatedAt": {
|
||
"description": "ISO 8601 timestamp when the server was last updated.",
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
||
},
|
||
"oauthClientId": {
|
||
"description": "Pre-registered OAuth client identifier, when configured.",
|
||
"type": "string"
|
||
},
|
||
"hasHeaders": {
|
||
"type": "boolean",
|
||
"description": "Whether any request headers are configured."
|
||
},
|
||
"headerNames": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string",
|
||
"description": "Configured header name."
|
||
},
|
||
"description": "Names of configured request headers. Header values are never returned."
|
||
},
|
||
"hasOauthClientSecret": {
|
||
"type": "boolean",
|
||
"description": "Whether an OAuth client secret is stored. The value is never returned."
|
||
}
|
||
},
|
||
"required": [
|
||
"id",
|
||
"name",
|
||
"transport",
|
||
"enabled",
|
||
"createdAt",
|
||
"updatedAt",
|
||
"hasHeaders",
|
||
"headerNames",
|
||
"hasOauthClientSecret"
|
||
],
|
||
"additionalProperties": false,
|
||
"title": "MCP server",
|
||
"description": "Public MCP server configuration without write-only credential values."
|
||
},
|
||
"ListMcpServersResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/V2McpServer"
|
||
},
|
||
"description": "Items in the current page."
|
||
},
|
||
"nextCursor": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Opaque cursor for the next page: send it back as `cursor` to continue, and stop when it is null. Most v2 lists page, so null means the last page was reached. A few are full-set lists that return their whole bounded result in one response and therefore always report null; those say so in the operation description. Either way, null means there is nothing further to fetch — never construct a cursor yourself."
|
||
}
|
||
},
|
||
"required": ["data", "nextCursor"],
|
||
"additionalProperties": false,
|
||
"title": "List MCP servers response",
|
||
"description": "MCP servers registered in the workspace.",
|
||
"examples": [
|
||
{
|
||
"data": [
|
||
{
|
||
"id": "mcp-3f7a9c21",
|
||
"name": "Docs server",
|
||
"description": "Internal documentation tools",
|
||
"transport": "streamable-http",
|
||
"authType": "headers",
|
||
"url": "https://mcp.example.com/sse",
|
||
"timeout": 30000,
|
||
"retries": 3,
|
||
"enabled": true,
|
||
"connectionStatus": "connected",
|
||
"lastError": null,
|
||
"toolCount": 7,
|
||
"lastToolsRefresh": "2026-06-20T14:02:11.000Z",
|
||
"lastConnected": "2026-06-20T14:02:11.000Z",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z",
|
||
"hasHeaders": true,
|
||
"headerNames": ["Authorization"],
|
||
"hasOauthClientSecret": false
|
||
}
|
||
],
|
||
"nextCursor": null
|
||
}
|
||
]
|
||
},
|
||
"CreateMcpServerResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2McpServer"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Create MCP server response",
|
||
"description": "The registered MCP server without write-only credentials.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "mcp-3f7a9c21",
|
||
"name": "Docs server",
|
||
"description": "Internal documentation tools",
|
||
"transport": "streamable-http",
|
||
"authType": "headers",
|
||
"url": "https://mcp.example.com/sse",
|
||
"timeout": 30000,
|
||
"retries": 3,
|
||
"enabled": true,
|
||
"connectionStatus": "connected",
|
||
"lastError": null,
|
||
"toolCount": 7,
|
||
"lastToolsRefresh": "2026-06-20T14:02:11.000Z",
|
||
"lastConnected": "2026-06-20T14:02:11.000Z",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z",
|
||
"hasHeaders": true,
|
||
"headerNames": ["Authorization"],
|
||
"hasOauthClientSecret": false
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"CreateMcpServerRequest": {
|
||
"type": "object",
|
||
"properties": {
|
||
"workspaceId": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace in which to register the server."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 255,
|
||
"description": "Server display name."
|
||
},
|
||
"description": {
|
||
"description": "Optional server description.",
|
||
"type": "string",
|
||
"maxLength": 2000
|
||
},
|
||
"transport": {
|
||
"description": "Transport used to communicate with the server. Applied server-side as `streamable-http` when omitted on create.",
|
||
"default": "streamable-http",
|
||
"type": "string",
|
||
"enum": ["streamable-http"]
|
||
},
|
||
"url": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 2048,
|
||
"description": "Absolute HTTP or HTTPS endpoint URL without `{{ENV_VAR}}` references."
|
||
},
|
||
"authType": {
|
||
"description": "Authentication method. Sim detects it from the server when omitted.",
|
||
"type": "string",
|
||
"enum": ["none", "headers", "oauth"]
|
||
},
|
||
"headers": {
|
||
"description": "Write-only request headers sent to the server.",
|
||
"writeOnly": true,
|
||
"type": "object",
|
||
"propertyNames": {
|
||
"type": "string",
|
||
"minLength": 1
|
||
},
|
||
"additionalProperties": {
|
||
"type": "string",
|
||
"description": "Header value sent to the MCP server."
|
||
}
|
||
},
|
||
"timeout": {
|
||
"description": "Per-request timeout in milliseconds. Applied server-side as 30000 when omitted on create.",
|
||
"default": 30000,
|
||
"type": "integer",
|
||
"minimum": 1000,
|
||
"maximum": 300000
|
||
},
|
||
"retries": {
|
||
"description": "Number of retries per request. Applied server-side as 3 when omitted on create.",
|
||
"default": 3,
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 10
|
||
},
|
||
"enabled": {
|
||
"description": "Whether the server tools are available to workflows. Applied server-side as true when omitted on create.",
|
||
"default": true,
|
||
"type": "boolean"
|
||
},
|
||
"oauthClientId": {
|
||
"description": "Pre-registered OAuth client identifier.",
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"maxLength": 512
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
]
|
||
},
|
||
"oauthClientSecret": {
|
||
"description": "Write-only pre-registered OAuth client secret.",
|
||
"writeOnly": true,
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"maxLength": 2048
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"required": ["workspaceId", "name", "url"],
|
||
"additionalProperties": false,
|
||
"title": "Create MCP server request",
|
||
"description": "Configuration for a new MCP server.",
|
||
"examples": [
|
||
{
|
||
"workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"name": "Docs server",
|
||
"url": "https://mcp.example.com/sse",
|
||
"authType": "headers",
|
||
"headers": {
|
||
"Authorization": "Bearer YOUR_TOKEN"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"GetMcpServerResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2McpServer"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Get MCP server response",
|
||
"description": "One MCP server without write-only credentials.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "mcp-3f7a9c21",
|
||
"name": "Docs server",
|
||
"description": "Internal documentation tools",
|
||
"transport": "streamable-http",
|
||
"authType": "headers",
|
||
"url": "https://mcp.example.com/sse",
|
||
"timeout": 30000,
|
||
"retries": 3,
|
||
"enabled": true,
|
||
"connectionStatus": "connected",
|
||
"lastError": null,
|
||
"toolCount": 7,
|
||
"lastToolsRefresh": "2026-06-20T14:02:11.000Z",
|
||
"lastConnected": "2026-06-20T14:02:11.000Z",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z",
|
||
"hasHeaders": true,
|
||
"headerNames": ["Authorization"],
|
||
"hasOauthClientSecret": false
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"UpdateMcpServerResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2McpServer"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Update MCP server response",
|
||
"description": "The updated MCP server.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "mcp-3f7a9c21",
|
||
"name": "Docs server",
|
||
"description": "Internal documentation tools",
|
||
"transport": "streamable-http",
|
||
"authType": "headers",
|
||
"url": "https://mcp.example.com/sse",
|
||
"timeout": 30000,
|
||
"retries": 3,
|
||
"enabled": false,
|
||
"connectionStatus": "connected",
|
||
"lastError": null,
|
||
"toolCount": 7,
|
||
"lastToolsRefresh": "2026-06-20T14:02:11.000Z",
|
||
"lastConnected": "2026-06-20T14:02:11.000Z",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z",
|
||
"hasHeaders": true,
|
||
"headerNames": ["Authorization"],
|
||
"hasOauthClientSecret": false
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"UpdateMcpServerRequest": {
|
||
"type": "object",
|
||
"properties": {
|
||
"workspaceId": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the MCP server."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 255,
|
||
"description": "Server display name."
|
||
},
|
||
"description": {
|
||
"description": "Optional server description.",
|
||
"type": "string",
|
||
"maxLength": 2000
|
||
},
|
||
"transport": {
|
||
"description": "Transport used to communicate with the server. Applied server-side as `streamable-http` when omitted on create.",
|
||
"default": "streamable-http",
|
||
"type": "string",
|
||
"enum": ["streamable-http"]
|
||
},
|
||
"url": {
|
||
"description": "Immutable server URL. When provided, it must equal the current URL; use delete and create to change endpoints.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 2048
|
||
},
|
||
"authType": {
|
||
"description": "Authentication method. Sim detects it from the server when omitted.",
|
||
"type": "string",
|
||
"enum": ["none", "headers", "oauth"]
|
||
},
|
||
"headers": {
|
||
"description": "Write-only request headers sent to the server.",
|
||
"writeOnly": true,
|
||
"type": "object",
|
||
"propertyNames": {
|
||
"type": "string",
|
||
"minLength": 1
|
||
},
|
||
"additionalProperties": {
|
||
"type": "string",
|
||
"description": "Header value sent to the MCP server."
|
||
}
|
||
},
|
||
"timeout": {
|
||
"description": "Per-request timeout in milliseconds. Applied server-side as 30000 when omitted on create.",
|
||
"default": 30000,
|
||
"type": "integer",
|
||
"minimum": 1000,
|
||
"maximum": 300000
|
||
},
|
||
"retries": {
|
||
"description": "Number of retries per request. Applied server-side as 3 when omitted on create.",
|
||
"default": 3,
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 10
|
||
},
|
||
"enabled": {
|
||
"description": "Whether the server tools are available to workflows. Applied server-side as true when omitted on create.",
|
||
"default": true,
|
||
"type": "boolean"
|
||
},
|
||
"oauthClientId": {
|
||
"description": "Pre-registered OAuth client identifier.",
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"maxLength": 512
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
]
|
||
},
|
||
"oauthClientSecret": {
|
||
"description": "Write-only pre-registered OAuth client secret.",
|
||
"writeOnly": true,
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"maxLength": 2048
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"required": ["workspaceId"],
|
||
"additionalProperties": false,
|
||
"title": "Update MCP server request",
|
||
"description": "MCP server fields to change; omitted fields retain their stored values.",
|
||
"examples": [
|
||
{
|
||
"workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"enabled": false
|
||
}
|
||
]
|
||
},
|
||
"V2McpServerDeleteData": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Identifier of the deleted MCP server."
|
||
},
|
||
"deleted": {
|
||
"type": "boolean",
|
||
"const": true,
|
||
"description": "Whether the server was deleted."
|
||
}
|
||
},
|
||
"required": ["id", "deleted"],
|
||
"additionalProperties": false,
|
||
"title": "Delete MCP server data",
|
||
"description": "MCP server deletion acknowledgement."
|
||
},
|
||
"DeleteMcpServerResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2McpServerDeleteData"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Delete MCP server response",
|
||
"description": "Acknowledgement that the MCP server was deleted.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "mcp-3f7a9c21",
|
||
"deleted": true
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"V2McpTool": {
|
||
"type": "object",
|
||
"properties": {
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Tool name, as the MCP server reports it."
|
||
},
|
||
"description": {
|
||
"description": "Tool description reported by the server.",
|
||
"type": "string"
|
||
},
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"type": {
|
||
"type": "string",
|
||
"const": "object",
|
||
"description": "JSON Schema type of the argument object. MCP requires `object`."
|
||
},
|
||
"properties": {
|
||
"description": "Argument schemas keyed by argument name.",
|
||
"type": "object",
|
||
"propertyNames": {
|
||
"type": "string"
|
||
},
|
||
"additionalProperties": {
|
||
"description": "Server-defined JSON Schema for one tool argument."
|
||
}
|
||
},
|
||
"required": {
|
||
"description": "Names of the arguments the tool requires.",
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string",
|
||
"description": "Name of a required argument."
|
||
}
|
||
},
|
||
"description": {
|
||
"description": "Description of the argument object.",
|
||
"type": "string"
|
||
}
|
||
},
|
||
"required": ["type"],
|
||
"additionalProperties": {
|
||
"description": "Additional JSON Schema keyword reported by the server."
|
||
},
|
||
"description": "JSON Schema for the tool's arguments, as reported by the server."
|
||
},
|
||
"serverId": {
|
||
"type": "string",
|
||
"description": "Identifier of the MCP server exposing the tool."
|
||
},
|
||
"serverName": {
|
||
"type": "string",
|
||
"description": "Display name of the MCP server exposing the tool."
|
||
}
|
||
},
|
||
"required": ["name", "inputSchema", "serverId", "serverName"],
|
||
"additionalProperties": false,
|
||
"title": "MCP tool",
|
||
"description": "A tool exposed by a registered MCP server."
|
||
},
|
||
"ListMcpServerToolsResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/V2McpTool"
|
||
},
|
||
"description": "Items in the current page."
|
||
},
|
||
"nextCursor": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Opaque cursor for the next page: send it back as `cursor` to continue, and stop when it is null. Most v2 lists page, so null means the last page was reached. A few are full-set lists that return their whole bounded result in one response and therefore always report null; those say so in the operation description. Either way, null means there is nothing further to fetch — never construct a cursor yourself."
|
||
}
|
||
},
|
||
"required": ["data", "nextCursor"],
|
||
"additionalProperties": false,
|
||
"title": "List MCP server tools response",
|
||
"description": "Tools exposed by the MCP server.",
|
||
"examples": [
|
||
{
|
||
"data": [
|
||
{
|
||
"name": "search_docs",
|
||
"description": "Search the internal documentation",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"query": {
|
||
"type": "string",
|
||
"description": "Search terms"
|
||
}
|
||
},
|
||
"required": ["query"]
|
||
},
|
||
"serverId": "mcp-3f7a9c21",
|
||
"serverName": "Docs server"
|
||
}
|
||
],
|
||
"nextCursor": null
|
||
}
|
||
]
|
||
},
|
||
"V2SkillSummary": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Unique skill identifier. Built-in skills use their name as the id."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Kebab-case name that agents use to reference the skill."
|
||
},
|
||
"description": {
|
||
"type": "string",
|
||
"description": "One-line summary of when the skill applies."
|
||
},
|
||
"readOnly": {
|
||
"type": "boolean",
|
||
"description": "Whether this is a built-in skill that cannot be modified or deleted."
|
||
},
|
||
"createdAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the skill was created. Built-in skills report the Unix epoch."
|
||
},
|
||
"updatedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the skill was last updated. Built-in skills report the Unix epoch."
|
||
}
|
||
},
|
||
"required": ["id", "name", "description", "readOnly", "createdAt", "updatedAt"],
|
||
"additionalProperties": false,
|
||
"title": "Skill summary",
|
||
"description": "Public summary metadata for a workspace or built-in skill."
|
||
},
|
||
"ListSkillsResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/V2SkillSummary"
|
||
},
|
||
"description": "Items in the current page."
|
||
},
|
||
"nextCursor": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Opaque cursor for the next page: send it back as `cursor` to continue, and stop when it is null. Most v2 lists page, so null means the last page was reached. A few are full-set lists that return their whole bounded result in one response and therefore always report null; those say so in the operation description. Either way, null means there is nothing further to fetch — never construct a cursor yourself."
|
||
}
|
||
},
|
||
"required": ["data", "nextCursor"],
|
||
"additionalProperties": false,
|
||
"title": "List skills response",
|
||
"description": "Skill summaries available in the workspace.",
|
||
"examples": [
|
||
{
|
||
"data": [
|
||
{
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"name": "refund-policy",
|
||
"description": "How support should handle refund requests",
|
||
"readOnly": false,
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
],
|
||
"nextCursor": null
|
||
}
|
||
]
|
||
},
|
||
"V2Skill": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Unique skill identifier. Built-in skills use their name as the id."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Kebab-case name that agents use to reference the skill."
|
||
},
|
||
"description": {
|
||
"type": "string",
|
||
"description": "One-line summary of when the skill applies."
|
||
},
|
||
"readOnly": {
|
||
"type": "boolean",
|
||
"description": "Whether this is a built-in skill that cannot be modified or deleted."
|
||
},
|
||
"createdAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the skill was created. Built-in skills report the Unix epoch."
|
||
},
|
||
"updatedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the skill was last updated. Built-in skills report the Unix epoch."
|
||
},
|
||
"content": {
|
||
"type": "string",
|
||
"description": "Skill body containing the instructions given to the agent."
|
||
}
|
||
},
|
||
"required": ["id", "name", "description", "readOnly", "createdAt", "updatedAt", "content"],
|
||
"additionalProperties": false,
|
||
"title": "Skill",
|
||
"description": "A workspace or built-in skill including its instruction body."
|
||
},
|
||
"CreateSkillResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2Skill"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Create skill response",
|
||
"description": "The created skill including its content.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"name": "refund-policy",
|
||
"description": "How support should handle refund requests",
|
||
"readOnly": false,
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z",
|
||
"content": "# Refund policy\n\nAlways check the order date first."
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"CreateSkillRequest": {
|
||
"type": "object",
|
||
"properties": {
|
||
"workspaceId": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace in which to create the skill."
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 64,
|
||
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
|
||
"description": "Kebab-case name, unique within the workspace and not reserved by a built-in skill."
|
||
},
|
||
"description": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 1024,
|
||
"description": "One-line summary of when the skill applies."
|
||
},
|
||
"content": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 50000,
|
||
"description": "Skill body containing the instructions given to the agent."
|
||
}
|
||
},
|
||
"required": ["workspaceId", "name", "description", "content"],
|
||
"additionalProperties": false,
|
||
"title": "Create skill request",
|
||
"description": "Definition of a new skill.",
|
||
"examples": [
|
||
{
|
||
"workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"name": "refund-policy",
|
||
"description": "How support should handle refund requests",
|
||
"content": "# Refund policy\n\nAlways check the order date first."
|
||
}
|
||
]
|
||
},
|
||
"GetSkillResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2Skill"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Get skill response",
|
||
"description": "One skill including its full content.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"name": "refund-policy",
|
||
"description": "How support should handle refund requests",
|
||
"readOnly": false,
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z",
|
||
"content": "# Refund policy\n\nAlways check the order date first."
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"UpdateSkillResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2Skill"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Update skill response",
|
||
"description": "The updated skill including its full content.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"name": "refund-policy",
|
||
"description": "Updated refund guidance",
|
||
"readOnly": false,
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z",
|
||
"content": "# Refund policy\n\nAlways check the order date first."
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"UpdateSkillRequest": {
|
||
"type": "object",
|
||
"properties": {
|
||
"workspaceId": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the skill."
|
||
},
|
||
"name": {
|
||
"description": "New kebab-case skill name.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 64,
|
||
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
|
||
},
|
||
"description": {
|
||
"description": "New one-line summary of when the skill applies.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 1024
|
||
},
|
||
"content": {
|
||
"description": "Replacement skill body.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 50000
|
||
}
|
||
},
|
||
"required": ["workspaceId"],
|
||
"additionalProperties": false,
|
||
"title": "Update skill request",
|
||
"description": "Skill fields to change; at least one editable field is required.",
|
||
"examples": [
|
||
{
|
||
"workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"description": "Updated refund guidance"
|
||
}
|
||
]
|
||
},
|
||
"V2SkillDeleteData": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Identifier of the deleted skill."
|
||
},
|
||
"deleted": {
|
||
"type": "boolean",
|
||
"const": true,
|
||
"description": "Whether the skill was deleted."
|
||
}
|
||
},
|
||
"required": ["id", "deleted"],
|
||
"additionalProperties": false,
|
||
"title": "Delete skill data",
|
||
"description": "Skill deletion acknowledgement."
|
||
},
|
||
"DeleteSkillResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2SkillDeleteData"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Delete skill response",
|
||
"description": "Acknowledgement that the skill was deleted.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"deleted": true
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"V2CustomTool": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Unique custom tool identifier."
|
||
},
|
||
"title": {
|
||
"type": "string",
|
||
"description": "Display title, unique within the workspace."
|
||
},
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"type": {
|
||
"type": "string",
|
||
"const": "function",
|
||
"description": "Function declaration discriminator."
|
||
},
|
||
"function": {
|
||
"type": "object",
|
||
"properties": {
|
||
"name": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Function name presented to the model."
|
||
},
|
||
"description": {
|
||
"description": "Optional explanation of what the function does.",
|
||
"type": "string"
|
||
},
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"type": {
|
||
"type": "string",
|
||
"description": "JSON Schema type for the arguments, usually `object`."
|
||
},
|
||
"properties": {
|
||
"type": "object",
|
||
"propertyNames": {
|
||
"type": "string"
|
||
},
|
||
"additionalProperties": {
|
||
"description": "Caller-defined JSON Schema for one tool argument."
|
||
},
|
||
"description": "Caller-defined argument schemas keyed by argument name."
|
||
},
|
||
"required": {
|
||
"description": "Names of required arguments.",
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
},
|
||
"required": ["type", "properties"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
},
|
||
"description": "JSON Schema describing the arguments accepted by the tool."
|
||
}
|
||
},
|
||
"required": ["name", "parameters"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
},
|
||
"description": "OpenAI-style function definition."
|
||
}
|
||
},
|
||
"required": ["type", "function"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
},
|
||
"description": "OpenAI-style function declaration describing the callable tool surface."
|
||
},
|
||
"code": {
|
||
"type": "string",
|
||
"description": "Tool implementation executed in the sandboxed function runtime."
|
||
},
|
||
"createdAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the tool was created."
|
||
},
|
||
"updatedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the tool was last updated."
|
||
}
|
||
},
|
||
"required": ["id", "title", "schema", "code", "createdAt", "updatedAt"],
|
||
"additionalProperties": false,
|
||
"title": "Custom tool",
|
||
"description": "A workspace custom tool and its callable function declaration."
|
||
},
|
||
"ListCustomToolsResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/V2CustomTool"
|
||
},
|
||
"description": "Items in the current page."
|
||
},
|
||
"nextCursor": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Opaque cursor for the next page: send it back as `cursor` to continue, and stop when it is null. Most v2 lists page, so null means the last page was reached. A few are full-set lists that return their whole bounded result in one response and therefore always report null; those say so in the operation description. Either way, null means there is nothing further to fetch — never construct a cursor yourself."
|
||
}
|
||
},
|
||
"required": ["data", "nextCursor"],
|
||
"additionalProperties": false,
|
||
"title": "List custom tools response",
|
||
"description": "Custom tools defined in the workspace.",
|
||
"examples": [
|
||
{
|
||
"data": [
|
||
{
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"title": "lookup_order",
|
||
"schema": {
|
||
"type": "function",
|
||
"function": {
|
||
"name": "lookup_order",
|
||
"description": "Look up an order by id",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"orderId": {
|
||
"type": "string"
|
||
}
|
||
},
|
||
"required": ["orderId"]
|
||
}
|
||
}
|
||
},
|
||
"code": "return { ok: true }",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
],
|
||
"nextCursor": null
|
||
}
|
||
]
|
||
},
|
||
"CreateCustomToolResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2CustomTool"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Create custom tool response",
|
||
"description": "The created custom tool.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"title": "lookup_order",
|
||
"schema": {
|
||
"type": "function",
|
||
"function": {
|
||
"name": "lookup_order",
|
||
"description": "Look up an order by id",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"orderId": {
|
||
"type": "string"
|
||
}
|
||
},
|
||
"required": ["orderId"]
|
||
}
|
||
}
|
||
},
|
||
"code": "return { ok: true }",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"CreateCustomToolRequest": {
|
||
"type": "object",
|
||
"properties": {
|
||
"workspaceId": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace in which to create the custom tool."
|
||
},
|
||
"title": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 200,
|
||
"description": "Display title, unique within the workspace."
|
||
},
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"type": {
|
||
"type": "string",
|
||
"const": "function",
|
||
"description": "Function declaration discriminator."
|
||
},
|
||
"function": {
|
||
"type": "object",
|
||
"properties": {
|
||
"name": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Function name presented to the model."
|
||
},
|
||
"description": {
|
||
"description": "Optional explanation of what the function does.",
|
||
"type": "string"
|
||
},
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"type": {
|
||
"type": "string",
|
||
"description": "JSON Schema type for the arguments, usually `object`."
|
||
},
|
||
"properties": {
|
||
"type": "object",
|
||
"propertyNames": {
|
||
"type": "string"
|
||
},
|
||
"additionalProperties": {
|
||
"description": "Caller-defined JSON Schema for one tool argument."
|
||
},
|
||
"description": "Caller-defined argument schemas keyed by argument name."
|
||
},
|
||
"required": {
|
||
"description": "Names of required arguments.",
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
},
|
||
"required": ["type", "properties"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
},
|
||
"description": "JSON Schema describing the arguments accepted by the tool."
|
||
}
|
||
},
|
||
"required": ["name", "parameters"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
},
|
||
"description": "OpenAI-style function definition."
|
||
}
|
||
},
|
||
"required": ["type", "function"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
},
|
||
"description": "OpenAI-style function declaration describing the callable tool surface."
|
||
},
|
||
"code": {
|
||
"type": "string",
|
||
"maxLength": 100000,
|
||
"description": "Tool implementation executed in the sandboxed function runtime."
|
||
}
|
||
},
|
||
"required": ["workspaceId", "title", "schema", "code"],
|
||
"additionalProperties": false,
|
||
"title": "Create custom tool request",
|
||
"description": "Definition and implementation of a new custom tool.",
|
||
"examples": [
|
||
{
|
||
"workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"title": "lookup_order",
|
||
"schema": {
|
||
"type": "function",
|
||
"function": {
|
||
"name": "lookup_order",
|
||
"description": "Look up an order by id",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"orderId": {
|
||
"type": "string"
|
||
}
|
||
},
|
||
"required": ["orderId"]
|
||
}
|
||
}
|
||
},
|
||
"code": "return { ok: true }"
|
||
}
|
||
]
|
||
},
|
||
"GetCustomToolResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2CustomTool"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Get custom tool response",
|
||
"description": "One custom tool.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"title": "lookup_order",
|
||
"schema": {
|
||
"type": "function",
|
||
"function": {
|
||
"name": "lookup_order",
|
||
"description": "Look up an order by id",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"orderId": {
|
||
"type": "string"
|
||
}
|
||
},
|
||
"required": ["orderId"]
|
||
}
|
||
}
|
||
},
|
||
"code": "return { ok: true }",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"UpdateCustomToolResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2CustomTool"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Update custom tool response",
|
||
"description": "The updated custom tool.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"title": "lookup_order",
|
||
"schema": {
|
||
"type": "function",
|
||
"function": {
|
||
"name": "lookup_order",
|
||
"description": "Look up an order by id",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"orderId": {
|
||
"type": "string"
|
||
}
|
||
},
|
||
"required": ["orderId"]
|
||
}
|
||
}
|
||
},
|
||
"code": "return { ok: false }",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"UpdateCustomToolRequest": {
|
||
"type": "object",
|
||
"properties": {
|
||
"workspaceId": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace that owns the custom tool."
|
||
},
|
||
"title": {
|
||
"description": "New display title for the tool.",
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 200
|
||
},
|
||
"schema": {
|
||
"description": "Replacement function declaration.",
|
||
"type": "object",
|
||
"properties": {
|
||
"type": {
|
||
"type": "string",
|
||
"const": "function",
|
||
"description": "Function declaration discriminator."
|
||
},
|
||
"function": {
|
||
"type": "object",
|
||
"properties": {
|
||
"name": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Function name presented to the model."
|
||
},
|
||
"description": {
|
||
"description": "Optional explanation of what the function does.",
|
||
"type": "string"
|
||
},
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"type": {
|
||
"type": "string",
|
||
"description": "JSON Schema type for the arguments, usually `object`."
|
||
},
|
||
"properties": {
|
||
"type": "object",
|
||
"propertyNames": {
|
||
"type": "string"
|
||
},
|
||
"additionalProperties": {
|
||
"description": "Caller-defined JSON Schema for one tool argument."
|
||
},
|
||
"description": "Caller-defined argument schemas keyed by argument name."
|
||
},
|
||
"required": {
|
||
"description": "Names of required arguments.",
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
},
|
||
"required": ["type", "properties"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
},
|
||
"description": "JSON Schema describing the arguments accepted by the tool."
|
||
}
|
||
},
|
||
"required": ["name", "parameters"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
},
|
||
"description": "OpenAI-style function definition."
|
||
}
|
||
},
|
||
"required": ["type", "function"],
|
||
"additionalProperties": {
|
||
"description": "Caller-defined extension value preserved by the public API."
|
||
}
|
||
},
|
||
"code": {
|
||
"description": "Replacement tool implementation.",
|
||
"type": "string",
|
||
"maxLength": 100000
|
||
}
|
||
},
|
||
"required": ["workspaceId"],
|
||
"additionalProperties": false,
|
||
"title": "Update custom tool request",
|
||
"description": "Custom tool fields to change; at least one editable field is required.",
|
||
"examples": [
|
||
{
|
||
"workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"code": "return { ok: false }"
|
||
}
|
||
]
|
||
},
|
||
"V2CustomToolDeleteData": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Identifier of the deleted custom tool."
|
||
},
|
||
"deleted": {
|
||
"type": "boolean",
|
||
"const": true,
|
||
"description": "Whether the custom tool was deleted."
|
||
}
|
||
},
|
||
"required": ["id", "deleted"],
|
||
"additionalProperties": false,
|
||
"title": "Delete custom tool data",
|
||
"description": "Custom tool deletion acknowledgement."
|
||
},
|
||
"DeleteCustomToolResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2CustomToolDeleteData"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Delete custom tool response",
|
||
"description": "Acknowledgement that the custom tool was deleted.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"id": "V1StGXR8Z5jdHi6BmyT",
|
||
"deleted": true
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"V2Credential": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "string",
|
||
"description": "Unique credential identifier."
|
||
},
|
||
"type": {
|
||
"type": "string",
|
||
"enum": ["oauth", "service_account"],
|
||
"description": "Authenticated connection type."
|
||
},
|
||
"displayName": {
|
||
"type": "string",
|
||
"description": "Credential display name."
|
||
},
|
||
"description": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Optional credential description."
|
||
},
|
||
"providerId": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Integration provider authenticated by this credential."
|
||
},
|
||
"accountId": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Linked account identifier for OAuth credentials."
|
||
},
|
||
"hasServiceAccountKey": {
|
||
"type": "boolean",
|
||
"description": "Whether a service-account payload is stored. Its contents are never returned."
|
||
},
|
||
"role": {
|
||
"type": "string",
|
||
"enum": ["admin", "member"],
|
||
"description": "Caller role for the credential."
|
||
},
|
||
"createdAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the credential was created."
|
||
},
|
||
"updatedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the credential was last updated."
|
||
}
|
||
},
|
||
"required": [
|
||
"id",
|
||
"type",
|
||
"displayName",
|
||
"description",
|
||
"providerId",
|
||
"accountId",
|
||
"hasServiceAccountKey",
|
||
"role",
|
||
"createdAt",
|
||
"updatedAt"
|
||
],
|
||
"additionalProperties": false,
|
||
"title": "Credential",
|
||
"description": "Public authenticated-connection metadata without secret material."
|
||
},
|
||
"ListCredentialsResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/V2Credential"
|
||
},
|
||
"description": "Items in the current page."
|
||
},
|
||
"nextCursor": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Opaque cursor for the next page: send it back as `cursor` to continue, and stop when it is null. Most v2 lists page, so null means the last page was reached. A few are full-set lists that return their whole bounded result in one response and therefore always report null; those say so in the operation description. Either way, null means there is nothing further to fetch — never construct a cursor yourself."
|
||
}
|
||
},
|
||
"required": ["data", "nextCursor"],
|
||
"additionalProperties": false,
|
||
"title": "List credentials response",
|
||
"description": "Credential metadata visible to the caller.",
|
||
"examples": [
|
||
{
|
||
"data": [
|
||
{
|
||
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
|
||
"type": "service_account",
|
||
"displayName": "Zoom service account",
|
||
"description": null,
|
||
"providerId": "zoom-service-account",
|
||
"accountId": null,
|
||
"hasServiceAccountKey": true,
|
||
"role": "admin",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
],
|
||
"nextCursor": null
|
||
}
|
||
]
|
||
},
|
||
"V2Secret": {
|
||
"type": "object",
|
||
"properties": {
|
||
"name": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 255,
|
||
"pattern": "^[A-Za-z0-9_]+$",
|
||
"description": "Secret name containing only letters, numbers, and underscores."
|
||
},
|
||
"scope": {
|
||
"type": "string",
|
||
"enum": ["workspace", "personal"],
|
||
"description": "Whether the secret belongs to the workspace or the caller."
|
||
},
|
||
"role": {
|
||
"type": "string",
|
||
"enum": ["admin", "member"],
|
||
"description": "Caller role for the secret."
|
||
},
|
||
"createdAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the secret was created."
|
||
},
|
||
"updatedAt": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||
"description": "ISO 8601 timestamp when the secret was last updated."
|
||
}
|
||
},
|
||
"required": ["name", "scope", "role", "createdAt", "updatedAt"],
|
||
"additionalProperties": false,
|
||
"title": "Secret metadata",
|
||
"description": "Public secret metadata without the stored secret value."
|
||
},
|
||
"ListSecretsResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"type": "array",
|
||
"items": {
|
||
"$ref": "#/components/schemas/V2Secret"
|
||
},
|
||
"description": "Items in the current page."
|
||
},
|
||
"nextCursor": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Opaque cursor for the next page: send it back as `cursor` to continue, and stop when it is null. Most v2 lists page, so null means the last page was reached. A few are full-set lists that return their whole bounded result in one response and therefore always report null; those say so in the operation description. Either way, null means there is nothing further to fetch — never construct a cursor yourself."
|
||
}
|
||
},
|
||
"required": ["data", "nextCursor"],
|
||
"additionalProperties": false,
|
||
"title": "List secrets response",
|
||
"description": "Secret metadata visible to the caller without stored values.",
|
||
"examples": [
|
||
{
|
||
"data": [
|
||
{
|
||
"name": "STRIPE_API_KEY",
|
||
"scope": "workspace",
|
||
"role": "admin",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
],
|
||
"nextCursor": null
|
||
}
|
||
]
|
||
},
|
||
"SetSecretResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2Secret"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Set secret response",
|
||
"description": "Metadata for the created or replaced secret without its value.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"name": "STRIPE_API_KEY",
|
||
"scope": "workspace",
|
||
"role": "admin",
|
||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"SetSecretRequest": {
|
||
"type": "object",
|
||
"properties": {
|
||
"workspaceId": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"description": "Workspace in which the secret is available."
|
||
},
|
||
"scope": {
|
||
"type": "string",
|
||
"enum": ["workspace", "personal"],
|
||
"description": "Whether the secret belongs to the workspace or the caller."
|
||
},
|
||
"value": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 65536,
|
||
"description": "Write-only secret value. It is never returned.",
|
||
"writeOnly": true
|
||
}
|
||
},
|
||
"required": ["workspaceId", "scope", "value"],
|
||
"additionalProperties": false,
|
||
"title": "Set secret request",
|
||
"description": "Ownership scope and write-only value for the secret.",
|
||
"examples": [
|
||
{
|
||
"workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64",
|
||
"scope": "workspace",
|
||
"value": "YOUR_SECRET_VALUE"
|
||
}
|
||
]
|
||
},
|
||
"V2SecretDeleteData": {
|
||
"type": "object",
|
||
"properties": {
|
||
"name": {
|
||
"type": "string",
|
||
"minLength": 1,
|
||
"maxLength": 255,
|
||
"pattern": "^[A-Za-z0-9_]+$",
|
||
"description": "Secret name containing only letters, numbers, and underscores."
|
||
},
|
||
"scope": {
|
||
"type": "string",
|
||
"enum": ["workspace", "personal"],
|
||
"description": "Whether the secret belongs to the workspace or the caller."
|
||
},
|
||
"deleted": {
|
||
"type": "boolean",
|
||
"const": true,
|
||
"description": "Whether the secret was deleted."
|
||
}
|
||
},
|
||
"required": ["name", "scope", "deleted"],
|
||
"additionalProperties": false,
|
||
"title": "Delete secret data",
|
||
"description": "Secret deletion acknowledgement without the stored value."
|
||
},
|
||
"DeleteSecretResponse": {
|
||
"type": "object",
|
||
"properties": {
|
||
"data": {
|
||
"description": "Response data.",
|
||
"$ref": "#/components/schemas/V2SecretDeleteData"
|
||
}
|
||
},
|
||
"required": ["data"],
|
||
"additionalProperties": false,
|
||
"title": "Delete secret response",
|
||
"description": "Acknowledgement that the secret was deleted.",
|
||
"examples": [
|
||
{
|
||
"data": {
|
||
"name": "STRIPE_API_KEY",
|
||
"scope": "workspace",
|
||
"deleted": true
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
},
|
||
"x-generated-by": "scripts/generate-openapi.ts"
|
||
}
|