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:

  1. 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.
  2. 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

LimitValue
Allowed content typesimage/jpeg, image/jpg, image/png, application/pdf
Minimum file size1 byte (empty uploads are rejected)
Maximum file size10 MB
Filename lengthUp to 255 characters
Presigned URL expiry15 minutes from creation
Orphan record TTLUnconfirmed 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:

  1. Include every entry in uploadUrlFormFields as a form field. They contain the signature, the upload policy, and the destination key — drop one and S3 will reject the upload.
  2. The file field 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"
  }
}