diff --git a/.vitepress/sidebar.ts b/.vitepress/sidebar.ts index 4aa5ba44..f364749e 100644 --- a/.vitepress/sidebar.ts +++ b/.vitepress/sidebar.ts @@ -157,6 +157,9 @@ export const apiReferenceSidebar: DefaultTheme.SidebarItem[] = [ { text: 'Snapshot Stream', link: '/api-reference/sessions/stream-snapshot' }, { text: 'Upload File', link: '/api-reference/sessions/upload-file' }, { text: 'List Files', link: '/api-reference/sessions/list-files' }, + { text: 'List Workspace Files', link: '/api-reference/sessions/list-workspace-files' }, + { text: 'List Artifacts', link: '/api-reference/sessions/list-artifacts' }, + { text: 'Download Artifact', link: '/api-reference/sessions/download-artifact' }, { text: 'Download File', link: '/api-reference/sessions/download-file' }, { text: 'Delete File', link: '/api-reference/sessions/delete-file' }, ], diff --git a/agents/agent-api.md b/agents/agent-api.md index 4ef23cea..fb50d1f6 100644 --- a/agents/agent-api.md +++ b/agents/agent-api.md @@ -52,7 +52,7 @@ The body is strict JSON (up to 1 MiB). Unknown fields return 400. | `instructions` | No | Up to 256 KiB | | `description` | No | Up to 4096 characters | | `runtime_profile` | No | `codex` (default), `claude`, or `mcode` | -| `skills` | No | `[{"skill_id": "skl_…", "version": null}]`; see [Skills](/agents/mcp-tools#skills) | +| `skills` | No | Up to 32 references. A new binding with `version: null` pins the current default when saved; existing bindings retain their pinned version. Supply an explicit version to upgrade; see [Skills](/agents/mcp-tools#skills) | | `mcp_connections` | No | Up to 32 references; see [MCP connections](/agents/mcp-tools#mcp-connections) | | `tools` | No | Up to 64 `function` tools and at most one `web_search` tool | | `text` | No | `format.type` `text` or `json_schema` (object schema); `verbosity` `low`, `medium`, or `high` | diff --git a/agents/api-credentials.md b/agents/api-credentials.md index 087a2bfe..d5ca3fb1 100644 --- a/agents/api-credentials.md +++ b/agents/api-credentials.md @@ -23,11 +23,12 @@ Create, rotate, and revoke Secrets with the Secrets API. This guide documents th Create a Secret. `Idempotency-Key` is required for every Secret write. ```bash -curl -X POST https://api.sandbase.ai/v1/secrets \ - -H "Authorization: Bearer $SANDBASE_API_KEY" \ - -H "Idempotency-Key: github-mcp-token" \ - -H "Content-Type: application/json" \ - -d '{"name": "GitHub MCP token", "kind": "mcp_bearer", "server_url": "https://mcp.example.com/github", "token": "'"$GITHUB_MCP_TOKEN"'"}' +python3 -c 'import json, os; print(json.dumps({"name": "GitHub MCP token", "kind": "mcp_bearer", "server_url": "https://mcp.example.com/github", "token": os.environ["GITHUB_MCP_TOKEN"]}))' | \ + curl -X POST https://api.sandbase.ai/v1/secrets \ + -H "Authorization: Bearer $SANDBASE_API_KEY" \ + -H "Idempotency-Key: github-mcp-token" \ + -H "Content-Type: application/json" \ + --data-binary @- ``` The response is `201 Created` with a `sec_` ID and `status` (`pending` while the token is stored, then `active`). Use the ID as `secret_id` when you [create the MCP connection](/api-reference/mcp-connections/#create-a-connection). diff --git a/agents/mcp-tools.md b/agents/mcp-tools.md index 47fbdc2d..633732fc 100644 --- a/agents/mcp-tools.md +++ b/agents/mcp-tools.md @@ -26,8 +26,8 @@ Most useful Agents combine both: an MCP server fetches data or takes actions, an } ``` -- `"version": null` uses the Skill's `default_version`. A string such as `"2"` pins one version. -- The version is resolved and frozen when each Session starts. Changing the default later affects new Sessions only. +- On first attachment, `"version": null` resolves the Skill's current `default_version` when the Agent is saved. A string such as `"2"` selects that version explicitly. +- The saved Agent pins the version. Changing the Skill default does not upgrade existing Agents or their new Sessions. Update the Agent with an explicit version to upgrade; sending `null` for an existing binding retains its pinned version. - Upload a new version with `POST /v1/skills/{skill_id}/versions`, and switch the default with `default=true` or `POST /v1/skills/{skill_id}`. - A disabled Skill makes new Sessions of Agents that reference it fail with `400 skill_not_found`. @@ -63,12 +63,27 @@ You can also browse public Skills in the [Skills catalog](/api-reference/skills/ | Field | Meaning | |---|---| -| `connection_id` | `mcp_` connection ID in your organization | +| `connection_id` | `mcp_` connection ID in your organization, or `builtin:sandbase` for the built-in SandBase server | | `server_label` | Starts with a letter; letters, digits, `_`, and `-`; up to 64 characters; unique within the Agent | -| `allowed_tools` | `null` allows every tool the server offers; `[]` allows none; a list allows only those names | +| `allowed_tools` | Omitted or `null` allows every tool the server offers; `[]` allows none; a list allows only those names (up to 256 names, each nonblank and at most 128 bytes) | | `required` | Whether the Session must load this server | -An Agent can reference up to 32 connections. The connection must be `active` when a Session starts. +An Agent can reference up to 32 connections. Organization connections must be `active` when a Session starts. + +### Built-in SandBase tools + +Creating an Agent without `mcp_connections` selects the built-in SandBase server. To explicitly keep it alongside your own server, include this entry in the array: + +```json +{ + "connection_id": "builtin:sandbase", + "server_label": "sandbase_builtin", + "allowed_tools": null, + "required": true +} +``` + +An explicit array replaces the whole selection; the custom-only example above excludes the built-in server. Send `[]` to disable all MCP tools. Omitting the field on an Agent update preserves the existing selection; `null` returns `400`. The built-in entry does not require creating an MCP connection or Secret. Do not send the retired `use_sandbase_mcp` field. ::: info Differs from OpenAI OpenAI's Agents API declares MCP servers inline as `tools: [{"type": "mcp", ...}]`. SandBase returns `400` for that form: register the server as a connection resource and reference it in `mcp_connections`. The OpenAI SDK has no methods for `/v1/mcp-connections` or `/v1/secrets`, so call them with `client.post(...)` and pass `mcp_connections` through `extra_body` (Python) or the request body (Node.js). See [OpenAI compatibility](/agents/openai-compatibility#mcp-connections-with-the-sdk). diff --git a/agents/openai-compatibility.md b/agents/openai-compatibility.md index 9749edf4..8994b948 100644 --- a/agents/openai-compatibility.md +++ b/agents/openai-compatibility.md @@ -232,9 +232,11 @@ SandBase runs every Session in its hosted cloud; there is no environment to choo | `sessions.items.list`, `sessions.turns.list / retrieve` | Compatible | Cursor pagination with `limit` 1-100, `after`, `order`. | | `client.skills` and `skills.versions` | Compatible, except version delete | `skills.versions.delete` returns `404`. | | Error envelope | Compatible | `{"error": {"type", "code", "param", "message"}}`; SDK exceptions expose it as `exc.body`. | -| Agent versions, restore, archive, catalog, Session rename/archive/reconnect/files | SandBase only | See the [Agents](/api-reference/agents/) and [Sessions](/api-reference/sessions/) API reference. | +| Agent versions, restore, archive, catalog, Session rename/archive/reconnect/files/workspace-files/artifacts | SandBase only | See the [Agents](/api-reference/agents/) and [Sessions](/api-reference/sessions/) API reference. | | Services, Schedules, MCP connections, Secrets | SandBase only | Use HTTP or `client.post` / `client.get`. | +SandBase exposes [artifact listing](/api-reference/sessions/list-artifacts) and [content downloads](/api-reference/sessions/download-artifact) over HTTP. Artifact IDs are opaque and Session-scoped. This does not imply support for every SDK artifact method; a separate artifact metadata endpoint is not exposed. + ### Not supported These OpenAI SDK calls fail against SandBase. The message is the actual `error.message` returned. @@ -250,7 +252,7 @@ These OpenAI SDK calls fail against SandBase. The message is the actual `error.m | `agents.create(..., tools=[{"type": "mcp", ...}])`; `tool_search`, `programmatic_tool_calling`, and `computer_use` tools | `400 invalid_request`. Only `function` and one `web_search` tool are accepted | | `events.create` with a `tool_result` or computer-use approval event | `400 invalid_request`. Sessions that reach `requires_action` are not supported | | `events.create` with more than one event | `400`: `exactly one text input or cancel event is supported per request` | -| `sessions.artifacts`, `sessions.traces`, `sessions.subagents`, `environments.templates` | `404 not_found` (openai-python 3.13.0 has no `traces`) | +| `sessions.traces`, `sessions.subagents`, `environments.templates` | `404 not_found` (openai-python 3.13.0 has no `traces`) | | `beta.agents.vaults` | `404` with a plain-text body (`404 page not found`), not the JSON error envelope | | `skills.versions.delete` | `404 not_found` | diff --git a/agents/schedules.md b/agents/schedules.md index a90d740a..47951fed 100644 --- a/agents/schedules.md +++ b/agents/schedules.md @@ -74,3 +74,7 @@ Schedules support the same Feishu or Lark notifications as [Services](/api-refer - [Schedules API reference](/api-reference/schedules/) - [Services](/agents/services) - [Sessions](/agents/sessions) + +## Change the model for future Runs + +Use the [update endpoint](/api-reference/schedules/update) with the latest `row_version` and a `model` string. Omit `model` to keep the current override, or send `""` to return to the pinned Agent model. `null` is rejected. A Run-specific model takes precedence over the saved override; changing the resource does not change an existing Run. diff --git a/agents/services.md b/agents/services.md index af98e003..bc777d29 100644 --- a/agents/services.md +++ b/agents/services.md @@ -73,3 +73,7 @@ A Service can post Run results to a Feishu or Lark bot. Save the webhook with `P - [Services API reference](/api-reference/services/) - [Schedules](/agents/schedules) - [Sessions](/agents/sessions) + +## Change the model for future Runs + +Use the [update endpoint](/api-reference/services/update) with the latest `row_version` and a `model` string. Omit `model` to keep the current override, or send `""` to return to the pinned Agent model. `null` is rejected. A Run-specific model takes precedence over the saved override; changing the resource does not change an existing Run. diff --git a/agents/sessions.md b/agents/sessions.md index 692a11cd..531b2e5c 100644 --- a/agents/sessions.md +++ b/agents/sessions.md @@ -135,3 +135,15 @@ Treat IDs as opaque strings. - [OpenAI compatibility](/agents/openai-compatibility) - [Services](/agents/services) - [Schedules](/agents/schedules) + +## Workspace files and generated artifacts + +These three resources serve different purposes: + +| Resource | Use it for | Pagination | +|---|---|---| +| [Attachments](/api-reference/sessions/list-files) | Files you uploaded and can reference in input with a `file_id` | `limit` / `offset` | +| [Workspace files](/api-reference/sessions/list-workspace-files) | Current file paths and sizes in a workspace directory | `page` / `next` | +| [Artifacts](/api-reference/sessions/list-artifacts) | Immutable generated outputs available for download | `after` / `last_id` | + +List artifacts to obtain an `art_` ID, then pass that exact ID to [Download Artifact](/api-reference/sessions/download-artifact). A workspace path or attachment `file_id` cannot substitute for it. Workspace listing returns metadata only and is not an arbitrary-path download API. Artifacts can be downloaded as complete files up to 200 MiB; partial `Range` downloads are unsupported. diff --git a/api-reference/agents/create.md b/api-reference/agents/create.md index 69d89aa8..f4fd0829 100644 --- a/api-reference/agents/create.md +++ b/api-reference/agents/create.md @@ -22,8 +22,7 @@ apiReference: - name: model type: string required: true - description: Model ID from the Models API, up to 200 characters. - default: deepseek/deepseek-v4-flash + description: Required model ID from the Models API, up to 200 characters. There is no default; omitting model returns 400. The examples use deepseek/deepseek-v4-flash. - name: name type: string required: false @@ -39,7 +38,7 @@ apiReference: - name: skills type: array required: false - description: 'Skill references: {"skill_id": "skl_…", "version": null | ""}. null uses the Skill default version, resolved when a Session starts.' + description: 'Skill references: {"skill_id": "skl_…", "version": null | ""}. Up to 32. On a new binding, null pins the current default when the Agent is saved. For an existing binding, null preserves its pinned version; supply an explicit version to upgrade.' - name: mcp_connections type: array required: false diff --git a/api-reference/agents/replace.md b/api-reference/agents/replace.md index ed902562..2cbb0a1f 100644 --- a/api-reference/agents/replace.md +++ b/api-reference/agents/replace.md @@ -48,7 +48,7 @@ apiReference: - name: skills type: array required: false - description: 'Skill references: {"skill_id": "skl_…", "version": null | ""}. null uses the Skill default version, resolved when a Session starts.' + description: 'Skill references: {"skill_id": "skl_…", "version": null | ""}. Up to 32. On a new binding, null pins the current default when the Agent is saved. For an existing binding, null preserves its pinned version; supply an explicit version to upgrade.' - name: mcp_connections type: array required: false diff --git a/api-reference/agents/update.md b/api-reference/agents/update.md index 7d537b73..6eef3c49 100644 --- a/api-reference/agents/update.md +++ b/api-reference/agents/update.md @@ -44,7 +44,7 @@ apiReference: - name: skills type: array required: false - description: 'Skill references: {"skill_id": "skl_…", "version": null | ""}. null uses the Skill default version, resolved when a Session starts.' + description: 'Skill references: {"skill_id": "skl_…", "version": null | ""}. Up to 32. On a new binding, null pins the current default when the Agent is saved. For an existing binding, null preserves its pinned version; supply an explicit version to upgrade.' - name: mcp_connections type: array required: false diff --git a/api-reference/mcp-connections/index.md b/api-reference/mcp-connections/index.md index fb58c12f..4a5a26b5 100644 --- a/api-reference/mcp-connections/index.md +++ b/api-reference/mcp-connections/index.md @@ -15,7 +15,7 @@ An MCP connection registers a remote MCP server reachable over HTTPS. Agents ref | `POST` | `/v1/mcp-connections/{connection_id}/enable` | [Enable a connection](#enable-a-connection) | | `DELETE` | `/v1/mcp-connections/{connection_id}` | [Disable a connection](#disable-a-connection) | -Every write requires `Idempotency-Key` (1-128 characters). The connection object has `id`, `name`, `server_url`, `secret_id` (when set), `status` (`active` or `disabled`), `row_version`, `created_at`, and `updated_at`. +Every write requires `Idempotency-Key` (1-128 bytes, without leading or trailing whitespace). The connection object has `id`, `name`, `server_url`, `secret_id` (when set), `status` (`active` or `disabled`), `row_version`, `created_at`, and `updated_at`. ## Create a connection @@ -27,8 +27,8 @@ curl -X POST https://api.sandbase.ai/v1/mcp-connections \ -d '{"name": "GitHub", "server_url": "https://mcp.example.com/github", "secret_id": "sec_9b1d3f5a-7c9e-5b2d-8f4a-6c8e0a2d4f61"}' ``` -- `name` is required, up to 128 characters. -- `server_url` must be HTTPS without user info, query, or fragment (up to 2048 characters). +- `name` is required and nonblank, up to 128 bytes. +- `server_url` must be HTTPS without user info, query, or fragment (up to 2048 bytes). - `secret_id` is optional. Omit it for an anonymous server. When set, the Secret must be `active` and its `server_url` must equal this `server_url` exactly, otherwise the request fails (409 for an inactive Secret, 400 for a URL mismatch). Returns `201 Created`. @@ -40,7 +40,7 @@ curl "https://api.sandbase.ai/v1/mcp-connections?limit=20&offset=0" \ -H "Authorization: Bearer $SANDBASE_API_KEY" ``` -Offset pagination with `data`, `total`, `limit`, and `offset`. +Offset pagination with `data`, `total`, `limit`, and `offset`. `limit` defaults to 20 and is capped at 200; missing, invalid, or nonpositive values use 20. `offset` defaults to 0; invalid or nonpositive values use 0. Advance by the returned `limit` until `offset + data.length >= total`. Disabled resources remain visible in the list. ## Get a connection @@ -59,7 +59,7 @@ curl -X PUT https://api.sandbase.ai/v1/mcp-connections/mcp_1f3d5b7a-9c2e-5f4a-8b -d '{"name": "GitHub", "server_url": "https://mcp.example.com/github", "row_version": 1}' ``` -`PUT` replaces `name`, `server_url`, and `secret_id` together; omitting `secret_id` makes the connection anonymous. `row_version` must match the current value. A disabled connection stays disabled after replacement until you enable it. +`PUT` replaces `name`, `server_url`, and `secret_id` together; omitting `secret_id` makes the connection anonymous. `row_version` is required and must match the current value returned by Get Connection. Returns `200 OK` with the updated connection. A disabled connection stays disabled after replacement until you enable it. ## Enable a connection @@ -71,7 +71,7 @@ curl -X POST https://api.sandbase.ai/v1/mcp-connections/mcp_1f3d5b7a-9c2e-5f4a-8 -d '{"row_version": 3}' ``` -Enabling keeps the same connection ID. It does not restore a revoked Secret; replace the connection with a new active Secret first. +`row_version` is required and must be a positive integer matching the current resource. Returns `200 OK` with the connection. Enabling keeps the same connection ID. It does not restore a revoked Secret; replace the connection with a new active Secret first. ## Disable a connection @@ -86,3 +86,9 @@ Returns `204 No Content` and sets `status` to `disabled`. New Sessions cannot us ## Changes and running Sessions Sessions freeze connection settings when they start. After you replace a connection or rotate its Secret, new Sessions use the new configuration and existing Sessions keep their snapshot. Disabling the connection or revoking its Secret blocks both new Sessions and further input to existing ones. + +## Errors and retries + +Errors use the [Agents error envelope](/api-reference/errors#agents-platform-errors). Invalid fields or a missing write idempotency key return `400 invalid_request`; an unknown or inaccessible resource returns `404 not_found`. Stale `row_version`, an incompatible lifecycle state, or reusing a create/update key with different input returns `409 conflict`. Read the resource again before issuing a new update, and use a new key for each new write operation. + +`created_at` and `updated_at` are RFC 3339 timestamp strings. diff --git a/api-reference/schedules/create.md b/api-reference/schedules/create.md index ba77ff46..a4327fff 100644 --- a/api-reference/schedules/create.md +++ b/api-reference/schedules/create.md @@ -42,7 +42,7 @@ apiReference: - name: model type: string required: false - description: Default model override. + description: Model override for Runs, up to 200 characters. Omit or send an empty string to use the pinned Agent model. Each Run freezes its selected model. - name: schedule type: object required: true diff --git a/api-reference/schedules/update.md b/api-reference/schedules/update.md index 62760ea1..447d75b2 100644 --- a/api-reference/schedules/update.md +++ b/api-reference/schedules/update.md @@ -8,7 +8,7 @@ apiReference: operation: Schedules method: PATCH path: /v1/schedules/{schedule_id} - description: Compare-and-set update. Only name, description, agent_id, agent_version, input, and row_version are accepted; explicit null returns 400. Changing agent_id without agent_version pins that Agent's current version. Change cron and timezone with PUT /v1/schedules/{schedule_id}/timing. + description: Compare-and-set update. Only name, description, agent_id, agent_version, model, input, and row_version are accepted; explicit null returns 400. Changing agent_id without agent_version pins that Agent's current version. Change cron and timezone with PUT /v1/schedules/{schedule_id}/timing. groups: - title: Path parameters fields: @@ -39,6 +39,10 @@ apiReference: type: integer required: false description: Pin a different version. + - name: model + type: string + required: false + description: Model override for future Runs, up to 200 characters. Omit to preserve the current override; send an empty string to use the pinned Agent model. null is rejected. Existing Runs keep their frozen model. - name: input type: string required: false @@ -52,7 +56,8 @@ apiReference: -H "Content-Type: application/json" \ -d '{ "row_version": 1, - "agent_version": 4 + "agent_version": 4, + "model": "deepseek/deepseek-v4-flash" }' - label: Python language: python @@ -70,7 +75,8 @@ apiReference: headers=headers, json={ "row_version": 1, - "agent_version": 4 + "agent_version": 4, + "model": "deepseek/deepseek-v4-flash" }, ) response.raise_for_status() @@ -86,7 +92,8 @@ apiReference: }, body: JSON.stringify({ "row_version": 1, - "agent_version": 4 + "agent_version": 4, + "model": "deepseek/deepseek-v4-flash" }), }); @@ -102,6 +109,7 @@ apiReference: "description": "", "agent_id": "agt_8c1f2b9e-3d4a-4f6b-9c2e-1a7d5e3b4c60", "agent_version": 4, + "model": "deepseek/deepseek-v4-flash", "input": "Summarize the previous day of AI agent news.", "status": "active", "row_version": 2, diff --git a/api-reference/secrets/index.md b/api-reference/secrets/index.md index a8b3a24f..ec6b9fc6 100644 --- a/api-reference/secrets/index.md +++ b/api-reference/secrets/index.md @@ -20,21 +20,22 @@ Secret endpoints take a SandBase API key and a plaintext token. Call them from a | `POST` | `/v1/secrets/{secret_id}/rotate` | [Rotate the token](#rotate-a-secret) | | `DELETE` | `/v1/secrets/{secret_id}` | [Revoke a Secret](#revoke-a-secret) | -Every write requires `Idempotency-Key` (1-128 characters). The Secret object has `id`, `name`, `kind`, `server_url`, `status`, `row_version`, `created_at`, and `updated_at`. `status` is `pending` while the token is being stored, `active` when it can be used, and `disabled` after revocation. +Every write requires `Idempotency-Key` (1-128 bytes, without leading or trailing whitespace). The Secret object has `id`, `name`, `kind`, `server_url`, `status`, `row_version`, `created_at`, and `updated_at`. `status` is `pending` while the token is being stored, `active` when it can be used, and `disabled` after revocation. ## Create a Secret ```bash -curl -X POST https://api.sandbase.ai/v1/secrets \ - -H "Authorization: Bearer $SANDBASE_API_KEY" \ - -H "Idempotency-Key: github-mcp-token" \ - -H "Content-Type: application/json" \ - -d '{"name": "GitHub MCP token", "kind": "mcp_bearer", "server_url": "https://mcp.example.com/github", "token": "'"$GITHUB_MCP_TOKEN"'"}' +python3 -c 'import json, os; print(json.dumps({"name": "GitHub MCP token", "kind": "mcp_bearer", "server_url": "https://mcp.example.com/github", "token": os.environ["GITHUB_MCP_TOKEN"]}))' | \ + curl -X POST https://api.sandbase.ai/v1/secrets \ + -H "Authorization: Bearer $SANDBASE_API_KEY" \ + -H "Idempotency-Key: github-mcp-token" \ + -H "Content-Type: application/json" \ + --data-binary @- ``` - `kind` must be `mcp_bearer`. - `server_url` must be HTTPS without user info, query, or fragment. The Secret only works for this exact URL; to use another URL, create another Secret. -- `name` is up to 128 characters; `token` is up to 16384 characters. +- `name` and `token` are required and nonblank. `name` is up to 128 bytes; `token` is up to 16384 bytes. `server_url` is up to 2048 bytes. Returns `201 Created` without the token. @@ -45,7 +46,7 @@ curl "https://api.sandbase.ai/v1/secrets?limit=20&offset=0" \ -H "Authorization: Bearer $SANDBASE_API_KEY" ``` -Offset pagination with `data`, `total`, `limit`, and `offset`. +Offset pagination with `data`, `total`, `limit`, and `offset`. `limit` defaults to 20 and is capped at 200; missing, invalid, or nonpositive values use 20. `offset` defaults to 0; invalid or nonpositive values use 0. Advance by the returned `limit` until `offset + data.length >= total`. Disabled resources remain visible in the list. ## Get a Secret @@ -56,15 +57,18 @@ curl https://api.sandbase.ai/v1/secrets/sec_9b1d3f5a-7c9e-5b2d-8f4a-6c8e0a2d4f61 ## Rotate a Secret +Set `SECRET_ROW_VERSION` to the latest `row_version` from Get Secret. Both token examples read an exported `GITHUB_MCP_TOKEN` and JSON-encode it before passing it on standard input. + ```bash -curl -X POST https://api.sandbase.ai/v1/secrets/sec_9b1d3f5a-7c9e-5b2d-8f4a-6c8e0a2d4f61/rotate \ - -H "Authorization: Bearer $SANDBASE_API_KEY" \ - -H "Idempotency-Key: github-mcp-token-rotate-1" \ - -H "Content-Type: application/json" \ - -d '{"token": "'"$GITHUB_MCP_TOKEN"'", "row_version": 2}' +python3 -c 'import json, os; print(json.dumps({"token": os.environ["GITHUB_MCP_TOKEN"], "row_version": int(os.environ["SECRET_ROW_VERSION"])}))' | \ + curl -X POST https://api.sandbase.ai/v1/secrets/sec_9b1d3f5a-7c9e-5b2d-8f4a-6c8e0a2d4f61/rotate \ + -H "Authorization: Bearer $SANDBASE_API_KEY" \ + -H "Idempotency-Key: github-mcp-token-rotate-1" \ + -H "Content-Type: application/json" \ + --data-binary @- ``` -Rotation replaces the token for the same `server_url`. The Secret must be `active` and `row_version` must match. Returns `201 Created`. New Sessions use the new token; existing Sessions keep the credential they froze. +Rotation replaces the token for the same `server_url`. `token` and a positive integer `row_version` are required. The Secret must be `active` and `row_version` must match the latest Get Secret response. Returns `201 Created`. New Sessions use the new token; existing Sessions keep the credential they froze. ## Revoke a Secret @@ -75,3 +79,9 @@ curl -X DELETE https://api.sandbase.ai/v1/secrets/sec_9b1d3f5a-7c9e-5b2d-8f4a-6c ``` Returns `204 No Content` and sets `status` to `disabled`. Connections that use the Secret stop working for new Sessions and for further input to existing Sessions. Revocation cannot be undone: create a new Secret and replace the connection. + +## Errors and retries + +Errors use the [Agents error envelope](/api-reference/errors#agents-platform-errors). Invalid fields or a missing write idempotency key return `400 invalid_request`; an unknown or inaccessible resource returns `404 not_found`. Stale `row_version`, an incompatible lifecycle state, or reusing a create/update key with different input returns `409 conflict`. Read the resource again before issuing a new update, and use a new key for each new write operation. + +`created_at` and `updated_at` are RFC 3339 timestamp strings. diff --git a/api-reference/services/create.md b/api-reference/services/create.md index 35368f54..1a35d6d7 100644 --- a/api-reference/services/create.md +++ b/api-reference/services/create.md @@ -42,7 +42,7 @@ apiReference: - name: model type: string required: false - description: Default model override. + description: Model override for Runs, up to 200 characters. Omit or send an empty string to use the pinned Agent model. Each Run freezes its selected model. examples: - label: cURL language: bash diff --git a/api-reference/services/update.md b/api-reference/services/update.md index c553c859..2a7acc6b 100644 --- a/api-reference/services/update.md +++ b/api-reference/services/update.md @@ -8,7 +8,7 @@ apiReference: operation: Services method: PATCH path: /v1/services/{service_id} - description: Compare-and-set update. Only name, description, agent_id, agent_version, input, and row_version are accepted; explicit null returns 400. Changing agent_id without agent_version pins that Agent's current version. + description: Compare-and-set update. Only name, description, agent_id, agent_version, model, input, and row_version are accepted; explicit null returns 400. Changing agent_id without agent_version pins that Agent's current version. groups: - title: Path parameters fields: @@ -39,6 +39,10 @@ apiReference: type: integer required: false description: Pin a different version. + - name: model + type: string + required: false + description: Model override for future Runs, up to 200 characters. Omit to preserve the current override; send an empty string to use the pinned Agent model. null is rejected. Existing Runs keep their frozen model. - name: input type: string required: false @@ -52,7 +56,8 @@ apiReference: -H "Content-Type: application/json" \ -d '{ "row_version": 1, - "agent_version": 4 + "agent_version": 4, + "model": "deepseek/deepseek-v4-flash" }' - label: Python language: python @@ -70,7 +75,8 @@ apiReference: headers=headers, json={ "row_version": 1, - "agent_version": 4 + "agent_version": 4, + "model": "deepseek/deepseek-v4-flash" }, ) response.raise_for_status() @@ -86,7 +92,8 @@ apiReference: }, body: JSON.stringify({ "row_version": 1, - "agent_version": 4 + "agent_version": 4, + "model": "deepseek/deepseek-v4-flash" }), }); @@ -102,6 +109,7 @@ apiReference: "description": "", "agent_id": "agt_8c1f2b9e-3d4a-4f6b-9c2e-1a7d5e3b4c60", "agent_version": 4, + "model": "deepseek/deepseek-v4-flash", "input": "Summarize this week in AI agents.", "status": "active", "row_version": 2, diff --git a/api-reference/sessions/download-artifact.md b/api-reference/sessions/download-artifact.md new file mode 100644 index 00000000..87b07501 --- /dev/null +++ b/api-reference/sessions/download-artifact.md @@ -0,0 +1,82 @@ +--- +title: Download Session Artifact +description: Download a captured Session artifact by ID. Learn the complete-file download limit, supported response types, and Range behavior. +aside: false +outline: false +apiReference: + title: Download Session Artifact + operation: Sessions + method: GET + path: /v1/agents/sessions/{session_id}/artifacts/{artifact_id}/content + description: Download a complete immutable artifact using its ID from List Session Artifacts. Files up to 200 MiB are supported. No query parameters are accepted; Range requests return 416 after access validation. + groups: + - title: Path parameters + fields: + - name: session_id + type: string + required: true + description: Session ID (ses_ prefix). + - name: artifact_id + type: string + required: true + description: Exact opaque art_ ID returned by this Session artifact list. Attachment IDs and workspace paths are not accepted. + - title: Response headers + fields: + - name: Content-Type + type: string + required: true + description: Media type inferred from the file extension, or application/octet-stream. + - name: Content-Disposition + type: string + required: true + description: attachment with the artifact filename. + - name: Content-Length + type: integer + required: true + description: Complete file size in bytes. + - name: Cache-Control + type: string + required: true + description: no-store. + examples: + - label: cURL + language: bash + code: |- + # Set SESSION_ID and ARTIFACT_ID from your Session and artifact list. + curl --fail-with-body "https://api.sandbase.ai/v1/agents/sessions/${SESSION_ID}/artifacts/${ARTIFACT_ID}/content" \ + -H "Authorization: Bearer $SANDBASE_API_KEY" \ + --output report.md + - label: Python + language: python + code: |- + import os + from pathlib import Path + import requests + + # Use IDs returned by your own Session and artifact list. + session_id = os.environ["SESSION_ID"] + artifact_id = os.environ["ARTIFACT_ID"] + response = requests.get( + f"https://api.sandbase.ai/v1/agents/sessions/{session_id}/artifacts/{artifact_id}/content", + headers={"Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}"}, + ) + response.raise_for_status() + Path("report.md").write_bytes(response.content) + - label: TypeScript + language: typescript + code: |- + import { writeFile } from 'node:fs/promises'; + + const { SESSION_ID, ARTIFACT_ID, SANDBASE_API_KEY } = process.env; + if (!SESSION_ID || !ARTIFACT_ID || !SANDBASE_API_KEY) throw new Error('Missing configuration'); + const response = await fetch( + `https://api.sandbase.ai/v1/agents/sessions/${encodeURIComponent(SESSION_ID)}/artifacts/${encodeURIComponent(ARTIFACT_ID)}/content`, + { headers: { Authorization: `Bearer ${SANDBASE_API_KEY}` } }, + ); + if (!response.ok) throw new Error(await response.text()); + await writeFile('report.md', new Uint8Array(await response.arrayBuffer())); +--- + + + +A successful response is the file bytes, not JSON. Download the whole file; partial transfers and resumable Range downloads are unsupported. An unavailable artifact returns `404`; an unavailable artifact service returns `executor_unavailable`. diff --git a/api-reference/sessions/index.md b/api-reference/sessions/index.md index ee1ea054..2d28f8c5 100644 --- a/api-reference/sessions/index.md +++ b/api-reference/sessions/index.md @@ -6,7 +6,7 @@ description: Run a saved Agent version, send input, stream events, and read Sess A Session runs one saved [Agent](/api-reference/agents/) version in SandBase's hosted cloud; there is no execution environment to choose. Session IDs use the `ses_` prefix. Sessions are a sub-resource of Agents: every path starts with `/v1/agents/sessions`. -The standard operations follow the OpenAI Agents protocol (`client.beta.agents.sessions`); see [OpenAI compatibility](/agents/openai-compatibility) for what the official SDK can and cannot do. SandBase extensions add renaming, archive state, cancellation, attachments, and a reconnectable product stream. +The standard operations follow the OpenAI Agents protocol (`client.beta.agents.sessions`); see [OpenAI compatibility](/agents/openai-compatibility) for what the official SDK can and cannot do. SandBase extensions add renaming, archive state, cancellation, attachments, workspace file metadata, immutable artifacts, and a reconnectable product stream. ::: info Where Sessions come from You create a Session directly with `POST /v1/agents/sessions`, or a [Service](/api-reference/services/) invocation or [Schedule](/api-reference/schedules/) Run creates one for you. The Session reports `source` (`direct`, `service`, or `schedule`) and `source_id`, and the Run reports the Session in `session_id`. @@ -14,7 +14,7 @@ You create a Session directly with `POST /v1/agents/sessions`, or a [Service](/a ## Recommended flow -1. [Create a Session](./create) for a saved Agent. The Agent version, Skill versions, and MCP connections are frozen at this point. +1. [Create a Session](./create) for a saved Agent. The Session freezes the selected Agent version and MCP connections, and uses the Skill versions already pinned to that Agent version. 2. [Open the event stream](./stream) with `GET /v1/agents/sessions/{session_id}/events`. The stream is live-only, so open it before you send input. 3. [Send input](./send-events) with `POST /v1/agents/sessions/{session_id}/events` and an `agent.session.input.message` event. The call returns 202. 4. Read results from the stream, or afterwards from [items](./items) and [turns](./turns). @@ -43,6 +43,9 @@ You create a Session directly with `POST /v1/agents/sessions`, or a [Service](/a | `GET` | `/v1/agents/sessions/{session_id}/files` | [List attachments](./list-files) | | `GET` | `/v1/agents/sessions/{session_id}/files/{file_id}/content` | [Download an attachment](./download-file) | | `DELETE` | `/v1/agents/sessions/{session_id}/files/{file_id}` | [Delete an attachment](./delete-file) | +| `GET` | `/v1/agents/sessions/{session_id}/workspace-files` | [List workspace file metadata](./list-workspace-files) | +| `GET` | `/v1/agents/sessions/{session_id}/artifacts` | [List immutable artifacts](./list-artifacts) | +| `GET` | `/v1/agents/sessions/{session_id}/artifacts/{artifact_id}/content` | [Download an artifact](./download-artifact) | ## Status fields @@ -82,4 +85,6 @@ Sending input can also return `409 session_input_pending` (earlier input is stil ## Pagination -Sessions, items, and turns use cursor pagination (`limit` 1-100, `after`, `order`). Attachments use offset pagination (`limit` 1-100, `offset`). +Sessions, items, turns, and artifacts use cursor pagination (`limit` 1-100, `after`, `order`). Attachments use offset pagination (`limit` 1-100, `offset`). + +Workspace files use `page` / `next` pagination instead. See [List workspace files](./list-workspace-files). diff --git a/api-reference/sessions/list-artifacts.md b/api-reference/sessions/list-artifacts.md new file mode 100644 index 00000000..1d0192e7 --- /dev/null +++ b/api-reference/sessions/list-artifacts.md @@ -0,0 +1,110 @@ +--- +title: List Session Artifacts +description: List captured Session output artifacts with cursor pagination, opaque IDs, file sizes, and download links. +aside: false +outline: false +apiReference: + title: List Session Artifacts + operation: Sessions + method: GET + path: /v1/agents/sessions/{session_id}/artifacts + description: List immutable generated outputs for this Session. Artifact IDs are opaque, Session-scoped handles; pass the returned ID unchanged to the download endpoint. Unknown or repeated query parameters return 400. + groups: + - title: Path parameters + fields: + - name: session_id + type: string + required: true + description: Session ID (ses_ prefix). + - title: Query parameters + fields: + - name: limit + type: integer + required: false + description: Page size from 1 to 100; invalid values return 400. + default: "20" + - name: order + type: string + required: false + description: asc or desc. + default: desc + - name: after + type: string + required: false + description: Artifact ID returned as last_id on the previous page. Keep the same order when continuing. + - title: Artifact object + fields: + - name: object + type: string + required: true + description: Always agent.session.artifact. + - name: id + type: string + required: true + description: Opaque art_ handle. Do not construct or decode it. + - name: session_id + type: string + required: true + description: Owning Session ID. + - name: path + type: string + required: true + description: Generated output path below /workspace/outputs/. + - name: size_bytes + type: integer + required: true + description: Artifact size in bytes. + - name: created_at + type: integer + required: true + description: Creation timestamp in Unix seconds. + examples: + - label: cURL + language: bash + code: |- + curl --fail-with-body 'https://api.sandbase.ai/v1/agents/sessions/ses_5b0e7c3a-91d2-5f4e-8a6b-2c9d4e1f7a83/artifacts' \ + -H "Authorization: Bearer $SANDBASE_API_KEY" + - label: Python + language: python + code: |- + import os + import requests + + response = requests.get( + "https://api.sandbase.ai/v1/agents/sessions/ses_5b0e7c3a-91d2-5f4e-8a6b-2c9d4e1f7a83/artifacts", + headers={"Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}"}, + ) + response.raise_for_status() + print(response.json()) + - label: TypeScript + language: typescript + code: |- + const response = await fetch('https://api.sandbase.ai/v1/agents/sessions/ses_5b0e7c3a-91d2-5f4e-8a6b-2c9d4e1f7a83/artifacts', { + headers: { Authorization: `Bearer ${process.env.SANDBASE_API_KEY}` }, + }); + if (!response.ok) throw new Error(await response.text()); + console.log(await response.json()); + response: + status: 200 OK + code: |- + { + "object": "list", + "data": [ + { + "object": "agent.session.artifact", + "id": "art_example_handle", + "session_id": "ses_5b0e7c3a-91d2-5f4e-8a6b-2c9d4e1f7a83", + "path": "/workspace/outputs/report.md", + "size_bytes": 1024, + "created_at": 1791273600 + } + ], + "has_more": false, + "first_id": "art_example_handle", + "last_id": "art_example_handle" + } +--- + + + +The example handle is illustrative. Use the `id` returned by your own request for [Download Artifact](./download-artifact). When `has_more` is true, send `after=last_id` for the next page. `first_id` and `last_id` are null for an empty list. A separate artifact metadata GET endpoint is not exposed. diff --git a/api-reference/sessions/list-workspace-files.md b/api-reference/sessions/list-workspace-files.md new file mode 100644 index 00000000..36950e5e --- /dev/null +++ b/api-reference/sessions/list-workspace-files.md @@ -0,0 +1,102 @@ +--- +title: List Workspace Files +description: List current Session workspace files with path, size, and pagination. This read-only endpoint returns file metadata. +aside: false +outline: false +apiReference: + title: List Workspace Files + operation: Sessions + method: GET + path: /v1/agents/sessions/{session_id}/workspace-files + description: List current file paths and sizes in one Session workspace directory. This is a metadata-only listing, not attachment history or artifact content. Unknown or repeated query parameters return 400. + groups: + - title: Path parameters + fields: + - name: session_id + type: string + required: true + description: Session ID (ses_ prefix). + - title: Query parameters + fields: + - name: path + type: string + required: false + description: Absolute directory /workspace or a normalized directory below it. Maximum 4096 bytes; no traversal, backslashes, or trailing slash. + default: /workspace + - name: limit + type: integer + required: false + description: Page size from 1 to 100. + default: "20" + - name: order + type: string + required: false + description: "Path sort order: asc or desc." + default: asc + - name: page + type: string + required: false + description: Opaque next value from the previous response. Omit on the first page; do not send an empty string. Keep path, order, and limit unchanged. + - title: Response fields + fields: + - name: object + type: string + required: true + description: Always page. + - name: data + type: array + required: true + description: Current directory files, each with path and size_bytes. This is not a recursive listing. + - name: next + type: string | null + required: true + description: Pass this value as page to continue; null at the end. + - name: has_more + type: boolean + required: true + description: Whether another page is available. + examples: + - label: cURL + language: bash + code: |- + curl --fail-with-body 'https://api.sandbase.ai/v1/agents/sessions/ses_5b0e7c3a-91d2-5f4e-8a6b-2c9d4e1f7a83/workspace-files' \ + -H "Authorization: Bearer $SANDBASE_API_KEY" + - label: Python + language: python + code: |- + import os + import requests + + response = requests.get( + "https://api.sandbase.ai/v1/agents/sessions/ses_5b0e7c3a-91d2-5f4e-8a6b-2c9d4e1f7a83/workspace-files", + headers={"Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}"}, + ) + response.raise_for_status() + print(response.json()) + - label: TypeScript + language: typescript + code: |- + const response = await fetch('https://api.sandbase.ai/v1/agents/sessions/ses_5b0e7c3a-91d2-5f4e-8a6b-2c9d4e1f7a83/workspace-files', { + headers: { Authorization: `Bearer ${process.env.SANDBASE_API_KEY}` }, + }); + if (!response.ok) throw new Error(await response.text()); + console.log(await response.json()); + response: + status: 200 OK + code: |- + { + "object": "page", + "data": [ + { + "path": "/workspace/notes.md", + "size_bytes": 1024 + } + ], + "next": null, + "has_more": false + } +--- + + + +Workspace reads can return `workspace_provisioning`, `workspace_environment_missing`, `workspace_cursor_invalid`, or `workspace_unavailable`. If the cursor is invalid, restart from the first page. Use [Artifacts](./list-artifacts) to find downloadable generated outputs. diff --git a/api-reference/skills/create.md b/api-reference/skills/create.md index fea16793..ff098ef5 100644 --- a/api-reference/skills/create.md +++ b/api-reference/skills/create.md @@ -15,7 +15,7 @@ apiReference: - name: Idempotency-Key type: string required: false - description: Optional. When omitted the service generates one, so a retried request can create a duplicate. Send your own stable key for retries. + description: Optional, 1-128 bytes without leading or trailing whitespace. When omitted the service generates one, so a retried request can create a duplicate. Send your own stable key for retries. - title: Request body description: multipart/form-data. fields: diff --git a/api-reference/skills/index.md b/api-reference/skills/index.md index 81c7ab95..550e08ff 100644 --- a/api-reference/skills/index.md +++ b/api-reference/skills/index.md @@ -52,15 +52,31 @@ A Skill is a versioned ZIP bundle of instructions and resources that an Agent ca -d '{"skills": [{"skill_id": "skl_3c5e7a9b-1d4f-5a6c-8e2b-4d6f8a0c2e57", "version": null}]}' ``` -`"version": null` follows the Skill's `default_version`; a string pins one version. The version is resolved and frozen when each Session starts, and the Session reports it in `environment.skills`. +When first attaching a Skill, `"version": null` (or an omitted `version`) resolves its current `default_version` when the Agent is saved. The saved Agent contains an explicit version. Changing the Skill default does not upgrade an existing Agent. To upgrade, update its `skills` array with an explicit version such as `"2"`; submitting `null` for an existing binding retains its pinned version. Each Session uses the saved Agent version and reports the resolved Skills in `environment.skills`. + +An Agent supports up to 32 Skills. Skill IDs and Skill names must be unique within its selection. ## Versions - Uploading to `POST /v1/skills/{skill_id}/versions` adds the next version. Send the form field `default=true` to make it the default. - `POST /v1/skills/{skill_id}` with `default_version` switches the default without uploading. - `status` is `active` or `disabled`. New Sessions of an Agent that references a disabled Skill fail with `400 skill_not_found`; Sessions that already froze a version keep it. -- A Skill that any Agent version references cannot be deleted (409). Disable it instead, or remove it from your Agents first. +- A Skill that any Agent version references cannot be deleted (409). Disable it instead. Removing it from the latest Agent version does not remove references in historical versions. ## Pagination and errors Lists use cursor pagination with `limit` 0-100, `after`, and `order`. Errors use the [Agents error envelope](/api-reference/errors#agents-platform-errors). + +## Download a bundle + +Content endpoints return `application/zip`, not JSON. Save the response to a file: + +```bash +curl --fail https://api.sandbase.ai/v1/skills/skl_3c5e7a9b-1d4f-5a6c-8e2b-4d6f8a0c2e57/versions/2/content \ + -H "Authorization: Bearer $SANDBASE_API_KEY" \ + --output release-notes-v2.zip +``` + +Use `/v1/skills/{skill_id}/content` for the current default version. For public catalog bundles, use `/v1/skills/catalog/{skill_id}/versions/{version}/content`. + +Version lists use the same cursor parameters as Skill lists. Pass the returned `last_id` (for example, `skl_…:2`) as `after`; do not pass the bare version number. diff --git a/api-reference/skills/update.md b/api-reference/skills/update.md index 0b39bf11..cbbced81 100644 --- a/api-reference/skills/update.md +++ b/api-reference/skills/update.md @@ -22,19 +22,19 @@ apiReference: - name: default_version type: string required: false - description: Version used by Agents that reference the Skill with version null. + description: Default used when first attaching this Skill without an explicit version. Existing Agent bindings keep their pinned versions. - name: status type: string required: false description: active or disabled. - name: metadata - type: object + type: object | null required: false - description: Market metadata. Requires row_version. + description: Presentation metadata under the market namespace only (up to 16 KiB). Requires row_version; replaces the metadata object. Unknown namespaces or fields return 400. See the metadata example below. - name: row_version type: integer required: false - description: Compare-and-set token. + description: Current row_version from Get Skill. A stale value returns 409 conflict; required when metadata is present. examples: - label: cURL language: bash @@ -111,3 +111,24 @@ apiReference: --- + +## Metadata example + +Send the current `row_version` together with the complete metadata object: + +```json +{ + "row_version": 3, + "metadata": { + "market": { + "display_name": "Release notes", + "categories": ["development"], + "tags": ["releases"], + "required_endpoints": ["github.issues"], + "servers": ["github"] + } + } +} +``` + +Market metadata describes the Skill for display. It does not install a server, grant credentials, or change the Agent's MCP selection. `metadata: {}` or `metadata: null` clears it; both still require `row_version`. diff --git a/api-reference/skills/versions.md b/api-reference/skills/versions.md index 3a66eb46..ab0ad6b4 100644 --- a/api-reference/skills/versions.md +++ b/api-reference/skills/versions.md @@ -21,7 +21,7 @@ apiReference: - name: Idempotency-Key type: string required: false - description: Optional. When omitted the service generates one, so a retried request can create a duplicate. Send your own stable key for retries. + description: Optional, 1-128 bytes without leading or trailing whitespace. When omitted the service generates one, so a retried request can create a duplicate. Send your own stable key for retries. - title: Request body description: multipart/form-data. fields: @@ -32,7 +32,7 @@ apiReference: - name: default type: string required: false - description: true or false. + description: true makes this the default for future attachments; false or omitted keeps the current default. Existing Agent bindings are unchanged. examples: - label: cURL language: bash @@ -112,3 +112,9 @@ apiReference: --- + +## List and download versions + +`GET /v1/skills/{skill_id}/versions` accepts `limit` (0-100, default 20), `order` (`asc` or `desc`, default `desc`), and `after`. Its response contains `object: "list"`, `data`, `has_more`, `first_id`, and `last_id`. Pass `last_id` unchanged as the next `after`; it is a Skill-prefixed version ID, not the bare version number. + +`GET /v1/skills/{skill_id}/versions/{version}` returns one version object. The corresponding `/content` endpoint returns ZIP bytes; see the [download example](./#download-a-bundle). diff --git a/for-agents/full.md b/for-agents/full.md index 7118aa6c..9c286aa3 100644 --- a/for-agents/full.md +++ b/for-agents/full.md @@ -430,6 +430,12 @@ Exactly one event per request: `agent.session.input.message` or `agent.session.i --- +### Workspace files and output artifacts + +- `GET /v1/agents/sessions/{session_id}/workspace-files` lists current file metadata under `path` (default `/workspace`). Query `limit` (1-100, default 20), `order` (`asc` default or `desc`), and `page` (the previous `next` cursor). Returns `{object:"page", data:[{path,size_bytes}], next, has_more}`. An invalid cursor requires restarting from page one. +- `GET /v1/agents/sessions/{session_id}/artifacts` lists immutable outputs captured under `/workspace/outputs/`. Query `limit` (default 20), `order` (`desc` default), and `after` (use `last_id` when `has_more` is true). +- `GET /v1/agents/sessions/{session_id}/artifacts/{artifact_id}/content` downloads a complete artifact using its returned opaque `art_` ID. Downloads have a 200 MiB limit; `Range` is unsupported (`416`). Workspace metadata, uploaded attachments, and captured artifacts are separate resources. + ## Services A Service pins an Agent version. Each invocation creates a Run (`run_`) that creates its own Session. @@ -525,3 +531,12 @@ Body `{"name", "kind": "mcp_bearer", "server_url", "token"}`; `Idempotency-Key` - [Models & Pricing](./models) — live model discovery, capabilities, and pricing guidance - [Error Guide](./errors) — documented response shapes, HTTP handling, and retry safety - [OpenAPI Spec](https://www.sandbase.ai/docs/openapi.yaml) — machine-readable schema + + +## Agent Skill version bindings + +Saving an Agent pins each new Skill reference to an explicit version. For a new binding, `version: null` selects the current default at save time. For an existing binding, `version: null` retains its pinned version. Changing a Skill default does not upgrade saved Agents or existing Sessions; save an explicit version on the Agent to upgrade future Sessions. + +## Updating Service and Schedule models + +`PATCH /v1/services/{service_id}` and `PATCH /v1/schedules/{schedule_id}` accept `model` with the required current `row_version`. Omit `model` to keep the saved override, send an empty string to clear it and use the pinned Agent model, or send a model ID to replace it. Explicit null is rejected. diff --git a/public/llms-full.txt b/public/llms-full.txt index 7f75f0fb..758e6d56 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -263,6 +263,16 @@ Creates a Session for a saved Agent and freezes its version, Skills, and MCP con ## GET /v1/agents/sessions/{session_id}/turns - Read turns ## POST /v1/agents/sessions/{session_id}/cancel - Cancel the running turn (Idempotency-Key required) +## GET /v1/agents/sessions/{session_id}/workspace-files - List current workspace file metadata + +Query: path (default /workspace), limit (1-100, default 20), order (asc default or desc), page (opaque next cursor). Returns object=page, data with path and size_bytes, next, has_more. This read-only listing does not download file content. + +## GET /v1/agents/sessions/{session_id}/artifacts - List captured output artifacts +## GET /v1/agents/sessions/{session_id}/artifacts/{artifact_id}/content - Download an artifact + +Artifacts are immutable outputs captured under /workspace/outputs/, separate from uploaded attachments. List with limit (1-100, default 20), order (desc default or asc), after (returned artifact ID). Use last_id while has_more is true. Download the returned opaque art_ ID as a complete file; Range is unsupported (416), with a 200 MiB download limit. + + --- ## POST /v1/services @@ -346,3 +356,12 @@ Do not retry unchanged 400, 401, 402, 403, or 404 requests. Check idempotency be - OpenAPI spec: https://www.sandbase.ai/docs/openapi.yaml - Console: https://www.sandbase.ai/console - API Keys: https://www.sandbase.ai/console/keys + + +## Agent Skill version bindings + +Saving an Agent pins each new Skill reference to an explicit version. For a new binding, `version: null` selects the current default at save time. For an existing binding, `version: null` retains its pinned version. Changing a Skill default does not upgrade saved Agents or existing Sessions; save an explicit version on the Agent to upgrade future Sessions. + +## Updating Service and Schedule models + +`PATCH /v1/services/{service_id}` and `PATCH /v1/schedules/{schedule_id}` accept `model` with the required current `row_version`. Omit `model` to keep the saved override, send an empty string to clear it and use the pinned Agent model, or send a model ID to replace it. Explicit null is rejected. diff --git a/public/openapi.yaml b/public/openapi.yaml index c2034540..6094848a 100644 --- a/public/openapi.yaml +++ b/public/openapi.yaml @@ -2196,6 +2196,237 @@ paths: '403': { description: 'insufficient_scope (scoped key) or org_disabled', content: { application/json: { schema: { $ref: '#/components/schemas/AgentsError' } } } } '404': { description: 'not_found: the resource does not exist in this organization', content: { application/json: { schema: { $ref: '#/components/schemas/AgentsError' } } } } '503': { description: 'authorization_unavailable, admission_unavailable, executor_unavailable, storage_unavailable, or internal_error', content: { application/json: { schema: { $ref: '#/components/schemas/AgentsError' } } } } + /v1/agents/sessions/{session_id}/workspace-files: + parameters: + - $ref: "#/components/parameters/SessionIdPath" + get: + operationId: listSessionWorkspaceFiles + tags: + - Sessions + summary: List workspace files + description: Read immediate file metadata under a normalized /workspace path. This is a read-only directory listing, not an attachment or artifact download. Use next as page with the same path, order and limit; restart from the first page if the cursor expires. + security: + - BearerAuth: [] + - AgentsApiKey: [] + parameters: + - name: path + in: query + required: false + description: Normalized absolute directory path under /workspace. No traversal, backslashes, or trailing slash. + schema: + type: string + maxLength: 4096 + default: /workspace + - name: limit + in: query + required: false + description: Page size. + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: order + in: query + required: false + description: Sort by path. + schema: + type: string + enum: + - asc + - desc + default: asc + - name: page + in: query + required: false + description: Opaque next cursor; omit for the first page. + schema: + type: string + minLength: 1 + maxLength: 1024 + responses: + "200": + description: List workspace files + content: + application/json: + schema: + $ref: "#/components/schemas/SessionWorkspacePage" + "400": + description: invalid_request or workspace_cursor_invalid; restart pagination for an invalid cursor + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "401": + description: unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + '402': { description: 'spending_limit_exceeded: API key spending limit or organization balance exhausted', content: { application/json: { schema: { $ref: '#/components/schemas/AgentsError' } } } } + "403": + description: insufficient_scope or org_disabled + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "404": + description: "not_found: resource not found in this organization" + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "409": + description: workspace_provisioning or workspace_environment_missing + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "503": + description: workspace_unavailable, authorization_unavailable, or storage_unavailable + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + /v1/agents/sessions/{session_id}/artifacts: + parameters: + - $ref: '#/components/parameters/SessionIdPath' + get: + operationId: listSessionArtifacts + tags: + - Sessions + summary: List session artifacts + description: List immutable outputs captured under /workspace/outputs/. Use returned opaque artifact IDs for after and downloads; do not construct IDs from paths. + security: + - BearerAuth: [] + - AgentsApiKey: [] + parameters: + - name: limit + in: query + required: false + description: Page size. + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: order + in: query + required: false + description: Artifact order. + schema: + type: string + enum: + - asc + - desc + default: desc + - name: after + in: query + required: false + description: Artifact ID from the preceding page; use last_id when has_more is true. + schema: + type: string + responses: + "200": + description: List session artifacts + content: + application/json: + schema: + $ref: "#/components/schemas/SessionArtifactList" + "400": + description: "invalid_request: invalid query parameters" + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "401": + description: unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + '402': { description: 'spending_limit_exceeded: API key spending limit or organization balance exhausted', content: { application/json: { schema: { $ref: '#/components/schemas/AgentsError' } } } } + "403": + description: insufficient_scope or org_disabled + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "404": + description: "not_found: resource not found in this organization" + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "503": + description: executor_unavailable, authorization_unavailable, or storage_unavailable + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + /v1/agents/sessions/{session_id}/artifacts/{artifact_id}/content: + parameters: + - $ref: '#/components/parameters/SessionIdPath' + - name: artifact_id + in: path + required: true + description: Opaque art_ ID returned by the artifact list for this Session. + schema: + type: string + get: + operationId: downloadSessionArtifact + tags: + - Sessions + summary: Download session artifact + description: Download a complete immutable artifact, up to 200 MiB. No query parameters. Range is unsupported and returns 416 after access checks. Content-Type follows the file extension, with application/octet-stream as fallback. + security: + - BearerAuth: [] + - AgentsApiKey: [] + responses: + "200": + description: Complete artifact bytes; Content-Disposition is attachment and Cache-Control is no-store. + content: + application/octet-stream: + schema: + type: string + format: binary + "400": + description: "invalid_request: invalid query parameters" + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "401": + description: unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + '402': { description: 'spending_limit_exceeded: API key spending limit or organization balance exhausted', content: { application/json: { schema: { $ref: '#/components/schemas/AgentsError' } } } } + "403": + description: insufficient_scope or org_disabled + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "404": + description: "not_found: resource not found in this organization" + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "416": + description: "invalid_request: Range downloads are unsupported" + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" + "503": + description: executor_unavailable, authorization_unavailable, or storage_unavailable + content: + application/json: + schema: + $ref: "#/components/schemas/AgentsError" # --- Services ----------------------------------------------- /v1/services: post: @@ -2276,7 +2507,7 @@ paths: operationId: updateService tags: [Services] summary: Update service - description: 'Compare-and-set update of name, description, agent_id, agent_version, or input. Explicit null values are rejected. Changing agent_id without agent_version pins that Agent''s current version.' + description: 'Compare-and-set update of name, description, agent_id, agent_version, input, or model. An empty model clears the saved override; omission preserves it. Explicit null values are rejected. Changing agent_id without agent_version pins that Agent''s current version.' security: - BearerAuth: [] - AgentsApiKey: [] @@ -2676,7 +2907,7 @@ paths: operationId: updateSchedule tags: [Schedules] summary: Update schedule - description: 'Compare-and-set update of name, description, agent_id, agent_version, or input. Explicit null values are rejected. Changing agent_id without agent_version pins that Agent''s current version.' + description: 'Compare-and-set update of name, description, agent_id, agent_version, input, or model. An empty model clears the saved override; omission preserves it. Explicit null values are rejected. Changing agent_id without agent_version pins that Agent''s current version.' security: - BearerAuth: [] - AgentsApiKey: [] @@ -3746,7 +3977,7 @@ components: in: path required: true schema: { type: string } - description: MCP connection ID (`mcp_` prefix). + description: MCP connection ID (`mcp_` prefix), or builtin:sandbase for the built-in SandBase server. SecretIdPath: name: secret_id in: path @@ -5258,14 +5489,14 @@ components: additionalProperties: false required: [skill_id] properties: - skill_id: { type: string, description: "Skill ID (`skl_` prefix) owned by your organization." } - version: { type: [string, 'null'], description: "Pinned Skill version. null uses the default version, resolved when a Session is created." } + skill_id: { type: string, description: "Skill ID (`skl_` prefix) owned by your organization or available from the public Skills catalog." } + version: { type: [string, 'null'], description: "Pinned Skill version. For a new binding, null resolves the current default when the Agent is saved. For an existing binding, null retains its pinned version; pass an explicit version to upgrade." } AgentMCPConnectionRef: type: object additionalProperties: false required: [connection_id, server_label] properties: - connection_id: { type: string, description: "MCP connection ID (`mcp_` prefix)." } + connection_id: { type: string, description: "MCP connection ID (`mcp_` prefix), or builtin:sandbase for the built-in SandBase server." } server_label: { type: string, pattern: '^[a-zA-Z][a-zA-Z0-9_-]{0,63}$', description: "Label unique within the Agent." } allowed_tools: { type: [array, 'null'], maxItems: 256, items: { type: string, minLength: 1, maxLength: 128 }, description: "null allows every tool; an empty array allows none." } required: { type: boolean, description: "Whether the Session must load this server." } @@ -5336,7 +5567,7 @@ components: text: { $ref: '#/components/schemas/AgentText' } reasoning: { $ref: '#/components/schemas/AgentReasoning' } skills: { type: array, items: { $ref: '#/components/schemas/AgentSkillRef' } } - mcp_connections: { type: array, maxItems: 32, items: { $ref: '#/components/schemas/AgentMCPConnectionRef' } } + mcp_connections: { type: array, maxItems: 32, items: { $ref: '#/components/schemas/AgentMCPConnectionRef' }, description: 'Omission selects builtin:sandbase on create. An explicit array replaces the selection; [] disables all MCP tools. Null is rejected.' } AgentCreateRequest: allOf: - $ref: '#/components/schemas/AgentConfigFields' @@ -5668,6 +5899,86 @@ components: model: { type: string, maxLength: 200 } schedule: { $ref: '#/components/schemas/ScheduleSpec' } model_provider: { $ref: '#/components/schemas/ModelProvider' } + SessionWorkspacePage: + type: object + required: + - object + - data + - next + - has_more + properties: + object: + type: string + enum: + - page + data: + type: array + items: + type: object + required: + - path + - size_bytes + properties: + path: + type: string + size_bytes: + type: integer + minimum: 0 + next: + type: [string, 'null'] + has_more: + type: boolean + SessionArtifact: + type: object + required: + - object + - id + - session_id + - path + - size_bytes + - created_at + properties: + object: + type: string + enum: + - agent.session.artifact + id: + type: string + description: Opaque art_ handle bound to this organization and Session. + session_id: + type: string + path: + type: string + description: Captured output path under /workspace/outputs/. + size_bytes: + type: integer + minimum: 0 + created_at: + type: integer + description: Unix seconds. + SessionArtifactList: + type: object + required: + - object + - data + - has_more + - first_id + - last_id + properties: + object: + type: string + enum: + - list + data: + type: array + items: + $ref: "#/components/schemas/SessionArtifact" + has_more: + type: boolean + first_id: + type: [string, 'null'] + last_id: + type: [string, 'null'] RunSourcePatch: type: object additionalProperties: false @@ -5678,6 +5989,7 @@ components: agent_id: { type: string } agent_version: { type: integer, minimum: 1 } input: { type: string, maxLength: 262144 } + model: { type: string, maxLength: 200, description: "Model override. Empty string clears the override; omission preserves it. Null is rejected." } row_version: { type: integer, minimum: 1 } ScheduleTimingRequest: type: object @@ -5860,7 +6172,7 @@ components: properties: default_version: { type: string } status: { type: string, enum: [active, disabled] } - metadata: { type: object, description: "Market metadata. Requires row_version." } + metadata: { type: [object, 'null'], description: "Market metadata. Requires row_version; null or an empty object clears it." } row_version: { type: integer, minimum: 1 } Skill: type: object