API Documentation

The FileCourier API enables you to programmatically request documents from external users. When a request is processed, an email or SMS containing a unique secure upload link is sent to the recipient. Once files are uploaded, FileCourier delivers them to your application via webhook.

Quick Start

To get started, create a Project in your dashboard. Each project has a Client ID and Client Secret for API authentication.

Authentication

All API requests require authentication using HTTP Basic Auth. Use your Project's Client ID as the username and Client Secret as the password.

Getting Your Credentials
  1. Go to Projects in your dashboard
  2. Create a new project or select an existing one
  3. Copy your Client ID and Client Secret
Authorization Header

Combine your Client ID and Client Secret with a colon (:), then Base64 encode the string:

Format
Authorization: Basic base64(client_id:client_secret)
Example
cURL
curl -X POST https://api.filecourier.io/api/ext/sessions \
  -H "Content-Type: application/json" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d '{
    "recipient": {
      "email": "customer@example.com",
      "name": "John Doe"
    }
  }'

The -u flag in cURL automatically handles the Base64 encoding for Basic Auth.

Keep your credentials secret. The Client Secret is only shown once when you create a project. Do not expose credentials in client-side code or public repositories.

Base URL

All API requests should be made to the following base URL:

Production
https://api.filecourier.io

External API endpoints are prefixed with /api/ext/. For example:

  • POST /api/ext/sessions - Create a new upload session
  • GET /api/ext/sessions/{sessionId} - Get session status
  • DELETE /api/ext/sessions/{sessionId} - Cancel a session
POST /api/ext/sessions

Create Session

Creates a new upload session and sends the recipient a secure upload link. The recipient needs an email address, a phone number, or both. The link goes by email when there is an email address, and by SMS when notify.sms is true, or by default when the request has only a phone number. When the recipient completes the upload, FileCourier sends one webhook to the session's callbackUrl, or to the project's webhook URL when webhooks are enabled.

Request Headers
Name Type Description
Content-Type string Must be application/json
Authorization string REQUIRED Basic Auth with Client ID and Client Secret
Basic base64(client_id:client_secret)
Request Body
Name Type Description
recipient.email string EMAIL OR PHONE Email address of the person who should upload documents. At least one of recipient.email and recipient.phone is required.
recipient.name string OPTIONAL Recipient name, used in the email greeting. At most 255 characters.
recipient.phone string EMAIL OR PHONE Phone number, best in E.164 format (e.g., +14165550123); spaces, dashes and brackets are accepted, and a 10 digit North American number gets +1. Required when notify.sms is true, and the only contact needed for a text only request.
request.subject string OPTIONAL Subject line for the email and heading on the upload page. At most 500 characters.
request.message string OPTIONAL Message shown to the recipient explaining what documents are needed. At most 5,000 characters; line breaks are kept.
request.documents array OPTIONAL Names of the documents requested (e.g., ["ID Verification", "Proof of Address"]), or a comma separated string. At most 20 names of up to 100 characters each.
referenceId string OPTIONAL Your own reference, such as an order number. Returned in the webhook, and in the response as referenceId. At most 255 characters (a longer value is a 400); spaces at either end and control characters are removed.
callbackUrl string OPTIONAL URL that receives the webhook when the upload completes. When omitted, the project's webhook URL is used if webhooks are enabled. Must start with https://, name a public host, carry no user name or password, and be at most 500 characters; anything else is a 400. See Webhook Events.
options.maxFileSizeMb number OPTIONAL Maximum size of each file in MB, a whole number from 1 to 100. Defaults to the project setting, 25 MB unless changed.
options.allowedFileTypes array OPTIONAL The file extensions the recipient may upload, as an array or a comma separated string: png, jpg, jpeg, pdf, doc, docx, xls and xlsx only. Case and a leading dot are ignored; the stored list is in that order. Defaults to png, jpg, jpeg and pdf. An empty list or any other extension is a 400 with a message naming it.
options.maxFiles number OPTIONAL Maximum number of files, a whole number from 1 to 20. Defaults to 1.
options.expirationHours number OPTIONAL Hours until the upload link expires, a whole number from 1 to 720 (30 days). Defaults to the project's link expiration, 24 hours unless changed.
brand.name string OPTIONAL Company name shown on the upload page and in the email, at most 255 characters. Defaults to the project name.
brand.logoUrl string OPTIONAL URL of your logo: https://, a public host, no user name or password, at most 500 characters (anything else is a 400). When omitted, the logo your workspace uploaded in the dashboard is used.
metadata object OPTIONAL Your own data, such as an order or record ID. Returned unchanged in GET, list and the webhook. Keys starting with _ are reserved and are not returned.
notify.email boolean OPTIONAL Send the upload link by email. Defaults to true when recipient.email is given. true without an email address is a 400.
notify.sms boolean OPTIONAL Send the upload link by SMS. Defaults to false, or to true when the request has a phone number and no email address. Requires recipient.phone.
Example Request
cURL
curl --location --request POST 'https://api.filecourier.io/api/ext/sessions' \
  --header 'Content-Type: application/json' \
  -u 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' \
  --data-raw '{
    "recipient": {
      "email": "customer@example.com",
      "name": "John Doe"
    },
    "request": {
      "subject": "Documents for your order",
      "message": "Please upload the following documents.",
      "documents": ["Government ID", "Proof of Address"]
    },
    "options": { "expirationHours": 48 },
    "metadata": { "orderId": "ORD-12345" },
    "callbackUrl": "https://api.acmecorp.com/webhooks/filecourier"
  }'
