Cookbook
Mint a portfolio-scoped token
Bind an agent token to one portfolio. The key operates that portfolio's member entities end-to-end; everything else returns 403, and other portfolios' resources are invisible.
Last updated
Bind an agent token to one portfolio. The key operates that portfolio's member entities end-to-end; everything else returns 403, and other portfolios' resources are invisible.
Trigger
You're delegating one portfolio — to an end customer's integration, a fund team, or an agent — and the blast radius has to end at the portfolio's edge.
Call sequence
1. Mint the scoped token with your org key
POST /v1/tokens { tier, portfolio_id, principal, ... }2. Operate member entities with the scoped key
POST /v1/entities { portfolio_id, ... } // must match the bound portfolioIdempotency
Token creation idempotent via `Idempotency-Key`.
Errors
| Status | Code | Description |
|---|---|---|
403 | portfolio_scope_denied | The calling key is itself portfolio-scoped — only an unscoped org key can mint. |
404 | not_found | `portfolio_id` does not exist in your org and mode — opaque by design. |
Related
The call
Pass portfolio_id on the standard token-creation body.
Everything else — tier, principal binding, acknowledgements — works exactly as for
an unscoped token:
{
"tier": 3,
"portfolio_id": "pf_S4dGqL2c",
"principal": { "human_id": "usr_4Kj2m8pQ" },
"api_version": "2026-06-10",
"acknowledgements": [
{ "slug": "not_legal_advice", "stakeholder_id": "stk_Qw5xY2bR" },
{ "slug": "agent_action_binds_principal", "stakeholder_id": "stk_Qw5xY2bR" }
]
}Two rules govern the chain of custody:
- Only an unscoped org key can mint. A portfolio-scoped key cannot create or
rotate tokens —
createTokenis outside its allowed set, so the attempt returns403 portfolio_scope_denied. Scope cannot widen itself. - Rotation preserves the binding. When your org key rotates a scoped token, the
replacement carries the same
portfolio_id— the attribution chain stays intact.
What the scoped key can do
The allowed set is deliberately narrow in v1:
- Its own portfolio, read-side — retrieve the bound portfolio, page its member
list, pull the compliance rollup.
GET /v1/portfoliosreturns only the bound portfolio. - Member entities, end-to-end — create entities into the bound portfolio
(
portfolio_idon the body must match), read and submit them, create and read filings, read entity compliance views, documents, stakeholders, and grants.
Everything else returns 403 portfolio_scope_denied: creating or updating
portfolios, attaching or detaching members, batch formation (an org-key operator
action), minting or rotating tokens, webhook endpoints, agent policies, search,
event listings, reports, and account operations. The 403 is a loud, named denial —
the caller holds a real credential that lacks the privilege, and should know it.
What the scoped key cannot see
Cross-portfolio probes fail differently — opaquely. An entity, filing, or document
that belongs to a different portfolio returns 404 not_found, byte-identical to an
id that never existed. A scoped key cannot enumerate, confirm, or time-probe its way
to learning what else lives in the org.
403 portfolio_scope_denied → known operation, insufficient privilege
404 not_found → the resource does not exist, for youVariations
The embedded pattern: at customer onboarding, create their portfolio with
metadata.customer_id, mint a scoped token against it, and store the token
alongside the customer record. Every call your product makes on that
customer's behalf uses their key — so isolation is enforced by Matter, not by
discipline in your request layer.
const portfolio = await matter.portfolios.create({
name: `Customer ${customer.id}`,
metadata: { customer_id: customer.id },
});
const token = await matter.tokens.create({
tier: 3,
portfolio_id: portfolio.id,
principal: { human_id: operatorUserId },
api_version: "2026-06-10",
acknowledgements,
});
// store token alongside the customer; revoke at offboardingAt offboarding, POST /v1/tokens/{id}/revoke with your org key kills the
customer's access immediately; the entities and their records remain intact.
The delegation pattern: one portfolio per fund, one scoped key per fund team
or per agent runtime. The Fund II team's key sweeps Fund II's compliance
calendar and files what's due; pointed at a Fund III entity, it gets 404 —
the same boundary that separates a platform's customers separates an
administrator's funds.
const fundTwoKey = await matter.tokens.create({
tier: 3,
portfolio_id: "pf_FundII",
principal: { human_id: fundTwoLeadId, agent_id: "agt_FundIIOps" },
api_version: "2026-06-10",
acknowledgements,
});Per-fund keys also make audit reads legible: AuditEntry records the acting
token on every mutation, so "which team touched this entity" is a filter,
not an investigation.
Related
- Create a token — full request schema and acknowledgement slugs
- Tenancy: portfolios — the multi-tenancy model the binding builds on
- Build a portfolio dashboard — the rollup reads in the allowed set
- Agents and tokens — the four-tier token model