curl --request POST \
--url https://prod.truv.com/v1/sandbox/documents/ \
--header 'Content-Type: application/json' \
--header 'X-Access-Client-Id: <api-key>' \
--header 'X-Access-Secret: <api-key>' \
--data '
{
"filename": "acme-w2-2025.pdf",
"content": "JVBERi0xLjQKJc...",
"documents": [
{
"document_type": "paystub",
"document_subtype": null,
"identity": {
"full_name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"ssn": "991-91-9991"
},
"company_name": "Acme Inc.",
"start_page": 1,
"end_page": 2
}
]
}
'import requests
url = "https://prod.truv.com/v1/sandbox/documents/"
payload = {
"filename": "acme-w2-2025.pdf",
"content": "JVBERi0xLjQKJc...",
"documents": [
{
"document_type": "paystub",
"document_subtype": None,
"identity": {
"full_name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"ssn": "991-91-9991"
},
"company_name": "Acme Inc.",
"start_page": 1,
"end_page": 2
}
]
}
headers = {
"X-Access-Client-Id": "<api-key>",
"X-Access-Secret": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Access-Client-Id': '<api-key>',
'X-Access-Secret': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
filename: 'acme-w2-2025.pdf',
content: 'JVBERi0xLjQKJc...',
documents: [
{
document_type: 'paystub',
document_subtype: null,
identity: {
full_name: 'John Doe',
first_name: 'John',
last_name: 'Doe',
ssn: '991-91-9991'
},
company_name: 'Acme Inc.',
start_page: 1,
end_page: 2
}
]
})
};
fetch('https://prod.truv.com/v1/sandbox/documents/', 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://prod.truv.com/v1/sandbox/documents/",
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([
'filename' => 'acme-w2-2025.pdf',
'content' => 'JVBERi0xLjQKJc...',
'documents' => [
[
'document_type' => 'paystub',
'document_subtype' => null,
'identity' => [
'full_name' => 'John Doe',
'first_name' => 'John',
'last_name' => 'Doe',
'ssn' => '991-91-9991'
],
'company_name' => 'Acme Inc.',
'start_page' => 1,
'end_page' => 2
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Access-Client-Id: <api-key>",
"X-Access-Secret: <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://prod.truv.com/v1/sandbox/documents/"
payload := strings.NewReader("{\n \"filename\": \"acme-w2-2025.pdf\",\n \"content\": \"JVBERi0xLjQKJc...\",\n \"documents\": [\n {\n \"document_type\": \"paystub\",\n \"document_subtype\": null,\n \"identity\": {\n \"full_name\": \"John Doe\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"ssn\": \"991-91-9991\"\n },\n \"company_name\": \"Acme Inc.\",\n \"start_page\": 1,\n \"end_page\": 2\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Access-Client-Id", "<api-key>")
req.Header.Add("X-Access-Secret", "<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://prod.truv.com/v1/sandbox/documents/")
.header("X-Access-Client-Id", "<api-key>")
.header("X-Access-Secret", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"acme-w2-2025.pdf\",\n \"content\": \"JVBERi0xLjQKJc...\",\n \"documents\": [\n {\n \"document_type\": \"paystub\",\n \"document_subtype\": null,\n \"identity\": {\n \"full_name\": \"John Doe\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"ssn\": \"991-91-9991\"\n },\n \"company_name\": \"Acme Inc.\",\n \"start_page\": 1,\n \"end_page\": 2\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://prod.truv.com/v1/sandbox/documents/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Access-Client-Id"] = '<api-key>'
request["X-Access-Secret"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"acme-w2-2025.pdf\",\n \"content\": \"JVBERi0xLjQKJc...\",\n \"documents\": [\n {\n \"document_type\": \"paystub\",\n \"document_subtype\": null,\n \"identity\": {\n \"full_name\": \"John Doe\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"ssn\": \"991-91-9991\"\n },\n \"company_name\": \"Acme Inc.\",\n \"start_page\": 1,\n \"end_page\": 2\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "3f1c8a90b7d2416e9a5c0e4f2b8d6a17",
"uploaded_file": {
"file_id": "3f1c8a90b7d2416e9a5c0e4f2b8d6a17",
"filename": "acme-w2-2025.pdf",
"mime_type": "application/pdf",
"size": 48219,
"md5": "9f2b1c0d4e6a8b3f5c7d9e1a2b4c6d8e",
"file": "https://s3.amazonaws.com/...",
"status": "successful",
"validations": {
"is_viable_size": true,
"is_supported_type": true,
"is_accessible": true,
"is_valid": true,
"is_readable": true,
"is_unique": true
},
"last_error": ""
},
"documents": [
{
"document_type": "paystub",
"document_subtype": null,
"identity": {
"full_name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"ssn": "991-91-9991"
},
"company_name": "Acme Inc.",
"start_page": 1,
"end_page": 2
}
],
"created_at": "2026-09-17T10:11:02.104882Z",
"updated_at": "2026-09-17T10:11:02.104901Z"
}{
"error": {
"code": "reserved_credential",
"message": "Username 'goodlogin' is reserved for Truv's documented sandbox logins."
}
}{
"error": {
"code": "authentication_failed",
"message": "No such token"
}
}{
"error": {
"code": "env_not_sandbox",
"message": "Sandbox configuration requires a sandbox key; this key is 'prod'."
}
}{
"error": {
"code": "throttled",
"message": "Request was throttled."
}
}Upload a sandbox document
Adds a file to your sandbox document library. A payroll scenario references the returned id as a paystub’s or W-2’s document_id; every W-2 requires one.
The documented body is the JSON form, carrying the bytes as base64 content. The same endpoint also accepts multipart/form-data with a file part and the same metadata, where documents is sent as a JSON-encoded string.
filename is unique per client, compared without regard to case, and is also the join key a document-upload scenario matches on.
curl --request POST \
--url https://prod.truv.com/v1/sandbox/documents/ \
--header 'Content-Type: application/json' \
--header 'X-Access-Client-Id: <api-key>' \
--header 'X-Access-Secret: <api-key>' \
--data '
{
"filename": "acme-w2-2025.pdf",
"content": "JVBERi0xLjQKJc...",
"documents": [
{
"document_type": "paystub",
"document_subtype": null,
"identity": {
"full_name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"ssn": "991-91-9991"
},
"company_name": "Acme Inc.",
"start_page": 1,
"end_page": 2
}
]
}
'import requests
url = "https://prod.truv.com/v1/sandbox/documents/"
payload = {
"filename": "acme-w2-2025.pdf",
"content": "JVBERi0xLjQKJc...",
"documents": [
{
"document_type": "paystub",
"document_subtype": None,
"identity": {
"full_name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"ssn": "991-91-9991"
},
"company_name": "Acme Inc.",
"start_page": 1,
"end_page": 2
}
]
}
headers = {
"X-Access-Client-Id": "<api-key>",
"X-Access-Secret": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Access-Client-Id': '<api-key>',
'X-Access-Secret': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
filename: 'acme-w2-2025.pdf',
content: 'JVBERi0xLjQKJc...',
documents: [
{
document_type: 'paystub',
document_subtype: null,
identity: {
full_name: 'John Doe',
first_name: 'John',
last_name: 'Doe',
ssn: '991-91-9991'
},
company_name: 'Acme Inc.',
start_page: 1,
end_page: 2
}
]
})
};
fetch('https://prod.truv.com/v1/sandbox/documents/', 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://prod.truv.com/v1/sandbox/documents/",
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([
'filename' => 'acme-w2-2025.pdf',
'content' => 'JVBERi0xLjQKJc...',
'documents' => [
[
'document_type' => 'paystub',
'document_subtype' => null,
'identity' => [
'full_name' => 'John Doe',
'first_name' => 'John',
'last_name' => 'Doe',
'ssn' => '991-91-9991'
],
'company_name' => 'Acme Inc.',
'start_page' => 1,
'end_page' => 2
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Access-Client-Id: <api-key>",
"X-Access-Secret: <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://prod.truv.com/v1/sandbox/documents/"
payload := strings.NewReader("{\n \"filename\": \"acme-w2-2025.pdf\",\n \"content\": \"JVBERi0xLjQKJc...\",\n \"documents\": [\n {\n \"document_type\": \"paystub\",\n \"document_subtype\": null,\n \"identity\": {\n \"full_name\": \"John Doe\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"ssn\": \"991-91-9991\"\n },\n \"company_name\": \"Acme Inc.\",\n \"start_page\": 1,\n \"end_page\": 2\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Access-Client-Id", "<api-key>")
req.Header.Add("X-Access-Secret", "<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://prod.truv.com/v1/sandbox/documents/")
.header("X-Access-Client-Id", "<api-key>")
.header("X-Access-Secret", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"acme-w2-2025.pdf\",\n \"content\": \"JVBERi0xLjQKJc...\",\n \"documents\": [\n {\n \"document_type\": \"paystub\",\n \"document_subtype\": null,\n \"identity\": {\n \"full_name\": \"John Doe\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"ssn\": \"991-91-9991\"\n },\n \"company_name\": \"Acme Inc.\",\n \"start_page\": 1,\n \"end_page\": 2\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://prod.truv.com/v1/sandbox/documents/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Access-Client-Id"] = '<api-key>'
request["X-Access-Secret"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"acme-w2-2025.pdf\",\n \"content\": \"JVBERi0xLjQKJc...\",\n \"documents\": [\n {\n \"document_type\": \"paystub\",\n \"document_subtype\": null,\n \"identity\": {\n \"full_name\": \"John Doe\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"ssn\": \"991-91-9991\"\n },\n \"company_name\": \"Acme Inc.\",\n \"start_page\": 1,\n \"end_page\": 2\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "3f1c8a90b7d2416e9a5c0e4f2b8d6a17",
"uploaded_file": {
"file_id": "3f1c8a90b7d2416e9a5c0e4f2b8d6a17",
"filename": "acme-w2-2025.pdf",
"mime_type": "application/pdf",
"size": 48219,
"md5": "9f2b1c0d4e6a8b3f5c7d9e1a2b4c6d8e",
"file": "https://s3.amazonaws.com/...",
"status": "successful",
"validations": {
"is_viable_size": true,
"is_supported_type": true,
"is_accessible": true,
"is_valid": true,
"is_readable": true,
"is_unique": true
},
"last_error": ""
},
"documents": [
{
"document_type": "paystub",
"document_subtype": null,
"identity": {
"full_name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"ssn": "991-91-9991"
},
"company_name": "Acme Inc.",
"start_page": 1,
"end_page": 2
}
],
"created_at": "2026-09-17T10:11:02.104882Z",
"updated_at": "2026-09-17T10:11:02.104901Z"
}{
"error": {
"code": "reserved_credential",
"message": "Username 'goodlogin' is reserved for Truv's documented sandbox logins."
}
}{
"error": {
"code": "authentication_failed",
"message": "No such token"
}
}{
"error": {
"code": "env_not_sandbox",
"message": "Sandbox configuration requires a sandbox key; this key is 'prod'."
}
}{
"error": {
"code": "throttled",
"message": "Request was throttled."
}
}Authorizations
Client ID
Client Access Key
Body
A library upload carrying the bytes as base64 content. The same endpoint also accepts multipart/form-data with a file part and the same metadata, where documents is sent as a JSON-encoded string.
Name the entry is addressed by. Unique per client, compared without regard to case, and refused when it is one of Truv's own documented sandbox filenames.
255"acme-w2-2025.pdf"
The file bytes, base64-encoded.
"JVBERi0xLjQKJc..."
Test metadata describing the documents in the file. Omitting it declares one paystub for John Doe at Acme Inc., so a document-upload flow resolves the file without the entry having to describe it. A payroll scenario only needs the file itself, so it can be left out.
Show child attributes
Show child attributes
Response
One file in your sandbox document library, plus the test metadata describing it.
Unique identifier of the library entry. This is the value a scenario payload references as document_id.
64"3f1c8a90b7d2416e9a5c0e4f2b8d6a17"
The file half of a library entry.
Show child attributes
Show child attributes
Test metadata describing the documents in the file, used by document-upload scenarios.
Show child attributes
Show child attributes
Time when the entry was created.
"2026-09-17T10:11:02.104882Z"
Time when the entry was last updated.
"2026-09-17T10:11:02.104901Z"
Was this page helpful?