JavaScript
// Create Base64 encoded credentials
const credentials = btoa('YOUR_CLIENT_ID:YOUR_CLIENT_SECRET');

const response = await fetch('https://api.filecourier.io/api/ext/sessions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Basic ${credentials}`
  },
  body: JSON.stringify({
    recipient: {
      email: 'customer@example.com',
      name: 'John Doe'
    },
    request: {
      message: 'Please upload the following documents.',
      documents: ['Government ID', 'Proof of Address']
    },
    metadata: { orderId: 'ORD-12345' },
    callbackUrl: 'https://api.acmecorp.com/webhooks/filecourier'
  })
});

const data = await response.json();
Example: text message only

A recipient with only a phone number gets the upload link by text message.

Request body
{
  "recipient": { "phone": "+14165550123", "name": "John Doe" },
  "request": { "subject": "Photo ID for order 1002" }
}

The response has "recipient": { "email": null, "name": "John Doe", "phone": "+14165550123" } and "notifications": { "email": false, "sms": true }.

Response
201 Created - Session created
Response
{
  "success": true,
  "message": "Upload session created successfully",
  "data": {
    "session": {
      "id": 123,
      "sessionToken": "ses_abc123xyz",
      "uploadUrl": "https://secure.filecourier.io/upload/ses_abc123xyz",
      "status": "pending",
      "expiresAt": "2025-01-24T10:30:00.000Z",
      "recipient": { "email": "customer@example.com", "name": "John Doe", "phone": null },
      "options": { "maxFileSizeMb": 25, "allowedFileTypes": ["png", "jpg", "jpeg", "pdf"], "maxFiles": 1 },
      "notifications": { "email": true, "sms": false },
      "notificationsRequested": { "email": true, "sms": false },
      "createdAt": "2025-01-22T10:30:00.000Z"
    }
  }
}

notificationsRequested says which channels the request asked for, and notifications says, per channel, whether the email or text was actually accepted for delivery. A failed send does not fail the request: the session is created, its upload link works, and the failure is recorded in its activity. When the recipient must enter a passcode, it goes to the email address, or by text when the request went only by text.

400 Bad Request - Invalid request parameters
Error Response
{
  "success": false,
  "error": "recipient.email or recipient.phone is required"
}

A value outside the limits above, or of the wrong type (for example the string "3" for options.maxFiles), is a 400 with a message such as "Max files must be a whole number from 1 to 20." or "Subject must be 500 characters or fewer." Text is trimmed and control characters are removed; an empty string counts as not given, so the default applies.

401 Unauthorized - Invalid or missing API credentials
Error Response
{
  "success": false,
  "error": "Invalid API credentials. Use Basic auth with clientId:clientSecret or x-api-key header."
}
GET /api/ext/sessions/{sessionId}

Get Session Status

Retrieves an upload session with its status, uploaded files and activity events.

Path Parameters
Name Type Description
sessionId string REQUIRED The sessionToken (starting ses_) or the numeric id returned when creating the session
Example Request
cURL
curl --location --request GET 'https://api.filecourier.io/api/ext/sessions/ses_abc123xyz' \
  -u 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET'
Response
Response
{
  "success": true,
  "data": {
    "session": {
      "id": 123,
      "sessionToken": "ses_abc123xyz",
      "uploadUrl": "https://secure.filecourier.io/upload/ses_abc123xyz",
      "status": "uploaded",
      "recipient": { "email": "customer@example.com", "name": "John Doe", "phone": null },
      "options": { "maxFileSizeMb": 25, "allowedFileTypes": ["png", "jpg", "jpeg", "pdf"], "maxFiles": 1 },
      "files": [
        {
          "id": 456,
          "filename": "drivers-license.pdf",
          "sizeBytes": 245832,
          "contentType": "application/pdf",
          "scanStatus": "pending",
          "validationStatus": "passed",
          "uploadedAt": "2025-01-22T14:22:15.000Z"
        }
      ],
      "events": [
        { "type": "upload_completed", "data": { }, "ip": "203.0.113.10", "timestamp": "2025-01-22T14:22:18.000Z" }
      ],
      "metadata": { "orderId": "ORD-12345" },
      "accessedAt": "2025-01-22T14:20:02.000Z",
      "completedAt": "2025-01-22T14:22:18.000Z",
      "expiresAt": "2025-01-24T10:30:00.000Z",
      "createdAt": "2025-01-22T10:30:00.000Z"
    }
  }
}
Status Values
Status Description
pending Session created and waiting for the recipient
accessed The recipient opened the upload link and has not completed an upload
uploaded The recipient completed an upload. The webhook, if any, is sent once at this point.
expired The upload link expired. Written when someone opens the link after its expiry, so a session nobody reopens keeps its earlier status.
cancelled The session was cancelled through the API
DELETE /api/ext/sessions/{sessionId}

