curl --request POST \
--url https://api.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"items": [
{
"external_id": "44895",
"amount_paid": 1250,
"balance_due": 0,
"payment_status": "paid"
},
{
"po_id": "6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b",
"amount_paid": 500,
"balance_due": 250,
"payment_status": "partial"
}
]
}
'import requests
url = "https://api.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance"
payload = { "items": [
{
"external_id": "44895",
"amount_paid": 1250,
"balance_due": 0,
"payment_status": "paid"
},
{
"po_id": "6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b",
"amount_paid": 500,
"balance_due": 250,
"payment_status": "partial"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
items: [
{
external_id: '44895',
amount_paid: 1250,
balance_due: 0,
payment_status: 'paid'
},
{
po_id: '6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b',
amount_paid: 500,
balance_due: 250,
payment_status: 'partial'
}
]
})
};
fetch('https://api.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance', 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.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance",
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([
'items' => [
[
'external_id' => '44895',
'amount_paid' => 1250,
'balance_due' => 0,
'payment_status' => 'paid'
],
[
'po_id' => '6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b',
'amount_paid' => 500,
'balance_due' => 250,
'payment_status' => 'partial'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance"
payload := strings.NewReader("{\n \"items\": [\n {\n \"external_id\": \"44895\",\n \"amount_paid\": 1250,\n \"balance_due\": 0,\n \"payment_status\": \"paid\"\n },\n {\n \"po_id\": \"6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b\",\n \"amount_paid\": 500,\n \"balance_due\": 250,\n \"payment_status\": \"partial\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"items\": [\n {\n \"external_id\": \"44895\",\n \"amount_paid\": 1250,\n \"balance_due\": 0,\n \"payment_status\": \"paid\"\n },\n {\n \"po_id\": \"6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b\",\n \"amount_paid\": 500,\n \"balance_due\": 250,\n \"payment_status\": \"partial\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"items\": [\n {\n \"external_id\": \"44895\",\n \"amount_paid\": 1250,\n \"balance_due\": 0,\n \"payment_status\": \"paid\"\n },\n {\n \"po_id\": \"6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b\",\n \"amount_paid\": 500,\n \"balance_due\": 250,\n \"payment_status\": \"partial\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "migration_po_historical_balance_result",
"total": 123,
"updated": 123,
"already_correct": 123,
"not_found": 123,
"failed": 123,
"results": [
{}
]
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}Set recovered historical payment balance on migrated purchase orders
Order-side sibling of vendor-bills/set-historical-balance. On a Versa
migration a settled purchase order’s payment state is not always carried
verbatim into the imported orders row, so a migrated historical PO can
land showing balance_due = NULL or payment_status = unpaid even when
the underlying bill was fully paid. The loader’s 6.4c post-pass derives
the recovered amount_paid / balance_due / payment_status per PO and
POSTs the resolved values here so the procurement view ties to the settled
truth.
GL-NEUTRAL. A purchase order posts no journal entry of its own (the vendor bill / GRNI does). This endpoint sets the three order-level payment columns only and never posts or touches a journal entry.
Lookup key. Each item resolves the migrated PO by po_id
(orders.id, preferred) OR external_id (orders.external_id, the loader’s
String(versa order_id) fallback).
Strict provenance. Only rows with document_type = 'purchase_order'
AND external_source = 'versa_cloud' AND is_historical_import = true
are touched. No live operator PO is ever modified.
Clamp. amount_paid is clamped to order_total;
balance_due = max(0, order_total - amount_paid); payment_status is
recomputed (paid / partial / unpaid) from the clamped numbers.
Idempotent. An already-at-target PO (within a cent + same status)
returns set=false (no-op). Re-running the whole pass is safe.
Scope: migration:write. Max 500 items per call.
curl --request POST \
--url https://api.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"items": [
{
"external_id": "44895",
"amount_paid": 1250,
"balance_due": 0,
"payment_status": "paid"
},
{
"po_id": "6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b",
"amount_paid": 500,
"balance_due": 250,
"payment_status": "partial"
}
]
}
'import requests
url = "https://api.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance"
payload = { "items": [
{
"external_id": "44895",
"amount_paid": 1250,
"balance_due": 0,
"payment_status": "paid"
},
{
"po_id": "6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b",
"amount_paid": 500,
"balance_due": 250,
"payment_status": "partial"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
items: [
{
external_id: '44895',
amount_paid: 1250,
balance_due: 0,
payment_status: 'paid'
},
{
po_id: '6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b',
amount_paid: 500,
balance_due: 250,
payment_status: 'partial'
}
]
})
};
fetch('https://api.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance', 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.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance",
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([
'items' => [
[
'external_id' => '44895',
'amount_paid' => 1250,
'balance_due' => 0,
'payment_status' => 'paid'
],
[
'po_id' => '6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b',
'amount_paid' => 500,
'balance_due' => 250,
'payment_status' => 'partial'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance"
payload := strings.NewReader("{\n \"items\": [\n {\n \"external_id\": \"44895\",\n \"amount_paid\": 1250,\n \"balance_due\": 0,\n \"payment_status\": \"paid\"\n },\n {\n \"po_id\": \"6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b\",\n \"amount_paid\": 500,\n \"balance_due\": 250,\n \"payment_status\": \"partial\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"items\": [\n {\n \"external_id\": \"44895\",\n \"amount_paid\": 1250,\n \"balance_due\": 0,\n \"payment_status\": \"paid\"\n },\n {\n \"po_id\": \"6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b\",\n \"amount_paid\": 500,\n \"balance_due\": 250,\n \"payment_status\": \"partial\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.arcuserp.com/v1/entities/{entity_id}/migration/purchase-orders/set-historical-balance")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"items\": [\n {\n \"external_id\": \"44895\",\n \"amount_paid\": 1250,\n \"balance_due\": 0,\n \"payment_status\": \"paid\"\n },\n {\n \"po_id\": \"6b1e9a4f-1d2c-4f8e-9c8a-8b1d2e3f4a5b\",\n \"amount_paid\": 500,\n \"balance_due\": 250,\n \"payment_status\": \"partial\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "migration_po_historical_balance_result",
"total": 123,
"updated": 123,
"already_correct": 123,
"not_found": 123,
"failed": 123,
"results": [
{}
]
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}Authorizations
API key issued per entity via Settings > Developers > API Keys.
Each key carries scopes (e.g. orders:read, products:write).
Bearer token format: Authorization: Bearer ark_live_ent_Test keys use ark_test_ent_. Both are issued per entity
via Settings > Developers > API Keys.
Headers
Optional idempotency key. If supplied, retrying the same call within 24 hours returns the previous result.
Path Parameters
Body
500Show child attributes
Show child attributes
Was this page helpful?

