Uploading Files from a URL in Tabidoo Server Scripts using multipart form
A Complete Guide to Building Multipart Form Requests for File Uploads Using Native Fetch
Table of Contents
📋 Prerequisites
Tabidoo server-side scripting (Node 18+ environment).
A table record (sptDigitooData
) with your stored tokens (so you can call a helper like getAccessToken()
).
A destination API that accepts multipart uploads (in our case Digitoo’s queue upload).
🚀 Overview
Fetch the remote file as binary.
Build the multipart/form-data payload manually (since FormData
+ fetch
on Tabidoo server may not work).
POST the raw payload with native fetch
, setting the correct Content-Type
header.
Parse and handle the response.
1️⃣ Fetch the Remote File
Use native fetch
to download the file into memory:
// URL of the file you want to upload
const fileUrl = 'https://example.com/invoice.pdf';
// 1.1) Download as ArrayBuffer
const fileRes = await fetch(fileUrl);
if (!fileRes.ok) {
throw new Error(`Failed to fetch file: ${fileRes.status} ${fileRes.statusText}`);
}
// 1.2) Convert to Uint8Array binary
const arrayBuffer = await fileRes.arrayBuffer();
const fileBytes = new Uint8Array(arrayBuffer);
2️⃣ Build the Multipart Body Manually
Since FormData
may drop the binary on Tabidoo’s server, you can assemble the multipart payload yourself:
// 2.1) Create a unique boundary
const boundary = '----TabidooBoundary' + Math.random().toString(36).slice(2);
const CRLF = '\r\n';
// 2.2) Start part (headers + double-CRLF before file bytes)
let part = `--${boundary}${CRLF}`;
part += `Content-Disposition: form-data; name="files"; filename="invoice.pdf"${CRLF}`;
part += `Content-Type: application/pdf${CRLF}${CRLF}`;
// 2.3) End boundary marker
const ending = `${CRLF}--${boundary}--${CRLF}`;
// 2.4) Encode the text segments
const encoder = new TextEncoder();
const startBuf = encoder.encode(part);
const endBuf = encoder.encode(ending);
// 2.5) Combine into one Uint8Array
const bodyBuf = new Uint8Array(
startBuf.length +
fileBytes.length +
endBuf.length
);
bodyBuf.set(startBuf, 0);
bodyBuf.set(fileBytes, startBuf.length);
bodyBuf.set(endBuf, startBuf.length + fileBytes.length);
3️⃣ Send the Request with Native fetch
Use Tabidoo’s server-side support for native fetch
. Be sure to include your auth token and the correct Content-Type
.
// 3.1) Get your Bearer token however you do it (e.g. getAccessToken())
const token = await getAccessToken();
// 3.2) Build your upload URL
const queueId = doo.environment.currentApplication.params.sptDigitooSettings.queueID;
const uploadUrl = `https://api.digitoo.ai/api/v2/queues/${queueId}/upload`;
// 3.3) Perform the POST
const response = await fetch(uploadUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': `multipart/form-data; boundary=${boundary}`
},
body: bodyBuf
});
4️⃣ Handle the Response
Read it as text, then parse to JSON—this lets you log errors or success details.
// 4.1) Read raw text
const text = await response.text();
console.debug('Raw response:', text);
// 4.2) Try to parse JSON
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error(`Invalid JSON: ${text}`);
}
// 4.3) Check HTTP status
if (!response.ok) {
const msg = data.error?.message || text;
throw new Error(`Upload failed ${response.status}: ${msg}`);
}
// 4.4) Success!
const documentIds = data.document_ids;
console.log('Uploaded document IDs:', documentIds);
📜 Complete Example
Putting it all together:
async function uploadFromUrl(fileUrl: string): Promise<string[]> {
// 1) Download file
const res = await fetch(fileUrl);
if (!res.ok) throw new Error(`Fetch failed ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
// 2) Build multipart body
const boundary = '----TabidooBoundary' + Math.random().toString(36).slice(2);
const CRLF = '\r\n';
let hdr = `--${boundary}${CRLF}` +
`Content-Disposition: form-data; name="files"; filename="upload.pdf"${CRLF}` +
`Content-Type: application/pdf${CRLF}${CRLF}`;
const footer = `${CRLF}--${boundary}--${CRLF}`;
const enc = new TextEncoder();
const startB = enc.encode(hdr);
const endB = enc.encode(footer);
const body = new Uint8Array(startB.length + bytes.length + endB.length);
body.set(startB, 0);
body.set(bytes, startB.length);
body.set(endB, startB.length + bytes.length);
// 3) POST via fetch
const token = await getAccessToken();
const queueId = doo.environment.currentApplication.params.sptDigitooSettings.queueID;
const url = `https://api.digitoo.ai/api/v2/queues/${queueId}/upload`;
const resp = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': `multipart/form-data; boundary=${boundary}`
},
body
});
// 4) Parse and return
const txt = await resp.text();
const json = JSON.parse(txt);
if (!resp.ok) throw new Error(`Upload error ${resp.status}: ${json.error?.message || txt}`);
return json.document_ids;
}