Document Uploads
Several compliance attestations (identity documents, proof of address, formation documents, UBO ownership documents, etc.) reference files that you upload separately through Mural's Document API. Uploads use a two-step presigned POST flow:
- Call Mural's API to request a presigned upload URL. The response includes a
documentId, the S3 URL, and a set of signed form fields. - POST the file directly to that URL as
multipart/form-data, including all of the returned form fields.
Once the file lands, the documentId becomes referenceable in attestation requests.
Constraints
| Limit | Value |
|---|---|
| Allowed content types | image/jpeg, image/jpg, image/png, application/pdf |
| Minimum file size | 1 byte (empty uploads are rejected) |
| Maximum file size | 10 MB |
| Filename length | Up to 255 characters |
| Presigned URL expiry | 15 minutes from creation |
| Orphan record TTL | Unconfirmed PENDING_UPLOAD records are deleted at 24 hours |
Step 1: Request a presigned upload URL
Call the Generate Upload URL endpoint with the filename and MIME type.
If you are acting on behalf of a child organization (parent API key), include the on-behalf-of: <organizationId> header. The resulting document will be owned by that organization. All four Documents endpoints support this header.
Example request
curl --request POST \
--url https://api.muralpay.com/api/documents/generate-upload-url \
--header 'accept: application/json' \
--header 'authorization: Bearer $API-KEY' \
--header 'content-type: application/json' \
--header 'mural-idempotency-key: 7c8e8f1c-3e54-4b3e-b66a-2b94a3d9c4e0' \
--data '{
"filename": "passport_scan.pdf",
"contentType": "application/pdf"
}'Save the documentId — you will reference it in attestation requests after the upload completes. The uploadUrl and uploadUrlFormFields are used only for the upload itself.
Step 2: Upload the file to S3
POST the file to uploadUrl as multipart/form-data. Two rules to follow exactly:
- Include every entry in
uploadUrlFormFieldsas a form field. They contain the signature, the upload policy, and the destination key — drop one and S3 will reject the upload. - The
filefield must come last. S3 ignores any form fields that arrive after the file, so the order matters.
A successful upload returns a 204 No Content from S3.
JavaScript example (Node 18+)
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { randomUUID } from 'node:crypto';
async function uploadDocument(filePath, contentType) {
const fileBuffer = await readFile(filePath);
const filename = basename(filePath);
// Step 1 — ask Mural for a presigned URL.
const presignedResponse = await fetch('https://api.muralpay.com/api/documents/generate-upload-url', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MURAL_API_KEY}`,
'Content-Type': 'application/json',
'mural-idempotency-key': randomUUID(),
},
body: JSON.stringify({ filename, contentType }),
});
if (!presignedResponse.ok) {
throw new Error(`Failed to generate upload URL: ${presignedResponse.status}`);
}
const { documentId, uploadUrl, uploadUrlFormFields } = await presignedResponse.json();
// Step 2 — POST the file directly to S3.
const formData = new FormData();
for (const [key, value] of Object.entries(uploadUrlFormFields)) {
formData.append(key, value);
}
// File must be appended last (S3 requirement).
formData.append('file', new Blob([fileBuffer], { type: contentType }), filename);
const uploadResponse = await fetch(uploadUrl, {
method: 'POST',
body: formData,
});
if (!uploadResponse.ok) {
throw new Error(`Upload to S3 failed: ${uploadResponse.status}`);
}
return documentId;
}
const documentId = await uploadDocument('./passport_scan.pdf', 'application/pdf');
console.log(`Uploaded as ${documentId}`);Step 3: Reference the document in an attestation
Once you have a documentId, pass it into any attestation that expects a document reference. For example, an individual identity document attestation:
{
"type": "individualIdentityDocument",
"governmentId": {
"type": "driversLicense",
"countryCode": "US",
"driverLicenseNumber": "D123456",
"driverLicenseFrontDocumentId": "550e8400-e29b-41d4-a716-446655440010",
"driverLicenseBackDocumentId": "d54bbc16-ad84-41db-96e4-22562a943561"
}
}Updated about 7 hours ago