Upload your Documents
Uploads one supporting document to Wholechain using multipart/form-data. Document metadata is provided through X-Document-* headers. A successful upload returns a Document ID that can be attached to one or more events.
curl -X POST "https://connect.wholechain.com/Integration/Documents" \
-H "X-API-KEY: example_string" \
-H "X-Document-Title: Purchase Order 450001" \
-H "X-Document-Type: PurchaseOrder" \
-H "X-Document-Type-Name: John Doe" \
-H "X-Document-Id: PO-450001" \
-H "X-Document-Expiration-Date: 2027-08-17" \
-H "X-Document-Company: supplier_001" \
-H "X-Document-Location: warehouse_001" \
-H "X-Document-Product: product_001" \
-H "X-Document-Privacy: Private" \
--form file=example_string
import requests
import json
url = "https://connect.wholechain.com/Integration/Documents"
headers = {
"X-API-KEY": "example_string",
"X-Document-Title": "Purchase Order 450001",
"X-Document-Type": "PurchaseOrder",
"X-Document-Type-Name": "John Doe",
"X-Document-Id": "PO-450001",
"X-Document-Expiration-Date": "2027-08-17",
"X-Document-Company": "supplier_001",
"X-Document-Location": "warehouse_001",
"X-Document-Product": "product_001",
"X-Document-Privacy": "Private"
}
data = {
"file": "example_string"
}
response = requests.post(url, headers=headers, data=data)
print(response.json())
const formData = new FormData();
formData.append("file", "example_string");
const response = await fetch("https://connect.wholechain.com/Integration/Documents", {
method: "POST",
headers: {
"X-API-KEY": "example_string",
"X-Document-Title": "Purchase Order 450001",
"X-Document-Type": "PurchaseOrder",
"X-Document-Type-Name": "John Doe",
"X-Document-Id": "PO-450001",
"X-Document-Expiration-Date": "2027-08-17",
"X-Document-Company": "supplier_001",
"X-Document-Location": "warehouse_001",
"X-Document-Product": "product_001",
"X-Document-Privacy": "Private"
},
body: formData
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"mime/multipart"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.WriteField("file", "example_string")
writer.Close()
req, err := http.NewRequest("POST", "https://connect.wholechain.com/Integration/Documents", body)
if err != nil {
panic(err)
}
req.Header.Set("X-API-KEY", "example_string")
req.Header.Set("X-Document-Title", "Purchase Order 450001")
req.Header.Set("X-Document-Type", "PurchaseOrder")
req.Header.Set("X-Document-Type-Name", "John Doe")
req.Header.Set("X-Document-Id", "PO-450001")
req.Header.Set("X-Document-Expiration-Date", "2027-08-17")
req.Header.Set("X-Document-Company", "supplier_001")
req.Header.Set("X-Document-Location", "warehouse_001")
req.Header.Set("X-Document-Product", "product_001")
req.Header.Set("X-Document-Privacy", "Private")
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
require 'net/http'
require 'json'
uri = URI('https://connect.wholechain.com/Integration/Documents')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['X-API-KEY'] = 'example_string'
request['X-Document-Title'] = 'Purchase Order 450001'
request['X-Document-Type'] = 'PurchaseOrder'
request['X-Document-Type-Name'] = 'John Doe'
request['X-Document-Id'] = 'PO-450001'
request['X-Document-Expiration-Date'] = '2027-08-17'
request['X-Document-Company'] = 'supplier_001'
request['X-Document-Location'] = 'warehouse_001'
request['X-Document-Product'] = 'product_001'
request['X-Document-Privacy'] = 'Private'
request.set_form([
["file", "example_string"]
], 'multipart/form-data')
response = http.request(request)
puts response.body
{
"documents": [
{
"documentId": "PO-450001",
"title": "Purchase Order 450001",
"documentType": "PurchaseOrder",
"documentTypeName": null,
"fileName": "purchase-order-450001.pdf",
"fileType": "application/pdf",
"expirationDate": "2027-08-17",
"company": "supplier_001",
"location": "warehouse_001",
"product": "product_001",
"privacy": "Private",
"status": "Uploaded",
"error": null
}
]
}
{
"error": "Bad Request",
"message": "The request contains invalid parameters or malformed data",
"code": 400,
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.2",
"title": "The API key is not valid.",
"status": 401,
"traceId": "00-a5f70f30a335bf1f20afd4c79ec7c78a-d22743f3e6e0ffc0-00"
}
{
"error": "Error",
"message": "Uploaded file exceeds the configured file size limit. The response body is an HTML page from the web server, not JSON.",
"code": 413
}
{
"documents": [
{
"documentId": "PO-450001",
"title": "Purchase Order 450001",
"documentType": "PurchaseOrder",
"documentTypeName": null,
"fileName": "invoice.txt",
"fileType": "text/plain",
"expirationDate": null,
"company": null,
"location": null,
"product": null,
"privacy": "Private",
"status": "Failed",
"error": "The file type is not supported. Allowed types are pdf, xlsx, xls, csv, docx, doc, jpeg, and jpg."
}
]
}
/Integration/Documents
Target server for requests. Edit to use your own host.
The media type of the request body
Your Wholechain Connect API key.
Display name of the document in Wholechain. Leading and trailing spaces are trimmed.
Document type. Values are not case-sensitive.
Custom document type name. Required when X-Document-Type is Other.
Optional external Document ID. Must be unique within the Wholechain account, ignoring case. If omitted, Wholechain generates an ID in the format DOC-<32 hex characters>.
Optional document expiration date in YYYY-MM-DD format.
External ID of a trade partner in your Wholechain account.
External ID of a location in your Wholechain account.
External ID of a product in your Wholechain account.
Document privacy setting. Values are not case-sensitive. Defaults to Private.
Document file to upload. Supported extensions: .pdf, .xlsx, .xls, .csv, .docx, .doc, .jpeg and .jpg. One file per request.
Request Preview
Response
Response will appear here after sending the request
Responses
Introduction
The Documents API lets you upload supporting documents to Wholechain, ready to attach to traceability events, with no manual steps in the Wholechain UI.
Use it for purchase orders, invoices, bills of lading, packing lists, certificates, inspection records and similar files. Documents uploaded through the API appear in the Wholechain Documents section and on each event they are attached to, just like documents uploaded in the UI.
Attaching a document takes two steps:
- Upload the document with
POST /Integration/Documents. Send one file per request. The response returns the Document ID: either the one you supplied or one Wholechain generated. - Reference the Document ID in an event. Add it to the
DocumentIdsarray of any event you send. Wholechain attaches the document when it creates the event. This step is covered in the Events API documentation.
A document can be attached to any number of events, and its ID can be used as soon as the upload succeeds.
Authentication
Authenticate every request with your existing Wholechain Connect API key in the X-API-KEY header.
Documents belong to the account that owns the API key. An event can only attach documents from that same account.
| Environment | Base URL |
|---|---|
| Production | https://connect.wholechain.com |
| Sandbox | https://connect-sandbox.wholechain.com |
Upload a document
POST /Integration/Documents uploads one file as multipart/form-data, with its metadata sent in X-Document-* headers.
Headers
| Header | Required | Description |
|---|---|---|
X-API-KEY | Yes | Your Wholechain Connect API key. |
X-Document-Title | Yes | Display name shown in Wholechain. Max 200 characters. Leading and trailing spaces are trimmed. |
X-Document-Type | Yes | Certification, PurchaseOrder, BOL or Other. Not case-sensitive. |
X-Document-Type-Name | When type is Other | Your label for the document type, such as Invoice or Packing List. |
X-Document-Id | No | Your own ID for the document, such as the ID in your ERP. 1–100 characters: letters, digits, ., _, - and #. Must be unique in your account, ignoring case. If omitted, Wholechain generates an ID in the form DOC-<32 hex characters>. |
X-Document-Expiration-Date | No | Expiration date in YYYY-MM-DD format, such as 2027-08-17. |
X-Document-Company | No | External ID of a trade partner in your account. |
X-Document-Location | No | External ID of a location in your account. |
X-Document-Product | No | External ID of a product in your account. |
X-Document-Privacy | No | Public or Private. Not case-sensitive. Defaults to Private. |
File
Send the file in a multipart form field named file. Each request takes exactly one file.
| Rule | Value |
|---|---|
| Allowed extensions | .pdf, .xlsx, .xls, .csv, .docx, .doc, .jpeg, .jpg (not case-sensitive) |
| Maximum size | 20 MB |
| Empty files | Rejected |
Set the file's content type in the request (for example type=application/pdf with curl). Wholechain determines the stored content type from the file; the fileType in the response shows the stored value.
Example request
curl -X POST "https://connect.wholechain.com/Integration/Documents" \
-H "X-API-KEY: <api-key>" \
-H "X-Document-Title: Purchase Order 450001" \
-H "X-Document-Type: PurchaseOrder" \
-H "X-Document-Id: PO-450001" \
-H "X-Document-Expiration-Date: 2027-08-17" \
-H "X-Document-Company: <trade-partner-id>" \
-H "X-Document-Location: <location-id>" \
-H "X-Document-Product: <product-id>" \
-H "X-Document-Privacy: Private" \
-F "file=@purchase-order-450001.pdf;type=application/pdf"
For a custom type, send Other together with a type name:
curl -X POST "https://connect.wholechain.com/Integration/Documents" \
-H "X-API-KEY: <api-key>" \
-H "X-Document-Title: Invoice INV-12345" \
-H "X-Document-Type: Other" \
-H "X-Document-Type-Name: Invoice" \
-F "file=@INV-12345.xlsx;type=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
Upload response
The response holds a documents array with one result for the uploaded file. Check status: Uploaded means success; Failed means nothing was stored, and error says why.
Status codes
| HTTP status | Meaning |
|---|---|
200 OK | Document uploaded. status is Uploaded. |
422 Unprocessable Content | Validation failed. status is Failed and error gives the reason. Nothing is stored. |
401 Unauthorized | API key missing or not valid. |
413 Request Entity Too Large | File exceeds the size limit. |
Fields
| Field | Description |
|---|---|
documentId | The Document ID: yours, or the one Wholechain generated. Use it in events. |
title | The stored title. |
documentType | Certification, PurchaseOrder, BOL or Other. |
documentTypeName | The custom type name, when the type is Other. |
fileName | The original file name. |
fileType | The file's content type, such as application/pdf. |
expirationDate | Expiration date (YYYY-MM-DD), or null. |
company | Trade partner external ID, or null. |
location | Location external ID, or null. |
product | Product external ID, or null. |
privacy | Public or Private. |
status | Uploaded or Failed. |
error | Why the upload failed, or null. |
Success example
{
"documents": [
{
"documentId": "PO-450001",
"title": "Purchase Order 450001",
"documentType": "PurchaseOrder",
"documentTypeName": null,
"fileName": "purchase-order-450001.pdf",
"fileType": "application/pdf",
"expirationDate": "2027-08-17",
"company": "<trade-partner-id>",
"location": "<location-id>",
"product": "<product-id>",
"privacy": "Private",
"status": "Uploaded",
"error": null
}
]
}
Failure example: duplicate Document ID
An ID already in use is rejected, and the existing document is left unchanged.
{
"documents": [
{
"documentId": "PO-450001",
"title": "Purchase Order 450001",
"documentType": "PurchaseOrder",
"documentTypeName": null,
"fileName": "purchase-order-450001.pdf",
"fileType": "application/pdf",
"expirationDate": null,
"company": null,
"location": null,
"product": null,
"privacy": "Private",
"status": "Failed",
"error": "Document ID 'PO-450001' is already in use."
}
]
}
Failure example: unsupported file type
{
"documents": [
{
"documentId": "PO-450001",
"fileName": "invoice.txt",
"fileType": "text/plain",
"status": "Failed",
"error": "The file type is not supported. Allowed types are pdf, xlsx, xls, csv, docx, doc, jpeg, and jpg."
}
]
}
Attaching documents to events
After a successful upload, add the returned documentId to the DocumentIds array of an event. See the Events API documentation for the field, the supported event types, and how IDs that can't be found are reported.
- The Document ID can be used as soon as the upload returns
status: Uploaded. - One document can be attached to any number of events.
- Only documents from the same account as the API key can be attached.
Errors reference
Every validation failure returns 422 with status: Failed and one of these messages in error. Nothing is stored.
error message | Cause |
|---|---|
Document title is required. | X-Document-Title missing or blank. |
Document title must not be longer than 200 characters. | Title over 200 characters. |
Document type is required and must be Certification, PurchaseOrder, Other, or BOL. | X-Document-Type missing or not one of the four values. |
Document type name is required when the document type is Other. | Type is Other without X-Document-Type-Name. |
Document ID must be 1-100 characters and must not contain control characters. | The document ID is empty, longer than 100 characters, or contains a control character. Letters, digits, periods, underscores, hyphens, and # are accepted; spaces are rejected. |
Document ID '<id>' is already in use. | The ID already exists in your account, in any letter case. |
Expiration date must be an ISO date (YYYY-MM-DD). | Wrong format or not a real date, such as 2027-02-29. |
Privacy must be Public or Private. | Any other privacy value. |
Company not found: <id> | No trade partner with that external ID. |
Location not found: <id> | No location with that external ID. |
Product not found: <id> | No product with that external ID. |
Please add a document file. | No file field, or the file is empty. |
The file type is not supported. Allowed types are pdf, xlsx, xls, csv, docx, doc, jpeg, and jpg. | File type not in the allowed list. |
Send one file per request. | More than one file in the request. Note: this error body is a plain JSON string, not a documents array. |
Non-validation errors:
| HTTP status | Cause |
|---|---|
401 | API key not valid. Body: {"type":"https://tools.ietf.org/html/rfc9110#section-15.5.2","title":"The API key is not valid.","status":401,"traceId":"…"} |
400 | X-API-KEY header empty (The apiKey field is required.). |
413 | File over the size limit. The response body is an HTML page from the web server, not JSON. |