Cancel Session

Cancels an upload session, so the upload link stops working. {sessionId} is the sessionToken or the numeric id. Returns 400 if the session is already cancelled or has already received an upload.

Example Request
cURL
curl --location --request DELETE 'https://api.filecourier.io/api/ext/sessions/ses_abc123xyz' \
  -u 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET'
Response
Response
{
  "success": true,
  "message": "Session cancelled successfully"
}

Webhook Events

When the recipient completes an upload, FileCourier sends one JSON POST to the request's callbackUrl, or, when the request has none, to the project's webhook URL if webhooks are enabled for the project. There is one event, upload.completed.

Headers sent with every webhook:

  • Content-Type: application/json
  • X-FileCourier-Event: upload.completed
  • X-FileCourier-Session: the request's sessionToken

Webhooks are not signed. To check one, read the request back with GET /api/ext/sessions/{sessionId} using the sessionToken it carries.

Webhook Payload
upload.completed
{
  "event": "upload.completed",
  "sessionToken": "ses_...",
  "referenceId": "ORDER-12345",
  "metadata": { "orderId": "ORD-12345" },
  "files": [
    {
      "id": 5001,
      "filename": "drivers-license.pdf",
      "sizeBytes": 245832,
      "contentType": "application/pdf",
      "downloadUrl": "https://...s3.us-east-1.amazonaws.com/...?X-Amz-Expires=300&X-Amz-Signature=..."
    }
  ],
  "completedAt": "2026-01-22T14:22:18.000Z"
}
File URLs expire after 5 minutes or less. downloadUrl is a temporary Amazon S3 link, valid for 5 minutes, or until the file's retention period ends if that is sooner, and is null once the retention period has ended. The payload has no expiry field. For a project that requires a passcode on upload links, files have no downloadUrl key at all: open them in the FileCourier dashboard, where each access is recorded. Download the files immediately upon receiving the webhook. Files are deleted automatically when the workspace's retention period ends (see Document Retention).
  • sessionToken: the request, as returned by POST /api/ext/sessions.
  • metadata: the metadata you sent when creating the request, unchanged, without keys that start with _. Use it to match the webhook to your own records.
  • referenceId: the reference you supplied, or null.
  • files: the files uploaded in this completion. completedAt: when the upload completed.
Handling Webhooks
webhook-handler.js
// Express.js webhook handler example
app.post('/webhooks/filecourier', async (req, res) => {
  const { event, sessionToken, metadata, files } = req.body;

  if (event === 'upload.completed') {
    // Your own data from the create request
    const orderId = metadata.orderId;

    for (const file of files) {
      // No downloadUrl key: a passcode project; open the file in the dashboard.
      // null: the retention period has ended.
      if (!file.downloadUrl) continue;
      const response = await fetch(file.downloadUrl); // within 5 minutes
      const buffer = await response.arrayBuffer();
      await saveFile(orderId, file.filename, buffer);
    }

    await updateOrder(orderId, { documentsReceived: true, sessionToken });
  }

  // Answer 2xx: any other answer counts as failed and is not retried
  res.status(200).json({ received: true });
});

FileCourier makes one delivery attempt per completed upload. A 2xx answer marks it delivered; any other answer, no answer, or a redirect (redirects are not followed) counts as a failed delivery and is not retried. The webhook carries no signature, and the only FileCourier headers sent are X-FileCourier-Event and X-FileCourier-Session.

A webhook URL, the project's or a request's callbackUrl, must start with https://, name a real host, carry no user name or password, and not point at localhost or a private, loopback or reserved address. It is checked again just before sending, including every address the host name resolves to; a URL that fails is not called, and the delivery counts as failed.

Document Retention

  • A Workspace Admin sets the retention period in the FileCourier dashboard, from 5 minutes up to the platform maximum shown there. It applies to every document in the workspace.
  • Documents are deleted automatically within about a minute after their retention period ends, counted from upload, whatever the status of the request. From the moment the period ends, a document can no longer be downloaded.
  • The access record of a document (who opened it, when, and from which IP address) is kept after the document is deleted. It holds no document content.
  • Every document link, in the dashboard or in a webhook, lasts at most 5 minutes, and never past the end of the retention period.
  • Documents are available to named users of your workspace in the FileCourier dashboard, where each access is recorded. Apart from a webhook's downloadUrl, the API has no way to download a document. Programmatic retrieval is planned.

Need Help?

If you have questions or run into issues, we're here to help.