Skip to main content

Shares

Grant another project actions on one of your project's tools or agents.

Overview​

Every resource belongs to one project. A share is the publisher project's record that another project — or any project — may invoke one of its resources with a fixed set of actions. It grants nothing until the grantee writes an acceptance, so the publisher always has one row per consumer to list and revoke.

A share is the record and its lifecycle. What a share lets a grantee reference and invoke is described on the pages of the resource types it names.

See the Permissions Reference for the IAM action strings for this module.

Data Model​

Share​

FieldTypeDescription
idstringPublic identifier (e.g. shr_…)
project_idstringThe publishing project
resourcestringSRN of the shared resource, srn:<project_id>:<type>:<id>
actionsstring[]The actions granted
granteestringThe project the share is offered to, or *
capobject | null{ calls, window }: the calls each accepting project may make per window; null for none
suspended_atstring | nullSet while the share is suspended
revoked_atstring | nullSet once the publisher revoked the share
acceptanceobject | nullOn a grantee's read only: that project's acceptance
projectionobject | nullOn a single-share read only: what a grantee sees of the resource; null once it is gone
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-updated timestamp

ShareAcceptance​

FieldTypeDescription
idstringPublic identifier (e.g. shr_acc_…)
share_idstringThe accepted share
project_idstringThe accepting project
statusstringactive or revoked
revoked_bystring | nullpublisher or consumer
accepted_atstringWhen the project last accepted
revoked_atstring | nullWhen the acceptance was revoked
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-updated timestamp

Key Concepts​

Shareable types​

The types a share may name, and what it may grant on each, are one fixed table. An action outside it is 400 VALIDATION_FAILED, and no write action is grantable.

TypeGrantable actionsprojection
tooltools:CallToolid, name, description, parameters — never execute, mcp, headers or output_mapping
agentagents:CreateAgentGenerationid, name — never instructions, provider or tools

resource names one concrete resource in the publishing project. There is no wildcard: srn:proj_P:tool:* would grant tools that do not exist yet, so ten tools are ten shares. A resource in another project is 400 VALIDATION_FAILED, and one that does not exist is 400 TOOL_NOT_FOUND / 400 AGENT_NOT_FOUND.

Grantee and acceptance​

grantee is a project id, or * for a public share. A public share is an offer: each consuming project still accepts it, so the publisher can list and revoke consumers one by one. * is 403 PUBLIC_SHARES_DISABLED unless the deployment sets SHARES_ALLOW_PUBLIC=true.

POST /api/v1/shares/{share_id}/accept is idempotent. A grantee reads a share addressed to it, or a public one by id, before accepting. There is no cross-project listing of offers: GET /api/v1/shares with role=grantee lists what is addressed to the project and the public shares it already accepted. Discovering public shares is the job of the platform built on SOAT.

Which side a call acts for​

The publisher authorizes against srn:<publisher>:share:<id>, a grantee against srn:<grantee>:share:<id>. The acting project is the one the request names in project_id, else the one a scoped credential is bound to. With neither, or when it is the publisher, the call is the publisher's. So a key scoped to the grantee project needs no project_id, and a share not addressed to the acting project reads as 404.

GET /api/v1/shares/{share_id} and POST /api/v1/shares/{share_id}/revoke serve both sides. Suspend, resume, delete and the acceptance routes are the publisher's.

Suspend and revoke​

SuspendRevoke
ReversibleYes: POST /api/v1/shares/{share_id}/resumeNo
AcceptancesKept, and denied while suspendedMarked revoked
Consumer re-accepts afterNever neededRefused with 403 SHARE_REVOKED

Revoke applies to one acceptance or to the whole share:

Accepting a suspended share records the acceptance, which grants nothing until the share is resumed.

GET /api/v1/shares/{share_id}/references, with the grantee project as project_id, lists the same resources: every agent, pipeline, ingestion rule, orchestration, trigger and formation of that project naming the shared resource, as { type, id }, paged with limit (default 50, max 100) / offset in a { data, total, limit, offset } envelope. 409 SHARE_IN_USE lists every one, unpaged. The publisher's revoke is never refused.

Cap​

A share's cap bounds what each accepting project may spend of the publisher's credentials and the target's rate limit: { "calls": 1000, "window": "rolling_1h" } admits 1,000 calls per acceptance per window. window is one of rolling_1m, rolling_1h, rolling_24h and calendar_month, fixed and aligned like a quota's window: rolling_1h resets on the hour. Set it on create or with PATCH /api/v1/shares/{share_id} (shares:UpdateShare); null removes it, and the next call reads the new value.

A call is one tool call or one agent turn through the share, wherever it starts. A call past the cap reaches nothing and is not metered:

  • a direct call answers 429 SHARE_CAP_EXCEEDED with a Retry-After header and meta.retry_after;
  • inside a generation it is the call's tool error, and the turn goes on;
  • in an ingestion converter the document fails with CONVERTER_FAILED.

Each refusal writes a share_cap_exceeded activity entry in the consumer project, with the share as ref_id. The publisher's own calls are never capped.

