Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .stainless/stainless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,43 @@ resources:
models:
session_list_response: '#/components/schemas/SessionListResponse'

agents:
models:
agent: '#/components/schemas/Agent'
agent_create_request: '#/components/schemas/AgentCreateRequest'
agent_create_response: '#/components/schemas/AgentCreateResponse'
agent_update_request: '#/components/schemas/AgentUpdateRequest'
agent_list_response: '#/components/schemas/AgentListResponse'
agent_device_code: '#/components/schemas/AgentDeviceCode'
agent_device_code_status_response: '#/components/schemas/AgentDeviceCodeStatusResponse'
agent_device_code_redeem_response: '#/components/schemas/AgentDeviceCodeRedeemResponse'
agent_action: '#/components/schemas/AgentAction'
agent_action_list_response: '#/components/schemas/AgentActionListResponse'
agent_action_reject_request: '#/components/schemas/AgentActionRejectRequest'
methods:
create: post /agents
list: get /agents
retrieve: get /agents/{agentId}
update: patch /agents/{agentId}
delete: delete /agents/{agentId}
list_approvals: get /agents/approvals
subresources:
policy:
models:
agent_policy: '#/components/schemas/AgentPolicy'
agent_policy_update_request: '#/components/schemas/AgentPolicyUpdateRequest'
methods:
update: patch /agents/{agentId}/policy
device_codes:
methods:
regenerate: post /agents/{agentId}/device-codes
get_status: get /agents/device-codes/{code}/status
redeem: post /agents/device-codes/{code}/redeem
actions:
methods:
approve: post /agents/{agentId}/actions/{actionId}/approve
reject: post /agents/{agentId}/actions/{actionId}/reject

internal_accounts:
methods:
export: post /internal-accounts/{id}/export
Expand Down Expand Up @@ -500,6 +537,16 @@ client_settings:
role: password
description: API token authentication using format `<api token id>:<api client secret>`
read_env: GRID_CLIENT_SECRET
agent_access_token:
type: string
nullable: true
auth:
security_scheme: AgentAuth
description: >-
Bearer access token obtained by redeeming a device code. Required when calling
agent-scoped endpoints (e.g. `GET /agents/me/...`). Leave unset for platform-scoped
operations.
read_env: GRID_AGENT_ACCESS_TOKEN
webhook_signature:
type: string
nullable: true
Expand Down
123 changes: 67 additions & 56 deletions mintlify/global-accounts/agents/approvals-and-audit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,86 +17,96 @@ Treat approval requests like first-class user tasks. They should be easy to find

## Approval lifecycle

When an agent requests an action that is not eligible for automatic execution:
When an agent submits an action that is not eligible for automatic execution, Grid creates an `AgentAction` in `PENDING_APPROVAL` status and fires an `AGENT_ACTION.PENDING_APPROVAL` webhook to your backend. The full action payload is included in the webhook body so you can render the approval UI and send a push notification to the customer without a second API call.

1. Grid creates a permission request for your product.
2. Grid dispatches that request to your product, typically via webhook or another configured callback mechanism.
3. Your app or dashboard shows the user the requested amount, source account, destination, and reason.
4. The user approves or rejects the action from that trusted surface.
5. Grid executes or rejects the action based on the latest account and policy state.
6. Grid records the final status and links it to the resulting transaction or quote execution.
1. Agent submits an action (quote execution or transfer).
2. Grid creates an `AgentAction` with `status: PENDING_APPROVAL` and returns it to the agent.
3. Grid fires `AGENT_ACTION.PENDING_APPROVAL` to your webhook endpoint with the full action payload.
4. Your backend sends a push notification to the customer.
5. Your app shows the customer the requested amount, accounts, and reason.
6. The customer approves or rejects from that trusted surface.
7. Your backend calls `POST /agents/{agentId}/actions/{actionId}/approve` or `.../reject`.
8. Grid executes the action and the `AgentAction` transitions to `APPROVED`, then `COMPLETED` — or `REJECTED` if declined.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 COMPLETED status does not exist in the spec

