Submit Agreement Actions
curl --request POST \
--url https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'X-API-Key: <api-key>' \
--data '
{
"agreement_actions": [
{
"agreement_id": "<string>",
"action": "<string>"
}
],
"signer_id": "<string>"
}
'import requests
url = "https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions"
payload = {
"agreement_actions": [
{
"agreement_id": "<string>",
"action": "<string>"
}
],
"signer_id": "<string>"
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
'X-API-Key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
agreement_actions: [{agreement_id: '<string>', action: '<string>'}],
signer_id: '<string>'
})
};
fetch('https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'agreement_actions' => [
[
'agreement_id' => '<string>',
'action' => '<string>'
]
],
'signer_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions"
payload := strings.NewReader("{\n \"agreement_actions\": [\n {\n \"agreement_id\": \"<string>\",\n \"action\": \"<string>\"\n }\n ],\n \"signer_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions")
.header("Idempotency-Key", "<idempotency-key>")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"agreement_actions\": [\n {\n \"agreement_id\": \"<string>\",\n \"action\": \"<string>\"\n }\n ],\n \"signer_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"agreement_actions\": [\n {\n \"agreement_id\": \"<string>\",\n \"action\": \"<string>\"\n }\n ],\n \"signer_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"results": [
{
"agreement_id": "<string>",
"status": "<string>",
"responded_at": "<string>",
"error": "<string>"
}
]
}Virtual Account Groups
Submit Agreement Actions
Accept or reject one or more of a virtual account group’s agreements in a single batch
POST
/
v1
/
accounts
/
{account_id}
/
virtual-account-groups
/
{group_id}
/
agreements
/
actions
Submit Agreement Actions
curl --request POST \
--url https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'X-API-Key: <api-key>' \
--data '
{
"agreement_actions": [
{
"agreement_id": "<string>",
"action": "<string>"
}
],
"signer_id": "<string>"
}
'import requests
url = "https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions"
payload = {
"agreement_actions": [
{
"agreement_id": "<string>",
"action": "<string>"
}
],
"signer_id": "<string>"
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
'X-API-Key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
agreement_actions: [{agreement_id: '<string>', action: '<string>'}],
signer_id: '<string>'
})
};
fetch('https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'agreement_actions' => [
[
'agreement_id' => '<string>',
'action' => '<string>'
]
],
'signer_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions"
payload := strings.NewReader("{\n \"agreement_actions\": [\n {\n \"agreement_id\": \"<string>\",\n \"action\": \"<string>\"\n }\n ],\n \"signer_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions")
.header("Idempotency-Key", "<idempotency-key>")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"agreement_actions\": [\n {\n \"agreement_id\": \"<string>\",\n \"action\": \"<string>\"\n }\n ],\n \"signer_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopnow.io/v1/accounts/{account_id}/virtual-account-groups/{group_id}/agreements/actions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"agreement_actions\": [\n {\n \"agreement_id\": \"<string>\",\n \"action\": \"<string>\"\n }\n ],\n \"signer_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"results": [
{
"agreement_id": "<string>",
"status": "<string>",
"responded_at": "<string>",
"error": "<string>"
}
]
}This endpoint works on any account you own. Create Virtual Account Group covers who can open a group and what the account needs first.
Records the account holder’s decision on the agreements listed by List Agreements. Onboarding completes when every agreement is accepted and every document requirement is satisfied.
The batch is validated as a whole before anything is sent to the provider: if any agreement in it is not
pending, has expired, or is being rejected while declinable is false, the request fails with 409 and nothing is applied.
A 200 does not mean every action was accepted. Hop forwards the batch to the provider and returns one entry per requested action in results. An entry with a non-null error failed and its agreement is unchanged, still pending, while the other entries in the same response may have succeeded. Always inspect every entry; never treat the status code alone as success.
After the call, Hop resumes the group’s onboarding in the background, so the group’s requirements and onboarding status update shortly afterwards. Poll Get Virtual Account Group for the new state.
Requires an API key with write access on the virtual_accounts scope.
Path Parameters
string
required
The account’s external ID (starts with
acct_)string
required
The virtual account group’s external ID (starts with
vag_). It must belong to the account in the path.Headers
string
required
Canonical lowercase 8-4-4-4-12 UUID. Scoped to the group, with the payload fingerprinted: replaying the same key with the same payload returns the recorded outcome, and with a different payload returns
409. See Idempotency.Request Body
array
required
The decisions to submit, 1 to 50 entries. Each
agreement_id must appear at most once and must belong to the group in the path.Show Action Fields
Show Action Fields
string
required
Agreement to act on (starts with
agr_), from List Agreementsstring
required
accept or reject. reject is only permitted when the agreement’s declinable is true.string
The authorised associated person signing on behalf of a business account (starts with
aap_, must belong to the account and carry is_signer: true). Required for a business account and rejected for an individual one, which has no associated persons and signs as itself. See Create Associated Person.Response
Returns200 with one entry per requested action, in the same order as agreement_actions.
array
Per-action outcomes
Show Result Fields
Show Result Fields
string
The agreement this entry is about (starts with
agr_)string
The agreement’s new status,
accepted or rejected, on a successful entry. null when error is set.string
ISO 8601 timestamp when the decision was recorded, on a successful entry.
null when error is set.string
Why this action failed, as reported by the provider, or
null on success. A non-null value is the only signal that this entry failed: its agreement stays pending and the decision was not recorded.Retrying a partial failure
Retry by sending the same batch again with the sameIdempotency-Key. Entries already recorded under that key are exempt from the pending check, and the batch reaches the provider under the same key, so only the entries that failed are acted on again.
A fresh key on the same batch is rejected with 409, because the agreements that succeeded the first time are no longer pending. Use a fresh key only for a batch of agreements you have not acted on yet.
Errors
| Status | code | When |
|---|---|---|
404 | RESOURCE_NOT_FOUND | group_id, an agreement_id or signer_id is unknown or belongs to another account or group |
409 | BUSINESS_RULE_VIOLATION | An agreement in the batch is not pending (details carry agreement_id and status), has passed its expires_at, or is being rejected while declinable is false; or the Idempotency-Key was already used with a different payload. Nothing in the batch is applied. |
422 | BUSINESS_RULE_VIOLATION | signer_id was omitted for a business account or sent for an individual account, the person is not marked as a signer, or the batch contains the same agreement_id twice |
422 | VALIDATION_ERROR | Idempotency-Key is missing or not a canonical 8-4-4-4-12 UUID, agreement_actions is empty or over 50 entries, or action is not accept or reject |
502 | GATEWAY_ERROR | The provider call failed outright, so no decision was recorded. Retry with the same key. A provider failure on a single agreement is reported in that entry’s error with 200 instead. |
Request Example
curl -X POST "https://api.hopnow.io/v1/accounts/acct_ka44qsvpo8q3wtzuwfqf0h6u/virtual-account-groups/vag_7m2k9x4c1b6v3n8q5z0j2p7t/agreements/actions" \
-H "X-API-Key: your_api_key" \
-H "X-Signature: hmac_signature" \
-H "X-Timestamp: 1234567890" \
-H "X-Nonce: abc123" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{
"signer_id": "aap_5r8t1v4x6z0b3n5q7w2e9y4u",
"agreement_actions": [
{"agreement_id": "agr_3f8k1m5p9r2t6v4x7z0b3n5q", "action": "accept"},
{"agreement_id": "agr_g738im3ueie9s9zexmf9cl87", "action": "accept"}
]
}'
Response Example
{
"results": [
{
"agreement_id": "agr_3f8k1m5p9r2t6v4x7z0b3n5q",
"status": "accepted",
"responded_at": "2026-01-16T09:12:00Z",
"error": null
},
{
"agreement_id": "agr_g738im3ueie9s9zexmf9cl87",
"status": null,
"responded_at": null,
"error": "Agreement is awaiting a new version and cannot be signed"
}
]
}