Upload a Snapshot Chunk
curl --request PUT \
--url https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"rows": [
{}
]
}
'import requests
url = "https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}"
payload = { "rows": [{}] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({rows: [{}]})
};
fetch('https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}', 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://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'rows' => [
[
]
]
]),
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://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}"
payload := strings.NewReader("{\n \"rows\": [\n {}\n ]\n}")
req, _ := http.NewRequest("PUT", 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.put("https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"rows\": [\n {}\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"rows\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_bodyCampaigns API
Upload a Snapshot Chunk
Upload the next ordered chunk of a staged snapshot
PUT
/
external-api
/
v1
/
campaigns
/
{apiReference}
/
snapshots
/
{snapshotReference}
/
chunks
/
{chunkIndex}
Upload a Snapshot Chunk
curl --request PUT \
--url https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"rows": [
{}
]
}
'import requests
url = "https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}"
payload = { "rows": [{}] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({rows: [{}]})
};
fetch('https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}', 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://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'rows' => [
[
]
]
]),
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://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}"
payload := strings.NewReader("{\n \"rows\": [\n {}\n ]\n}")
req, _ := http.NewRequest("PUT", 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.put("https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"rows\": [\n {}\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://getbill.io/external-api/v1/campaigns/{apiReference}/snapshots/{snapshotReference}/chunks/{chunkIndex}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"rows\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_bodyChunk rows may contain new references or update known debts. A new valid reference with a positive remaining balance will create a debt attached to the campaign and its collection timeline. Uploading a chunk does not modify any debt yet: creations, updates, and omission reconciliation are applied after finalization and asynchronous processing of the complete snapshot.
Chunk rows use the exact same
GetBill stores a SHA-256 of the exact request-body bytes. An identical retry of an accepted index returns
Authentication and limits
Requiresdebts:write. Uses the write limiter: 500 requests/hour per company. Each body is limited to 10 MiB and each rows array to 20,000 rows.
string
required
Your campaign reference.
string
required
The same client-created snapshot reference used in the start request.
integer
required
Zero-based index. Send chunks strictly in order.
array
required
Rows following the snapshot row contract. Choose either invoice balances or account balances for the same receivables stream; see balance granularity.
rows[].payment_plan contract as a one-request snapshot. See Send an external payment plan for activation, payment, cancellation, and idempotency rules.
curl -X PUT "https://getbill.io/external-api/v1/campaigns/client-a-2026/snapshots/export-2026-07-17-550e8400/chunks/0" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
--data-binary '{
"rows": [
{
"internal_id": "account-284",
"remaining_amount": 249.9,
"currency": "EUR",
"firstname": "Amina",
"lastname": "Martin",
"email": "amina@example.com",
"payment_plan": {
"external_reference": "plan-789",
"status": "active",
"installments": [
{"external_reference": "installment-1", "amount": 100, "payment_date": "2026-09-01", "status": "pending"},
{"external_reference": "installment-2", "amount": 149.9, "payment_date": "2026-10-01", "status": "pending"}
]
}
}
]
}'
HTTP/1.1 201 Created
Location: /external-api/v1/campaigns/client-a-2026/snapshots/export-2026-07-17-550e8400
{
"error": false,
"message": "Snapshot chunk accepted",
"data": {
"api_reference": "client-a-2026",
"snapshot_reference": "export-2026-07-17-550e8400",
"delivery_mode": "staged",
"status": "uploading",
"core_applied": false,
"expected_row_count": 2500,
"received_row_count": 1,
"received_chunk_count": 1,
"next_chunk_index": 1,
"counts": null,
"failure": null,
"created_at": "2026-07-17T09:00:00Z",
"updated_at": "2026-07-17T09:01:00Z",
"chunk_index": 0,
"status_url": "/external-api/v1/campaigns/client-a-2026/snapshots/export-2026-07-17-550e8400"
}
}
200; changed JSON bytes return 409 chunk_content_conflict. An index greater than next_chunk_index returns 409 chunk_out_of_order.
Rows across all chunks may not exceed the declared expected_row_count. Final validation also rejects duplicate effective identities or invoice references across chunks.
A costly phone destination is validated asynchronously during processing after finalization. GetBill controls it per campaign, disabled by default; the partner cannot change it in a chunk, and the legacy accept_expensive_destination key is tolerated and ignored regardless of its value or JSON type. When the campaign does not allow it, its row remains part of the snapshot and completed status records a non-blocking warning in warnings, with warning_count and warnings_omitted; see Get Snapshot Status.