Step 8 describes the AgentAction transitioning to APPROVED, then COMPLETED. However, AgentActionStatus.yaml only defines four values: PENDING_APPROVAL, APPROVED, REJECTED, and FAILED — there is no COMPLETED terminal state. APPROVED is itself the completion state ("execution is in progress or completed" per the schema description). Documenting a nonexistent status here will cause integrators to poll for a terminal state that never arrives.

Suggested change
8. Grid executes the action and the `AgentAction` transitions to `APPROVED`, then `COMPLETED` — or `REJECTED` if declined.
8. Grid executes the action and the `AgentAction` transitions to `APPROVED` — or `REJECTED` if declined. If the underlying quote expires before approval is granted, the action transitions to `FAILED` instead.
Prompt To Fix With AI
This is a comment left during a code review.
Path: mintlify/global-accounts/agents/approvals-and-audit.mdx
Line: 29

Comment:
**`COMPLETED` status does not exist in the spec**

Step 8 describes the `AgentAction` transitioning to `APPROVED`, then `COMPLETED`. However, `AgentActionStatus.yaml` only defines four values: `PENDING_APPROVAL`, `APPROVED`, `REJECTED`, and `FAILED` — there is no `COMPLETED` terminal state. `APPROVED` is itself the completion state ("execution is in progress or completed" per the schema description). Documenting a nonexistent status here will cause integrators to poll for a terminal state that never arrives.

