Statuses and decisions in the API
Your compliance team works with policies, requirements and statuses in the dashboard, as Transfers, Policies, Requirements and Statuses explain. This page is the same ground for your backend: where it reads each of them on a transfer, and the calls that decide one.
Read a transfer
| On the transfer | In the API |
|---|---|
| Your verdict | Your audit trail, as transaction.compliance.evaluated with its decision. |
| Your status | The status of your entry in agents, the one with your DID and the role VASP. Other entries, such as the settlement address, do not count towards any status. |
| Requirements | Under policies, in the entry of the institution that set them: type, status, and from_did, the institution that must meet it. |
| Transaction status | The transfer’s status. |
| Something waits on you | requires_action is true. |
| What your backend does next | next_action. |
| On the transfer | In the API |
|---|---|
| Your verdict | Your audit trail, as transaction.compliance.evaluated with its decision. |
| Your status | The status of your entry in agents, the one with your DID and the role VASP. Other entries, such as the settlement address, do not count towards any status. |
| Requirements | Under policies, in the entry of the institution that set them: type, status, and from_did, the institution that must meet it. |
| Transaction status | The transfer’s status. |
| Something waits on you | requires_action is true. |
| What your backend does next | next_action. |
The same requirement names its two institutions differently in a transfer and in an event:
| Where | The institution that set it | The institution that must meet it |
|---|---|---|
| A transfer you read | The entry in agents the requirement sits under | from_did |
The requirements.pending.v3 event | from | agentId |
| Where | The institution that set it | The institution that must meet it |
|---|---|---|
| A transfer you read | The entry in agents the requirement sits under | from_did |
The requirements.pending.v3 event | from | agentId |
curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80 \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80', { method: 'GET', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', }, }); const answer = response.status === 204 ? null : await response.json();import requests response = requests.get( 'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transfer’s id
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Active",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5",
"requires_action": true,
"next_action": {
"type": "Review"
},
"agents": [
{
"agent_did": "did:web:originator.example:pt",
"role": "VASP",
"status": "Authorized",
"policies": [
{
"type": "WalletAddressConfirmation",
"status": "Satisfied",
"from_did": "did:web:beneficiary.example:pt"
}
]
},
{
"agent_did": "did:web:beneficiary.example:pt",
"role": "VASP",
"status": "Pending",
"policies": [
{
"type": "ReceiveTRInfo",
"status": "Pending",
"from_did": "did:web:originator.example:pt"
},
{
"type": "Authorization",
"status": "Satisfied",
"from_did": "did:web:originator.example:pt"
},
{
"type": "BeneficiaryNameMatch",
"status": "Pending",
"from_did": "did:web:beneficiary.example:pt"
}
]
}
]
}curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80 \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80', {
method: 'GET',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
},
});
const answer = response.status === 204 ? null : await response.json();import requests
response = requests.get(
'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
)
answer = None if response.status_code == 204 else response.json(){
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Active",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5",
"requires_action": true,
"next_action": {
"type": "Review"
},
"agents": [
{
"agent_did": "did:web:originator.example:pt",
"role": "VASP",
"status": "Authorized",
"policies": [
{
"type": "WalletAddressConfirmation",
"status": "Satisfied",
"from_did": "did:web:beneficiary.example:pt"
}
]
},
{
"agent_did": "did:web:beneficiary.example:pt",
"role": "VASP",
"status": "Pending",
"policies": [
{
"type": "ReceiveTRInfo",
"status": "Pending",
"from_did": "did:web:originator.example:pt"
},
{
"type": "Authorization",
"status": "Satisfied",
"from_did": "did:web:originator.example:pt"
},
{
"type": "BeneficiaryNameMatch",
"status": "Pending",
"from_did": "did:web:beneficiary.example:pt"
}
]
}
]
}Here the beneficiary’s institution reads a deposit that the originator’s institution has already authorized. Its own side is still Pending, because the Travel Rule data has not arrived and the name check is open.
What to do next
When you read a transfer, next_action says what your backend does next, and requires_action says whether a decision waits on a person on your team. Both are on GET /api/v1/transfers/{id}, not on the list of transfers.
| Type | What it means | What you do |
|---|---|---|
ProvideInformation | Your customer’s Travel Rule data is incomplete, or the counterparty asked for it. | Complete it with PATCH/, which takes the data as party.ivms, then send it with POST/. |
ConfirmRelationship | The counterparty asked you to confirm the settlement address. | Answer it as Requirements you resolve describes, or register the address as Wallets explains. |
Review | A person on your team decides. requires_action is true. | Nothing for your backend to do unless it makes the decision, with the calls under Decide a transfer. |
ExecuteSettlement | The transfer is ready to settle, and you send the funds. next_action.settlement holds the asset, the amount, the address and any memo_tag. | Send the funds, then record the transaction hash with POST/. |
| Type | What it means | What you do |
|---|---|---|
ProvideInformation | Your customer’s Travel Rule data is incomplete, or the counterparty asked for it. | Complete it with PATCH /api/v1/transfers/{id}/parties/{role}, which takes the data as party.ivms, then send it with POST /api/v1/transfers/{id}/send-presentation. |
ConfirmRelationship | The counterparty asked you to confirm the settlement address. | Answer it as Requirements you resolve describes, or register the address as Wallets explains. |
Review | A person on your team decides. requires_action is true. | Nothing for your backend to do unless it makes the decision, with the calls under Decide a transfer. |
ExecuteSettlement | The transfer is ready to settle, and you send the funds. next_action.settlement holds the asset, the amount, the address and any memo_tag. | Send the funds, then record the transaction hash with POST /api/v1/transfers/{id}/settle. |
next_action is null when nothing of yours is pending, as when the counterparty is holding the transfer. It is also null before your policies reach a verdict, once the transfer has concluded, and on a payment.
Requirements
Each requirement carries its name in type:
| Requirement | In the API |
|---|---|
| Send Travel Rule data | SendTRInfo |
| Transmit Travel Rule data | TransmitTRData |
| Receive Travel Rule data | ReceiveTRInfo |
| Submit Travel Rule data | SubmitTRInfo |
| Confirm beneficiary name match | BeneficiaryNameMatch |
| Complete internal compliance checks | InternalComplianceCheck |
| Require wallet ownership confirmation | WalletAddressConfirmation |
| Require wallet ownership confirmation, added by Notabene | WalletConfirmationForDisclosure |
| Require counterparty authorization | Authorization |
| Requirement | In the API |
|---|---|
| Send Travel Rule data | SendTRInfo |
| Transmit Travel Rule data | TransmitTRData |
| Receive Travel Rule data | ReceiveTRInfo |
| Submit Travel Rule data | SubmitTRInfo |
| Confirm beneficiary name match | BeneficiaryNameMatch |
| Complete internal compliance checks | InternalComplianceCheck |
| Require wallet ownership confirmation | WalletAddressConfirmation |
| Require wallet ownership confirmation, added by Notabene | WalletConfirmationForDisclosure |
| Require counterparty authorization | Authorization |
In the API, a requirement’s status reads Pending, Satisfied, Skipped, Rejected, NotApplicable for N/A, or Incomplete when it is partially satisfied.
Answer a requirement
Three requirements take your backend’s answer, all through the same call, as Requirements you resolve describes with each outcome.
| Requirement | In the path | Your answer |
|---|---|---|
| Confirm wallet ownership | wallet-address-confirmation | confirmed, or rejected when the address is not your customer’s |
| Confirm beneficiary name match | beneficiary-name-match | matched, partial_match (held for review) or mismatch |
| Complete internal compliance checks | internal-compliance-check | completed or rejected |
| Requirement | In the path | Your answer |
|---|---|---|
| Confirm wallet ownership | wallet-address-confirmation | confirmed, or rejected when the address is not your customer’s |
| Confirm beneficiary name match | beneficiary-name-match | matched, partial_match (held for review) or mismatch |
| Complete internal compliance checks | internal-compliance-check | completed or rejected |
Registering an address in advance, as Wallets explains, confirms it by itself. A mismatch rejects the transfer and tells the sender why with the reason code BE01, and partial_match exists only in the API. The call is in the API reference as Answer a requirement.
Submit Travel Rule data takes no answer, because adding the sender’s data meets it, as described in Deposits, funds first.
Decide a transfer
Three calls decide a transfer by hand, and they are easy to confuse:
| To | Call | When |
|---|---|---|
| Authorize a transfer your policies flagged, or with requirements open | POST/, with a reason | It overrides the flag, and every open requirement your institution set then reads Skipped. |
| Accept a transfer that asks for your authorization | POST/ | When the counterparty requires your authorization: an Authorization requirement whose from_did is your DID. |
| Reject a transfer | POST/ | In place of either of the two above. |
| To | Call | When |
|---|---|---|
| Authorize a transfer your policies flagged, or with requirements open | POST /api/v1/transfers/{id}/force-authorize, with a reason | It overrides the flag, and every open requirement your institution set then reads Skipped. |
| Accept a transfer that asks for your authorization | POST /api/v1/transfers/{id}/authorize | When the counterparty requires your authorization: an Authorization requirement whose from_did is your DID. |
| Reject a transfer | POST /api/v1/transfers/{id}/reject | In place of either of the two above. |
Requirements another institution set for you stay open after a force-authorize, and so does the hold on personal data. Your audit trail records each verdict as transaction.compliance.evaluated, whose details.decision is Authorize, Flag or Reject, and writes Approve as Authorize. Withdrawals shows these calls within a flow.
Read your policies
curl https://sandbox.notabene.id/api/v1/policy-configs \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'const response = await fetch('https://sandbox.notabene.id/api/v1/policy-configs', { method: 'GET', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', }, }); const answer = response.status === 204 ? null : await response.json();import requests response = requests.get( 'https://sandbox.notabene.id/api/v1/policy-configs', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, ) answer = None if response.status_code == 204 else response.json()
[
{
"policy_name": "AmountThreshold",
"kind": "Standard",
"type": "Transaction",
"is_enabled": true,
"config": {
"basis": "",
"approve_below": 0,
"flag_above": 10000,
"reject_above": 100000
}
},
{
"policy_name": "GeneralDefault",
"kind": "Standard",
"type": "Transaction",
"is_enabled": true,
"config": {
"verdict": "Flag"
}
}
]curl https://sandbox.notabene.id/api/v1/policy-configs \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'const response = await fetch('https://sandbox.notabene.id/api/v1/policy-configs', {
method: 'GET',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
},
});
const answer = response.status === 204 ? null : await response.json();import requests
response = requests.get(
'https://sandbox.notabene.id/api/v1/policy-configs',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
)
answer = None if response.status_code == 204 else response.json()[
{
"policy_name": "AmountThreshold",
"kind": "Standard",
"type": "Transaction",
"is_enabled": true,
"config": {
"basis": "",
"approve_below": 0,
"flag_above": 10000,
"reject_above": 100000
}
},
{
"policy_name": "GeneralDefault",
"kind": "Standard",
"type": "Transaction",
"is_enabled": true,
"config": {
"verdict": "Flag"
}
}
]Each policy carries its policy_name, whether it is_enabled, and its settings in config. Here a transfer above 10,000 is flagged, one above 100,000 is rejected, and one that no policy has an opinion on is flagged too.
Follow the status
Each time the transaction status changes, your webhook endpoint receives transfer.status_changed.v3 with the status before and after. When your policies hold the transfer for review, the transfer reads requires_action: true until your team decides. Webhooks describes the other events.
{
"eventType": "transfer.status_changed.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:example.com:pt",
"fromStatus": "ReadyToSettle",
"toStatus": "Settled",
"settlementId": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
}
{
"eventType": "transfer.status_changed.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:example.com:pt",
"fromStatus": "ReadyToSettle",
"toStatus": "Settled",
"settlementId": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
}| Transaction status | What your backend does |
|---|---|
Active | Nothing, unless something waits on you: then the transfer’s requires_action is true and you receive requirements.pending.v3. |
ReadyToSettle | As the sender, send the funds and record the transaction hash with POST/. |
Rejected, Canceled | Do not send the funds. |
Settled | As the beneficiary, credit your customer. |
SettledIncompleteAuthorization | Keep answering what is asked. A transfer you create from a deposit is here within a second of being created. |
| Transaction status | What your backend does |
|---|---|
Active | Nothing, unless something waits on you: then the transfer’s requires_action is true and you receive requirements.pending.v3. |
ReadyToSettle | As the sender, send the funds and record the transaction hash with POST /api/v1/transfers/{id}/settle. |
Rejected, Canceled | Do not send the funds. |
Settled | As the beneficiary, credit your customer. |
SettledIncompleteAuthorization | Keep answering what is asked. A transfer you create from a deposit is here within a second of being created. |
Revert a transfer
To return the funds of a settled transfer, request a revert with POST /api/v1/transfers/{id}/revert and a reason, which is shared with the counterparty. The revert is a new transfer linked to the original, and the funds go back to the original source_address or to the settlement_address you give. Statuses lists the revert statuses.
Updated 3 days ago