Consumers are told​

Suspend, resume and every publisher-side revoke write a share_suspended, share_resumed or share_revoked entry in the activity feed of each affected consumer project. ref_id is the share, and detail carries share_id, resource and publisher_project_id. Deleting a share, or an active acceptance, writes share_revoked too. A consumer revoking its own acceptance writes nothing.

Using a shared tool​

Once a project accepts a share of a tool, it names that tool by id like one of its own in:

  • an agent's tool_bindings, active_tool_ids and step_rules[].active_tool_ids;
  • a pipeline step's tool_id;
  • an orchestration tool, poll or approval node;
  • a trigger's tool target;
  • an ingestion rule's tool_id, including one declared in a formation template.

Every other reference to a tool — a decider, inline decision or eval scorer backend, a memory rule, a workflow dispatch, a guardrail's context_tool_id — names one of the project's own tools only.

A key or OAuth token scoped to the grantee project also reaches the shared tool on its own routes, when its policies allow the action in that project:

A share that is not accepted, suspended or revoked resolves nothing, and nothing runs, so nothing is metered:

  • a write naming it is 400 TOOL_NOT_FOUND, and a call or read on its own route answers 403 API_KEY_PROJECT_SCOPE;
  • an agent's binding to it is left out of the turn: the model is told the tool is unavailable, and a tool_resolution_failed activity entry records why;
  • an ingestion rule converting with it fails the document with CONVERTER_FAILED;
  • a formation naming it fails to deploy with Tool not found: <tool_id>.

A call already running when the share goes away completes and is metered.

A call through a share:

  • Runs as the publisher's tool: its endpoint, its {{secret:...}} references and its presets resolve in the publisher's project.
  • Is governed by the caller: the calling project's guardrails apply, and so do the calling agent's; the tool's own guardrail_ids name guardrails in the publisher's project and do not.
  • Names the caller: the tool receives the calling project's id as the calling_project_id tool context key, so it can refuse by grantee. The key is reserved: a caller cannot set it, and a call in the tool's own project carries none.
  • Is metered in the calling project: its tool_execution event belongs to the caller, with publisher_project_id naming the publisher. See Usage.

Using a shared agent​

Once a project accepts a share of an agent, the agent runs in that project from:

  • POST /api/v1/agents/{agent_id}/generate, with a key or OAuth token scoped to the grantee project;
  • a conversation's generation, naming it as agent_id, and a message it authors;
  • a session, opened with a key or OAuth token scoped to the grantee project;
  • an orchestration agent node;
  • an ingestion rule's agent_id, as its converter, including one declared in a formation template.

Every other reference to an agent — a decider, an inline decision, an eval, a memory rule, a workflow dispatch — names one of the project's own agents only.

A turn through a share:

  • Runs on the publisher's configuration: its instructions, provider credential or model route, version and rollout, tools and knowledge resolve in the publisher's project. Its own tools are called through the share, carrying calling_project_id.
  • Is governed by the grantee: the grantee project's quotas, pause state and guardrails apply; the agent's own guardrail_ids name guardrails in the publisher's project and do not. The agent is offered no write_memory tool, so the grantee's conversation is never written into the publisher's memory store.
  • Is recorded in the grantee: its generation, trace and content are the grantee project's, under its zero-retention mode, retention and purge; the publisher holds no copy. The generation listing's agent_id filter accepts the shared agent's id.
  • Is metered in the grantee: its llm_tokens events belong to the grantee, with publisher_project_id naming the publisher and ai_provider_id the publisher's provider, priced from the publisher's price book; agent_id on the event listing and the rollup accepts the shared agent's id. See Usage.

A turn that pauses for client tool outputs or a tool-call approval is resumed on the agent's own routes, which a grantee-scoped credential does not reach.

Deleting the agent in the publisher's project leaves the grantee's records in place: its generations, traces and sessions keep agent_id, and a session whose agent is gone answers a new turn with 400 AGENT_NOT_FOUND.

Accepted shares are dependents​

DELETE /api/v1/tools/{tool_id} answers 409 TOOL_HAS_DEPENDENTS and DELETE /api/v1/agents/{agent_id} answers 409 AGENT_HAS_DEPENDENTS while another project has an active acceptance of a live share, with meta.accepted_share_count. force=true revokes the shares, then deletes. Any delete of a shared resource revokes its remaining shares, accepted or not.

Deleting a consumer project removes its acceptances; deleting the publisher project with force=true removes its shares.

Configuration​

Environment VariableRequiredDescription
SHARES_ALLOW_PUBLICNotrue allows grantee: "*". Off by default.

Examples​

Share a tool with another project​

soat create-share \
--project-id proj_P \
--resource "srn:proj_P:tool:tool_OCR" \
--actions '["tools:CallTool"]' \
--grantee proj_Q

Accept it as the grantee​

soat accept-share --share-id shr_ABC --project-id proj_Q

Cut one consumer​

soat list-share-acceptances --share-id shr_ABC --status active
soat revoke-share-acceptance --share-id shr_ABC --acceptance-id shr_acc_XYZ