```suggestion
8. Grid executes the action and the `AgentAction` transitions to `APPROVED` — or `REJECTED` if declined. If the underlying quote expires before approval is granted, the action transitions to `FAILED` instead.
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code


## Approval outcomes

An approval request shown to the user is still subject to the latest account and policy state when Grid processes the decision.

For example, between the original request and the final decision:
An approved action is still subject to the latest account and policy state when Grid executes it. Between the original request and your approval decision:

- The agent may have been paused
- The customer's limits may have been reduced
- The permitted accounts may have changed
- Another action may already have consumed the available daily spend
- Another action may have already consumed the available daily spend
- For `EXECUTE_QUOTE` actions, the underlying quote may have expired

<Warning>
An approval in your UI is not always a guarantee that the original request will execute successfully. Your product should be ready to show a final rejected or failed state if the request is no longer valid when Grid processes it.
An approval is not always a guarantee that the action will execute. Your product should be ready to show a `FAILED` state — for example, if a quote expired while waiting for approval. The `AgentAction.status` field reflects the final outcome.
</Warning>

## Activity and audit records

The activity and audit data returned by Grid makes it easy for customers and operators to understand both intent and outcome.
## AgentAction fields

Each activity record captures:
`GET /agents/approvals` returns a paginated list of `AgentAction` objects. Filter by `agentId` or `customerId` to scope results. Each record includes:

- Agent ID and display name
- Action type
- Requested amount and asset
- Source and destination context
- Reason or natural-language detail
- Policy decision
- Approval status
- Linked Grid quote ID or transaction ID, when available
- Created, approved, rejected, and completed timestamps
- `id` — action identifier (e.g. `AgentAction:...`)
- `agentId` — the agent that submitted the action
- `customerId` / `platformCustomerId` — the customer on whose behalf the agent acted
- `status` — `PENDING_APPROVAL` while awaiting a decision; transitions to `APPROVED`, `REJECTED`, or `FAILED`
- `type` — `EXECUTE_QUOTE`, `TRANSFER_OUT`, or `TRANSFER_IN`
- `quote` — for `EXECUTE_QUOTE` actions, the full quote object including amounts, currencies, exchange rate, and destination
- `transferDetails` — for `TRANSFER_OUT` / `TRANSFER_IN` actions, amount, currency, and source/destination account IDs
- `transaction` — populated after the action is approved and execution begins; absent while pending or rejected
- `rejectionReason` — optional reason string set when your platform rejects the action
- `createdAt` / `updatedAt` — timestamps for the action lifecycle

For example, a permission request surfaced in your product might look like:
For example, a pending approval request delivered via webhook might look like:

```json
{
"id": "Approval:019542f5-b3e7-1d02-0000-000000000099",
"id": "AgentAction:019542f5-b3e7-1d02-0000-000000000099",
"agentId": "Agent:019542f5-b3e7-1d02-0000-000000000042",
"status": "PENDING",
"actionType": "EXECUTE_QUOTE",
"quoteId": "Quote:019542f5-b3e7-1d02-0000-000000000025",
"source": {
"accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965",
"currency": "USD"
},
"destination": {
"accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123",
"currency": "EUR"
"customerId": "Customer:019542f5-b3e7-1d02-0000-000000000010",
"platformCustomerId": "user-a1b2c3",
"status": "PENDING_APPROVAL",
"type": "EXECUTE_QUOTE",
"quote": {
"id": "Quote:019542f5-b3e7-1d02-0000-000000000006",
"totalSendingAmount": 50000,
"sendingCurrency": { "code": "USD", "name": "United States Dollar", "symbol": "$", "decimals": 2 },
"totalReceivingAmount": 4625000,
"receivingCurrency": { "code": "INR", "name": "Indian Rupee", "symbol": "₹", "decimals": 2 },
"exchangeRate": 92.5,
"feesIncluded": 250,
"expiresAt": "2025-10-03T15:00:30Z"
},
"sendingAmount": {
"amount": 50000,
"currency": {
"code": "USD",
"decimals": 2
}
},
"description": "Send supplier payout",
"policyDecision": "APPROVAL_REQUIRED",
"createdAt": "2025-10-03T15:00:00Z"
"createdAt": "2025-10-03T15:00:00Z",
"updatedAt": "2025-10-03T15:00:00Z"
}
```

## Approving and rejecting

Call these endpoints from your platform backend using your platform credentials (`BasicAuth`):

```bash
# Approve
POST /agents/{agentId}/actions/{actionId}/approve

# Reject (optional reason body)
POST /agents/{agentId}/actions/{actionId}/reject
{
"reason": "Transaction amount exceeds customer's current risk limit."
}
```

This lets you present a clean customer-facing activity feed while preserving an operator-friendly audit trail.
Both return the updated `AgentAction`. The `agentId` and `actionId` are available in the webhook payload.

## Pause and revoke

Customers need immediate control over delegated access.

Support at least two controls:

- `Pause`: temporarily block new executions while preserving the agent configuration.
- `Revoke`: permanently remove delegated access and invalidate the agent connection.
- **Pause** — temporarily block new executions while preserving the agent configuration. Use `PATCH /agents/{agentId}` with `isPaused: true`.
- **Revoke** — permanently remove delegated access. Use `DELETE /agents/{agentId}`.

Pause is useful for temporary uncertainty. Revoke is appropriate when a device is lost, a credential is exposed, or the customer no longer wants the agent connected.

Expand All @@ -108,18 +118,19 @@ Your approval UI should answer:

- What is the agent trying to do?
- Which account is affected?
- How much value is moving?
- How much value is moving, and in what currencies?
- Who or what is on the other side of the transaction?
- Why is approval required?

If the user cannot answer those questions quickly, the approval surface is too opaque.
All of this information is present in the `AgentAction` payload delivered via webhook. If the user cannot answer those questions quickly, the approval surface is too opaque.

## Integration guidance

- Use Grid events or webhooks to reconcile approval decisions with the final activity and transaction state.
- Treat approval decisions as idempotent in your integration so duplicate taps or retries do not create a confusing UX.
- Preserve status history in your UI instead of collapsing everything into a single final state.
- Make agent connection status, approval queue state, and audit history easy to surface in your apps and dashboards.
- Subscribe to `AGENT_ACTION.PENDING_APPROVAL` webhooks and send the customer a push notification immediately — quote-based actions expire quickly.
- Use `GET /agents/approvals` to build an in-app approval queue as a fallback for users who miss the push notification.
- Treat approval and rejection calls as idempotent in your integration so duplicate taps or retries do not cause confusing UX.
- Show `FAILED` and `REJECTED` outcomes clearly — they are distinct states with different meanings for the customer.
- Make agent connection status, approval queue state, and action history easy to surface close to the customer's account view.

<Tip>
Show approvals and audit history close to the account or transaction views users already trust. That reduces confusion and makes agent activity feel like part of the main account lifecycle rather than a separate subsystem.
Expand Down
48 changes: 22 additions & 26 deletions mintlify/global-accounts/agents/policies-and-permissions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ An agent policy should answer four questions:

## Permissions

Grid exposes explicit allowlists instead of broad agent access. Common permissions include:
Grid exposes explicit allowlists instead of broad agent access. Available permissions are:

- View balances and account details
- View transaction history
- Create external accounts
- Create quotes
- Execute withdrawals or quote-backed transfers
- Fund or move value between allowed accounts
| Permission | What it allows |
|---|---|
| `VIEW_TRANSACTIONS` | List and retrieve transactions and account balances |
| `CREATE_TRANSFERS` | Initiate same-currency transfers |
| `CREATE_QUOTES` | Create cross-currency quotes |
| `EXECUTE_QUOTES` | Execute cross-currency quotes |
| `MANAGE_EXTERNAL_ACCOUNTS` | Create and manage external accounts |

These permissions are intentionally narrow and map to concrete Grid-backed actions rather than broad scopes such as "manage wallet."

Expand Down Expand Up @@ -72,8 +73,8 @@ Keep limits in the smallest currency unit used by Grid so comparisons are exact

Each action should resolve to one of two execution modes:

- `auto`: Grid may execute the action immediately after policy validation.
- `approval_required`: Grid creates a pending approval and dispatches it to your product for customer confirmation.
- `AUTO`: Grid may execute the action immediately after policy validation.
- `APPROVAL_REQUIRED`: Grid creates a pending approval and dispatches it to your product for customer confirmation.

You can apply execution mode globally or per account. A practical pattern is to allow automatic execution for low-risk actions and require approvals for higher-value withdrawals or new destination setup.

Expand Down Expand Up @@ -106,37 +107,32 @@ For example, Grid stores and enforces a policy object shaped like:
```json
{
"permissions": [
"view_balances",
"view_transactions",
"create_external_accounts",
"execute_withdrawals"
"VIEW_TRANSACTIONS",
"CREATE_QUOTES",
"EXECUTE_QUOTES",
"MANAGE_EXTERNAL_ACCOUNTS"
],
"defaultExecutionMode": "approval_required",
"defaultExecutionMode": "APPROVAL_REQUIRED",
"spendingLimits": {
"perTransaction": {
"amount": 50000,
"currency": "USD"
},
"daily": {
"amount": 200000,
"currency": "USD"
},
"dailyTransactionCount": 5
"currency": "USD",
"perTransactionLimit": 50000,
"dailyLimit": 200000,
"dailyTransactionLimit": 5
},
"accountRestrictions": {
"allowedAccountIds": [
"InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
]
},
"approvalThresholds": {
"amount": 25000,
"currency": "USD"
"currency": "USD",
"amount": 25000
}
}
```

<Info>
Policy amounts use the smallest unit of the specified currency. For example, `50000` with `"currency": "USD"` means $500.00. If Grid compares across currencies or assets, it uses the current exchange rate at evaluation time.
Policy amounts are integers in the smallest unit of the specified currency. For example, `50000` with `"currency": "USD"` means $500.00. When a transaction is denominated in a different currency, Grid converts using the current exchange rate at evaluation time.
</Info>

## Practical guidance
Expand Down
Loading
Loading