# Manage intents and approvals

Use this page for day-to-day intent submission, review, approval, rejection, dry runs, and state checks. For the concept, see [Intents and approvals](/pt-br/products/custody/governance/intents).

## Intent actions

| Action | UI procedure | API procedure |
|  --- | --- | --- |
| Request a change | [Request a change in the UI](#request-a-change-in-the-ui) | [Submit an intent with the API](#submit-an-intent-with-the-api) |
| Submit a system-signed proposal | Not available in the UI. | [Submit a system-signed intent with the API](#submit-a-system-signed-intent-with-the-api) |
| Dry run an intent | Use the API procedure. | [Dry run an intent with the API](#dry-run-an-intent-with-the-api) |
| Approve or reject an intent | [Approve or reject an intent in the UI](#approve-or-reject-an-intent-in-the-ui) | [Approve or reject an intent with the API](#approve-or-reject-an-intent-with-the-api) |
| Check request, intent, entity, transaction state, and remaining approvers | [Check state in the UI](#check-state-in-the-ui) | [Check state with the API](#check-state-with-the-api) |


## Intent workflow

During initial system setup, the `POST /v1/genesis` API operation creates the initial environment and entities without authentication or governance. After that initial setup, changes to system data follow the intent workflow.

1. Dry run the payload when you are using the API or testing a high-risk payload.
2. Submit the intent. Most submissions are user-signed. API service callers can submit system-signed proposals when the deployment and policies allow it.
3. Review the pending intent.
4. Approve or reject.
5. Check request, intent, entity, and transaction state.


## Request a change in the UI

Every change you make to system data in the UI creates an intent. For example, creating a domain, creating a user, registering an endpoint, or sending assets all follow the same high-level pattern:

1. Navigate to the relevant UI area.
2. Enter the change details.
3. Click **Submit for approval**.
4. Sign the operation with the **Ripple Custody: Auth & Sign** app. For signing instructions, see [Sign UI operations](/pt-br/products/custody/identity-and-access/authentication/ui-authentication#sign-ui-operations).
5. Track the intent until it is approved, rejected, expired, or executed.


For UI and other user-signed submissions, the submitter of the intent acts as the first approver. If the selected policy includes an approval workflow that does not match the submitter's user role, the intent fails.

## Dry run an intent with the API

Dry runs simulate user-signed intent submission so you can understand whether a payload is valid before proposing and signing it.

Use dry runs for:

- Policy, domain, user, and user role-assignment changes.
- High-risk or complex transactions.
- Payloads where condition or workflow behavior is uncertain.
- Payloads that depend on ledger-specific validation, balances, destinations, or fees.


To perform a dry run:

1. Prepare the request body in the format shown in [Dry run and signature request body](/pt-br/products/custody/governance/intents/intent-request-structure#dry-run-and-signature-request-body).
2. Call the [Perform a dry run](/pt-br/products/custody/reference/api/openapi/intents/intentdryrun) operation.
3. Review the `success` and `errors` fields. For transaction intents, inspect `result` and `estimate`: their failure variants include a `hint` field.


The SDK examples on this page assume a configured client. To configure one, see [Integrate via SDK](/pt-br/products/custody/get-started/integrate-via-sdk). The following examples perform a dry run with the API and with each SDK:

API
```sh
curl -X POST "${CUSTODY_API_URL}/v1/intents/dry-run" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d @intent-dry-run.json
```

TypeScript
The TypeScript SDK has no general-purpose intent dry run. For transfer orders, `transactions.dryRun` performs a dry run of a `v0_CreateTransferOrder` intent to a single destination:

```typescript
const estimate = await client.transactions.dryRun(accountId, tickerId, {
  toAddress: destinationAddress,
  amount: '1000',
  feePriority: 'Low',
});
console.log(estimate.success, estimate.fee, estimate.errors);
```

Java
```java
// Wrap any intent payload model, such as CoreV0CreateTransferOrder.
CoreIntentDryRunResponse response = client.governance().intents().dryRun(
        domainId, client.getConfig().getAuthor(), new CoreUserIntentPayload(payloadModel));

// The response is a union keyed by intent type.
var result = response.getCoreIntentDryRunResponseV0CreateTransferOrder();
System.out.println(result.getSuccess() + " " + result.getErrors());
```

Python
The Python SDK has no general-purpose intent dry run. For transfer orders, `transactions.dry_run` performs a dry run of a `v0_CreateTransferOrder` intent to a single destination:

```python
estimate = client.transactions.dry_run(
    account_id,
    ticker_id,
    to_address=destination_address,
    amount="1000",
    fee_strategy="Low",
)
print(estimate.success, estimate.fee, estimate.errors)
```

The dry-run request body uses the user-signed proposal shape and includes an `author`. The API specification does not define a separate system-signed dry-run body.

## Submit an intent with the API

The [Propose an intent](/pt-br/products/custody/reference/api/openapi/intents/createintent) operation is the entry point for state mutation API requests. This procedure describes the standard user-signed proposal flow.

To submit an intent:

1. Prepare the intent request object with the author, expiry, target domain, intent ID, payload, description, custom properties, and `type: "Propose"`.
2. Canonicalize and sign the request object. For signing details, see [Authenticate API requests](/pt-br/products/custody/identity-and-access/authentication/authenticate-api-requests#signing-intents-state-mutation-operations).
3. Create the full request body with the signed `request` object and `signature`.
4. Call [Propose an intent](/pt-br/products/custody/reference/api/openapi/intents/createintent) with the signed request body.


With an SDK, each method that changes state performs steps 1 to 4 and returns a pending intent. The pending intent's wait methods poll the request state until Ripple Custody (Vault) accepts the request. They then poll the intent state until the intent reaches a final status. The following examples submit a transfer order with the API and with each SDK:

API
```sh
curl -X POST "${CUSTODY_API_URL}/v1/intents" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "requestId: ${REQUEST_ID}" \
  -d @signed-intent.json
```

TypeScript
```typescript
const pending = await client.transactions.transfer(
  accountId,
  tickerId,
  [{ destination: { type: 'Address', address: destinationAddress }, amount: '1000' }],
  'Low',
  { description: 'Transfer submitted with the SDK' },
);

await pending.waitAccepted(30_000); // Request accepted; pending.intentId is now set
await pending.waitExecuted(120_000); // Intent reached a final status
```

Java
```java
CoreCreateTransferOrderOutput output = new CoreCreateTransferOrderOutput()
        .destination(new CoreTransactionDestination(new CoreTransactionDestinationAddress()
                .address(destinationAddress)
                .type(CoreTransactionDestinationAddress.TypeEnum.ADDRESS)))
        .amount("1000");

PendingIntent pending = client.transactions().transfer(
        domainId, accountId, tickerId, List.of(output), CoreFeePriority.LOW, null, null, Map.of());

pending.waitAccepted(60);  // Request accepted; pending.getIntentId() is now set
pending.waitExecuted(120); // Intent reached a final status
```

Python
```python
pending = client.transactions.transfer(
    account_id,
    ticker_id,
    [{"address": destination_address, "amount": "1000"}],
    fee_strategy="Low",
    description="Transfer submitted with the SDK",
)

pending.wait_accepted(timeout=60)   # Request accepted; pending.intent_id is now set
pending.wait_executed(timeout=120)  # Intent reached a final status
```

For a complete program, see example `13` (create transfer order). To get the examples, see [Examples](/pt-br/products/custody/developer-experience/sdks#examples).

For more about pending intents and wait methods, see [SDK intents](/pt-br/products/custody/developer-experience/sdks/intents).

The `requestId` header is optional but recommended for traceability. You can use it to check request state.

The operation returns `202 Accepted` with an intent response when the request is accepted for asynchronous processing. This response means the workflow is scheduled. It does not mean the intent has executed or that the resulting entity or transaction state has changed.

## Submit a system-signed intent with the API

System-signed intent submission is for service callers that are allowed to propose specific intent types without sending a client-side payload signature. The request is still authenticated with a bearer token, signed internally by the platform, and governed by policies.

The Ripple Custody (Vault) SDKs don't submit system-signed proposals. Use the API for this flow.

Before using this flow, confirm that:

- System-signed intent configuration is available for the deployment.
- The active Gateway system signing public key is registered in Notary. To discover the active public key, call `GET /v1/system-signing/info`. For more information, see [System signing info endpoint](/pt-br/products/custody/deployment/reference/system-signed-intents#system-signing-info-endpoint).
- `NOTARY_SYSTEM_SIGNED_INTENTS_ENABLED` is set to `enabled: true`.
- The service caller can [obtain a bearer token](/pt-br/products/custody/identity-and-access/authentication/authenticate-api-requests#obtain-a-token-for-a-service-caller) accepted by the API.
- A policy with `intentOrigin: "SystemSigned"` matches the target domain, intent type, and service caller.


System-signed intent configuration is enabled by default at deployment level. This does not activate system-signed traffic. Until key registration, the runtime system property, and matching policies are in place, system-signed proposals fail closed.

Prepare a request body with `request.type: "SystemSigned"`. Do not include `request.author` or a top-level `signature`.

```json
{
  "request": {
    "type": "SystemSigned",
    "targetDomainId": "9067d363-6411-498b-a32b-15d230a86706",
    "id": "00baa536-421e-11ee-be56-0242ac120002",
    "expiryAt": "2026-06-30T15:30:00.000Z",
    "payload": {
      "type": "v0_CreateTransactionOrder",
      "...": "payload-specific fields"
    },
    "description": "Service-submitted transaction order",
    "customProperties": {}
  }
}
```

Submit the request:

```sh
curl -X POST "${CUSTODY_API_URL}/v1/intents" \
  -H "Authorization: Bearer ${SERVICE_JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "requestId: ${REQUEST_ID}" \
  -d @system-signed-intent.json
```

The response uses the same `202 Accepted` response shape as user-signed proposal submission. When you retrieve the intent or request state, service-authored records identify the submitter with `author.subject` or `requester.subject`. For how these relate to `context.submitter.subject` in policy conditions, see [Service-caller identity](/pt-br/products/custody/identity-and-access/authentication/authenticate-api-requests#service-caller-identity-submitter-author-and-requester).

Approvals and rejections stay user-signed
System-signed submission applies to proposals only. The service submitter does not count as a user approval. If the selected system-signed policy includes a workflow, user-signed approvers must satisfy it.

## Approve or reject an intent in the UI

You can approve or reject intents that are relevant for your user. The intents you can approve or reject depend on your user role and the policies that apply in the domain hierarchy.

To approve or reject an intent:

1. Navigate to **Intents**.
2. Select the **Waiting for decision** tab.
3. Select the arrow next to the intent's status to open the decision workflow panel.
4. Review the intent details, selected policy, approval workflow, and any compliance or incident notes.
5. Scroll to the end of the workflow and select **Approve** or **Reject**.
6. Enter the reason. The reason is mandatory for rejections and optional for approvals.
7. Sign the operation with the **Ripple Custody: Auth & Sign** app.


If you approve the intent, it continues through the workflow. When all required steps are complete, the intent can execute. If you reject the intent, the status changes to **Rejected**.

## Approve or reject an intent with the API

To approve or reject an intent with the API:

1. Retrieve the intent details with [List intents](/pt-br/products/custody/reference/api/openapi/intents/getintents) or [Get intent](/pt-br/products/custody/reference/api/openapi/intents/getintent).
2. Prepare the approval or rejection request body. Use the intent ID and the `proposalSignature` from the intent details.
3. Canonicalize and sign the request object. For signing details, see [Authenticate API requests](/pt-br/products/custody/identity-and-access/authentication/authenticate-api-requests#signing-intents-state-mutation-operations).
4. Call [Approve intent](/pt-br/products/custody/reference/api/openapi/intents/approveintent) or [Reject intent](/pt-br/products/custody/reference/api/openapi/intents/rejectintent).


Approval and rejection API calls also return `202 Accepted` when the signed decision is accepted for asynchronous processing. Check the request or intent state to confirm whether the workflow processed the decision successfully.

With an SDK, you approve or reject by intent ID. The SDK fetches the `proposalSignature` (the Java SDK takes it as an argument), then builds, signs, and submits the decision. If you hold the pending intent from your own proposal, you can also call its `approve` or `reject` method.

The following examples approve and reject an intent with the API and with each SDK:

API
Approve example:

```json
{
  "request": {
    "author": {
      "id": "828b554c-c9c9-11eb-a79b-dcfb48cfb3cb",
      "domainId": "455ad43e-cdd9-11eb-8465-dcfb48cfb3cb"
    },
    "targetDomainId": "455ad43e-cdd9-11eb-8465-dcfb48cfb3cb",
    "intentId": "846b5c3c-27e3-4146-ab18-ce6732624d35",
    "proposalSignature": "<PROPOSAL_SIGNATURE>",
    "approvalReason": "Approved after review",
    "type": "Approve"
  },
  "signature": "<APPROVAL_SIGNATURE>"
}
```

```sh
curl -X POST "${CUSTODY_API_URL}/v1/intents/approve" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d @signed-approval.json
```

For rejection, use `type: "Reject"` and include `rejectReason`.

TypeScript
```typescript
const approval = await client.governance.intents.approve(intentId, {
  comment: 'Approved after review',
});
await approval.waitAccepted(30_000);

// To reject instead:
const rejection = await client.governance.intents.deny(intentId, {
  comment: 'Destination not on the approved list',
});
await rejection.waitAccepted(30_000);
```

Java
```java
CoreTrustedIntent intent = client.governance().intents().get(domainId, intentId);
String proposalSignature = intent.getData().getDetails().getProposalSignature();

client.governance().intents().approve(domainId, intentId, proposalSignature, "Approved after review");

// To reject instead:
// client.governance().intents().reject(
//         domainId, intentId, proposalSignature, "Destination not on the approved list");
```

Python
```python
approval = client.governance.intents.approve(intent_id, comment="Approved after review")
approval.wait_accepted(timeout=30)

# To reject instead:
rejection = client.governance.intents.deny(
    intent_id, comment="Destination not on the approved list"
)
rejection.wait_accepted(timeout=30)
```

For a complete program, see example `20` (governance intents). To get the examples, see [Examples](/pt-br/products/custody/developer-experience/sdks#examples).

## Check state

Intent workflows have several state checks:

### Check state in the UI

| Check | UI procedure |
|  --- | --- |
| Request state | Use request lists and status messages in the UI. |
| Intent state | Navigate to **Intents** and review the intent status and workflow details. |
| Remaining approvers | Review the approval workflow in the UI. |
| Entity state | Open the created or updated entity in the relevant UI area. |
| Transaction state | View transaction, transfer, and order tabs for the account. |


### Check state with the API

The following examples check each state with the API and with each SDK:

API
| Check | API procedure |
|  --- | --- |
| Request state | Call [Get request state](/pt-br/products/custody/reference/api/openapi/requests/getrequeststate) with the `requestId`. |
| Intent state | Call [Get intent](/pt-br/products/custody/reference/api/openapi/intents/getintent) with the intent ID. |
| Remaining approvers | Call [Get remaining users](/pt-br/products/custody/reference/api/openapi/intents/getremainingusers). |
| Entity state | Call the relevant entity API, such as [Get current user](/pt-br/products/custody/reference/api/openapi/users/getme), [Get domain details](/pt-br/products/custody/reference/api/openapi/domains/getdomain), or [Get account details](/pt-br/products/custody/reference/api/openapi/accounts/getaccount). |
| Transaction state | Use [View transactions with the API](/pt-br/products/custody/transactions/viewing-assets/view-and-audit-api). |


TypeScript
```typescript
// Request state
const requestState = await client.requests.getState(pending.requestId);
console.log(requestState.status);

// Intent state
const intent = await client.governance.intents.get(intentId);
console.log(intent.state?.status);

// Remaining approvers, grouped by domain
const remaining = await client.governance.intents.getRemainingUsers(intentId);
```

Java
```java
// Request state
CoreRequestState requestState =
        client.governance().requests().get(domainId, pending.getRequestId());
System.out.println(requestState.getStatus());

// Intent state
CoreTrustedIntent intent = client.governance().intents().get(domainId, intentId);
System.out.println(intent.getData().getState().getStatus());

// Remaining approvers, grouped by domain
List<CoreRemainingDomainUsers> remaining =
        client.governance().intents().getRemainingUsers(domainId, intentId);
```

Python
```python
# Request state
request_state = client.events.request_state(pending.request_id)
print(request_state.status)

# Intent state
intent = client.governance.intents.get(intent_id)
print(intent.state.status)

# Remaining approvers, grouped by domain
remaining = client.governance.intents.get_remaining_users(intent_id)
```

Any authorized rejection stops the workflow. Expired intents do not execute and must be recreated if the change is still needed.

For service-authored records, [List intents](/pt-br/products/custody/reference/api/openapi/intents/getintents) supports filtering with `details.author.subject`.

## Review checklist

Before approving an intent, check:

- Target domain.
- Intent type.
- Payload.
- Selected policy.
- Required workflow.
- Remaining approvers.
- Expiry time.
- Compliance or incident notes.


For payload fields, see [Intent reference](/pt-br/products/custody/governance/intents/reference).