concepts
concepts
Six objects explain all of Nostro: the workspace, its agents, their keys, their mandates, approval requests and the audit log. In the sandbox, money lives on a virtual ledger.
workspace
A workspace is one company. It owns one EUR account (its balance is what overview and get_wallet report), one admin key and every agent created in it. IDs look like ws_7c1e9a42b0d3. A sandbox signup creates a fresh, isolated workspace with 10 000 EUR of fake money and up to 25 agents.
agents
An agent is the unit of control: one agent = one virtual card + one API key + one mandate. Agents spend from the company account through their card; they never see the card number (it is masked) and cannot touch other agents.
| status | meaning |
|---|---|
active | Can spend within its mandate. |
frozen | Every spend fails with 403 until an admin unfreezes it. Pending approvals cannot execute either. |
revoked | Key disabled, card cancelled. Irreversible; the agent disappears from list_agents. |
The agent object, as returned by admin tools:
{
"id": "agt_3f2b8d10",
"name": "demo-agent",
"purpose": "Demo agent (sandbox, fake euros)",
"status": "active",
"keyPrefix": "nst_k3Jd",
"card": {
"id": "vcard_5e1d77a0c4b2",
"status": "Enabled",
"maskedNumber": "4970 •••• •••• 3182",
"consentUrl": null
},
"policy": {
"currency": "EUR",
"perTxMax": 500,
"dailyMax": 2000,
"monthlyMax": 5000,
"approvalAbove": 250,
"allowedMccs": [],
"blockedMerchants": [],
"transfersEnabled": false,
"transferApprovalAbove": 0
},
"spend": {
"today": 49,
"month": 49
},
"createdAt": "2026-11-05T09:12:04.118Z"
}keys
| prefix | scope | where it lives |
|---|---|---|
nst_ | One agent: its wallet, its payments, its activity, its requests. | In the agent's MCP config or environment. |
nsa_ | The whole workspace: agents, mandates, approvals, funding, audit. | With humans only: a secret manager, your terminal. |
Keys are random, shown once, and stored only as a SHA-256 hash. The first 8 characters (keyPrefix, e.g. nst_k3Jd) are kept so you can tell keys apart. See security.
mandates
A mandate (the policy field) is the set of rules evaluated before any money moves:
| field | type | default | description |
|---|---|---|---|
perTxMax | number | 100 | Maximum amount of a single card payment, in EUR. Above it the payment is blocked. |
dailyMax | number | 300 | Maximum executed spend per calendar day (UTC), card payments and transfers combined. |
monthlyMax | number | 1000 | Monthly budget. Also set as the spending limit on the card itself (defense in depth). |
approvalAbove | number | 50 | Card payments strictly above this amount wait for a human approval instead of executing. |
allowedMccs | string[] | [] | Allowed merchant category codes (4 digits). Empty means every category is allowed. |
blockedMerchants | string[] | [] | Merchant names to block. Case-insensitive substring match on the merchant name. |
transfersEnabled | boolean | false | Whether the agent may send SEPA transfers at all. |
transferApprovalAbove | number | 0 | Transfers strictly above this amount need a human approval. 0 means every transfer is reviewed. |
Every payment gets one of three outcomes: allow, review (human approval) or block. Full rules and examples in mandates & policy engine.
approval requests
When the mandate says review, Nostro creates a request (req_…) instead of charging the card, and returns pending_approval to the agent. A human with the admin key approves or rejects it. Approval executes the payment immediately with the original parameters.
| status | meaning |
|---|---|
pending | Waiting for a human. |
executed | Approved and paid (or transfer initiated). result holds the payment. |
failed | Approved but could not execute (agent frozen, bank decline). result.reason says why. |
rejected | Rejected by a human. Nothing charged. |
Only one decision is ever applied to a request, even if two admins click at once; the second gets 400. Pending amounts do not count toward daily or monthly spend until they execute.
audit log
Every action writes an event: who (agent), what (type), how much, with whom (counterparty), the decision and its reason, and a reference (transaction, card or request id). Agents read their own events with list_my_activity; admins read everything with audit_log.
| type | written when |
|---|---|
card_payment | An agent tries to pay (any outcome). |
transfer | An agent tries a SEPA transfer. |
funding | The sandbox account is funded. |
agent_created, policy_updated | Admin creates an agent or changes a mandate (the patch is in reason). |
agent_frozen, agent_unfrozen, agent_revoked | Status changes. |
| decision | meaning |
|---|---|
executed | Money moved (or admin action applied). |
denied | Blocked by the mandate. |
pending_approval | Held for a human; bank_ref is the request id. |
rejected_by_human | A human rejected the request. |
declined_by_bank | Allowed by the mandate but refused by the card issuer (e.g. card limit). |
virtual ledger (sandbox)
In the sandbox there is no real money and no real card network:
- Balances, cards and transactions live on a virtual ledger. Amounts are EUR with 2 decimals, returned as strings in money objects:
{"value":"10000.00","currency":"EUR"}. paysimulates a card authorization and debit. Debits are atomic, so concurrent payments can never overdraw the balance (InsufficientFunds). The card's monthly limit equalsmonthlyMax; beyond it the issuer declines withCardSpendingLimitExceeded.fund_accountcredits fake euros (up to 100 000 EUR per call, balance capped at 1 000 000 EUR). IBANs use the fictitious bank code 99999 and are never routable; card numbers start with 4970 and are masked.- Merchants are whatever name you pass; no money reaches them. Use any 4-digit MCC to test category rules.
overviewreportsbankMode: "ledger"andworkspace.kind: "sandbox", so you always know you are on the simulated ledger. Each sandbox workspace is isolated: an admin key never sees another workspace's agents or requests (it gets404).