Execute a batch of admin API requests
Batches multiple admin API calls into a single HTTP request. Each sub-request in requests is dispatched internally to its normal controller/action and is authenticated and authorized against its own target endpoint, exactly as if it had been called directly — a caller can never reach an endpoint through a batch that they could not reach on their own.
Scope lock: every sub-request in one batch must resolve to the same endpoint (same HTTP method and the same target action, resolved through the real URL mappings). For example, fetching organizations A, B and C in one call is allowed (GET /organizations/{organizationId} repeated for each), but mixing a request to GET /organizations/{organizationId} with one to PUT /organizations/{organizationId}/policies/documents/{documentId} in the same batch is rejected with 400.
Sub-request paths must start with /v1/admin/ (the /api context prefix is optional and is stripped if present). Nesting a bulk call inside another bulk call is rejected.
Sub-requests run sequentially and each commits independently in its own database session — there is no all-or-nothing behavior across the batch, so a failing sub-request does not roll back or block the others (partial success). The overall response is 200 whenever the batch envelope itself is well-formed, even if individual sub-requests failed; check each entry’s own status for its outcome.
Binary or streamed endpoints (images, PDFs, content downloads) are not supported inside a batch and return a per-entry 415 — call them directly instead.
curl --request POST \
--url https://stage-api.findity.com/api/v1/admin/bulk \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"requests": [
{
"id": "0",
"method": "GET",
"path": "/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92"
},
{
"id": "1",
"method": "GET",
"path": "/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
}
]
}
'import requests
url = "https://stage-api.findity.com/api/v1/admin/bulk"
payload = { "requests": [
{
"id": "0",
"method": "GET",
"path": "/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92"
},
{
"id": "1",
"method": "GET",
"path": "/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
}
] }
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({
requests: [
{
id: '0',
method: 'GET',
path: '/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92'
},
{
id: '1',
method: 'GET',
path: '/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4'
}
]
})
};
fetch('https://stage-api.findity.com/api/v1/admin/bulk', 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://stage-api.findity.com/api/v1/admin/bulk",
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([
'requests' => [
[
'id' => '0',
'method' => 'GET',
'path' => '/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92'
],
[
'id' => '1',
'method' => 'GET',
'path' => '/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4'
]
]
]),
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://stage-api.findity.com/api/v1/admin/bulk"
payload := strings.NewReader("{\n \"requests\": [\n {\n \"id\": \"0\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92\"\n },\n {\n \"id\": \"1\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4\"\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://stage-api.findity.com/api/v1/admin/bulk")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"requests\": [\n {\n \"id\": \"0\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92\"\n },\n {\n \"id\": \"1\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://stage-api.findity.com/api/v1/admin/bulk")
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 \"requests\": [\n {\n \"id\": \"0\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92\"\n },\n {\n \"id\": \"1\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"responses": [
{
"id": "0",
"index": 0,
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"id": "f328e47d595c40ad80f27f52626c5b92",
"name": "Acme AB"
}
},
{
"id": "1",
"index": 1,
"status": 404,
"body": {
"result": "error",
"code": "ORGANIZATION_NOT_FOUND",
"description": "Organization not found"
}
}
]
}{
"result": "error",
"code": "INVALID_PARAMETER",
"description": "Bulk 'requests' array must not be empty"
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Sub-requests to dispatch, in order. Sub-requests run sequentially and each commits independently — there is no all-or-nothing behavior across the batch, so a failing sub-request does not roll back or block the others.
1 - 50 elementsHide child attributes
Hide child attributes
HTTP method to dispatch this sub-request with.
GET, POST, PUT, DELETE API path to dispatch to. Must start with the same prefix as the bulk endpoint being called (/v1/admin/ for the admin bulk endpoint, /v1/expense/ for the expense bulk endpoint). The leading /api context prefix is optional and is stripped if present. Percent-encoding, path traversal (..), query strings (?), and nested /bulk paths are rejected. All sub-requests within one batch must resolve to the same underlying endpoint (same HTTP method and the same target action) — see the endpoint description for the scope-lock rule.
"/v1/expense/expenses/f328e47d595c40ad80f27f52626c5b92"
Optional client-supplied identifier for this sub-request. Echoed back verbatim on the matching entry in the response's responses array so callers can correlate results without relying on array order. Not required — every response entry also carries the sub-request's zero-based index.
100"0"
Query/form parameters to send with the sub-request, as key-value pairs. Each value must be a string, number, or boolean, or an array of those — nested objects are rejected. Authentication-bearing parameters (e.g. authenticationToken) are silently stripped; the sub-request always runs as the caller who made the outer bulk call.
JSON body to send with the sub-request, matching the shape the target endpoint normally expects. Ignored when method is GET. Limited to 1 MB per sub-request.
Response
The batch envelope was well-formed. Individual sub-requests may still have failed or been unauthorized — check each entry's own status.
One entry per sub-request in the original requests array, in the same order.
Hide child attributes
Hide child attributes
Echoes the id supplied on the matching sub-request, or its zero-based array index (as a string) when no id was supplied.
Zero-based position of this entry's sub-request in the original requests array.
HTTP status code returned by the target endpoint for this sub-request (for example 200, 401, 404). 415 means the target endpoint returns binary/streamed content, which is not supported inside a batch — call it directly instead. 500 means the sub-request failed to dispatch.
JSON body returned by the target endpoint for this sub-request, unchanged. null when the target endpoint returned no body. A response larger than the capture limit (10 MB by default) is replaced with {"omitted": true, "reason": "response too large", "contentType": "...", "contentLength": 0}.
curl --request POST \
--url https://stage-api.findity.com/api/v1/admin/bulk \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"requests": [
{
"id": "0",
"method": "GET",
"path": "/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92"
},
{
"id": "1",
"method": "GET",
"path": "/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
}
]
}
'import requests
url = "https://stage-api.findity.com/api/v1/admin/bulk"
payload = { "requests": [
{
"id": "0",
"method": "GET",
"path": "/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92"
},
{
"id": "1",
"method": "GET",
"path": "/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
}
] }
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({
requests: [
{
id: '0',
method: 'GET',
path: '/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92'
},
{
id: '1',
method: 'GET',
path: '/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4'
}
]
})
};
fetch('https://stage-api.findity.com/api/v1/admin/bulk', 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://stage-api.findity.com/api/v1/admin/bulk",
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([
'requests' => [
[
'id' => '0',
'method' => 'GET',
'path' => '/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92'
],
[
'id' => '1',
'method' => 'GET',
'path' => '/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4'
]
]
]),
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://stage-api.findity.com/api/v1/admin/bulk"
payload := strings.NewReader("{\n \"requests\": [\n {\n \"id\": \"0\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92\"\n },\n {\n \"id\": \"1\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4\"\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://stage-api.findity.com/api/v1/admin/bulk")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"requests\": [\n {\n \"id\": \"0\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92\"\n },\n {\n \"id\": \"1\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://stage-api.findity.com/api/v1/admin/bulk")
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 \"requests\": [\n {\n \"id\": \"0\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/f328e47d595c40ad80f27f52626c5b92\"\n },\n {\n \"id\": \"1\",\n \"method\": \"GET\",\n \"path\": \"/v1/admin/organizations/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"responses": [
{
"id": "0",
"index": 0,
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"id": "f328e47d595c40ad80f27f52626c5b92",
"name": "Acme AB"
}
},
{
"id": "1",
"index": 1,
"status": 404,
"body": {
"result": "error",
"code": "ORGANIZATION_NOT_FOUND",
"description": "Organization not found"
}
}
]
}{
"result": "error",
"code": "INVALID_PARAMETER",
"description": "Bulk 'requests' array must not be empty"
}
