External API
SendMeDocs provides a RESTful API for programmatically creating document requests, checking their status, downloading submitted files, and cancelling requests. This lets you integrate document collection into your existing workflows without using the dashboard.
Quick start
- Go to Settings > API Keys in the dashboard (owner or admin only)
- Click Create API Key, give it a name
- Copy the key immediately — it starts with
smd_and is shown only once - Make your first request:
curl -X POST https://your-instance.com/api/v1/requests \
-H "Authorization: Bearer smd_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"recipient_name": "Jane Doe",
"recipient_contact": "jane@example.com",
"message": "Please upload your documents.",
"items": [
{ "prefab": "photo_id" },
{ "name": "Tax Return", "description": "Last 2 years", "allow_multiple": true },
{ "name": "Filing Status", "type": "select", "options": ["Single", "Married Filing Jointly", "Head of Household"] }
]
}'
The response includes the request id and an upload_url path that the recipient will use to upload their documents. You can also use template_id instead of (or in addition to) items — see Templates.
Human operator checklist
Complete these steps before an AI (or developer) can implement the integration. These require dashboard access or organizational decisions that can't be generated by code alone.
Before implementation
- Create an API key in the dashboard — go to Settings > API Keys (requires owner or admin role). Give the key a descriptive name (e.g. "CRM integration"). Click Create.
- Copy the key immediately — it is shown exactly once. The full key looks like
smd_followed by 64 hex characters (68 chars total). Store it in your secrets manager or.envfile. - Provide the key to your integration — set it as an environment variable (e.g.
SENDMEDOCS_API_KEY) so your code can include it in theAuthorizationheader. - Decide which prefab item types you need — see Prefab item types below. Custom items can also be defined inline.
- Set up webhook receiving (optional) — if you want to be notified when requests are completed, set up a webhook endpoint. See docs/webhooks.md.
After implementation
- Create a test request — use the API to create a request with a test email address you control.
- Complete the upload flow — open the
upload_urland upload test files to verify the end-to-end flow. - Verify file download — use the download endpoint to confirm you can retrieve the uploaded files.
- Confirm webhook delivery (if configured) — check the webhook delivery log in the dashboard to verify events are being received.
Authentication
All API requests require an Authorization header with a Bearer token:
Authorization: Bearer smd_a1b2c3d4e5f6...
API keys are org-scoped — every request made with a key operates within that organization. Keys are created in the dashboard by owners or admins.
Key format: smd_ prefix + 32 random bytes (hex-encoded) = 68 characters total. The key is stored as a SHA-256 hash on the server. The first 12 characters (key_prefix) are shown in the dashboard for identification.
Key limits: Maximum 5 API keys per organization.
If the key is missing or invalid, the API returns:
HTTP 401
{ "error": "Missing or invalid API key. Use: Authorization: Bearer smd_..." }
Rate limits
Each API key is limited to 60 requests per minute. When exceeded:
HTTP 429
{ "error": "Rate limit exceeded. Maximum 60 requests per minute." }
The rate limit uses a sliding window. Wait briefly and retry.
Base URL
All endpoints are under /api/v1/:
https://your-instance.com/api/v1/requests GET (list) / POST (create)
https://your-instance.com/api/v1/requests/:id GET (detail)
https://your-instance.com/api/v1/requests/:id/files/:fileId/download GET
https://your-instance.com/api/v1/requests/:id/cancel POST
https://your-instance.com/api/v1/templates GET (list) / POST (create)
https://your-instance.com/api/v1/templates/:id GET (detail) / PATCH (update) / DELETE
See also: MCP Server for AI assistant integration.
Endpoints
List requests
GET /api/v1/requests
Returns a paginated list of document requests for your organization.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
status |
string | — | Filter by status: pending, completed, or expired |
search |
string | — | Search by recipient name or contact (case-insensitive partial match) |
limit |
number | 20 | Number of results to return (1-100) |
offset |
number | 0 | Number of results to skip (for pagination) |
Response (200):
{
"requests": [
{
"id": "019471a2-b3c4-7d5e-8f6a-1234567890ab",
"recipient_name": "Jane Doe",
"recipient_contact": "jane@example.com",
"status": "pending",
"created_at": "2026-02-10T12:00:00.000Z",
"expires_at": null,
"completed_at": null
}
],
"total": 42
}
The total field is the count of all matching requests (ignoring limit/offset), useful for building pagination UI.
curl example:
# List all requests
curl https://your-instance.com/api/v1/requests \
-H "Authorization: Bearer smd_your_key_here"
# Filter by status with pagination
curl "https://your-instance.com/api/v1/requests?status=pending&limit=10&offset=0" \
-H "Authorization: Bearer smd_your_key_here"
# Search by recipient
curl "https://your-instance.com/api/v1/requests?search=jane" \
-H "Authorization: Bearer smd_your_key_here"
Create request
POST /api/v1/requests
Creates a new document request. Sends an upload link email to the recipient automatically.
Request body:
{
"recipient_name": "Jane Doe",
"recipient_contact": "jane@example.com",
"message": "Please upload your documents for loan processing.",
"expires_at": "2026-03-10T00:00:00.000Z",
"items": [
{ "prefab": "photo_id", "is_required": true },
{ "prefab": "proof_of_address" },
{ "name": "Tax Return", "description": "Last 2 years", "is_required": true, "allow_multiple": true },
{ "name": "Date of Birth", "type": "date" },
{ "name": "Filing Status", "type": "select", "options": ["Single", "Married Filing Jointly", "Head of Household"] }
]
}
Or using a template:
{
"recipient_name": "Jane Doe",
"recipient_contact": "jane@example.com",
"template_id": "019471a2-b3c4-7d5e-8f6a-1234567890ab",
"items": [
{ "name": "Client ID", "type": "hidden", "value": "CRM-12345" }
]
}
Body fields:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
recipient_name |
string | Yes | 1-255 chars | Recipient's display name |
recipient_contact |
string | Yes | 1-255 chars | Email or US phone number. Accepts common formats: (555) 123-4567, 555-123-4567, 15551234567. Only US numbers are supported. |
message |
string | No | max 1000 chars | Custom message included in the upload link email |
expires_at |
string | No | ISO 8601 | When the request expires. Omit for no expiration. |
locale |
string | No | One of: en, es, fr, de, pt, zh, ja, ko |
Language for the upload portal and recipient emails. Defaults to the org's configured default locale, which defaults to en. |
template_id |
string | No | UUID | Use a template's items as the base. Any provided items are appended after the template items. |
items |
array | Conditional | 1-200 items | Document/form items to request. Required if template_id is not provided. |
Item fields:
Each item is either a prefab (predefined document type), a custom document item, or a form field:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
prefab |
string | No | — | One of: photo_id, proof_of_address, proof_of_income, w9. Sets name and description automatically. |
name |
string | No | "Document" |
Custom item name (max 255 chars). Ignored if prefab is set. |
description |
string | No | null |
Custom item description (max 1000 chars). Ignored if prefab is set. |
type |
string | No | "document" |
Item type: document, text, date, select, yes_no, or hidden. See Form field types. |
options |
string[] | Conditional | — | Required for select type. 2-20 options, each under 200 chars. |
value |
string | Conditional | — | Required for hidden type. Org-side metadata, not shown to recipient (max 2000 chars). |
is_required |
boolean | No | true |
Whether the recipient must complete this item |
allow_multiple |
boolean | No | false |
Whether the recipient can upload multiple files (document type only). Ignored if prefab is set. |
group_label |
string | No | null |
Group items visually in the upload portal. Items with the same group_label appear under a shared section header. |
Response (200):
{
"id": "019471a2-b3c4-7d5e-8f6a-1234567890ab",
"token": "abc123def456...",
"status": "pending",
"upload_url": "/u/abc123def456...",
"items": [
{
"id": "019471a2-...",
"name": "Photo ID",
"description": "Driver's license, passport, or state ID",
"is_required": true,
"allow_multiple": false,
"position": 0,
"status": "pending"
},
{
"id": "019471a3-...",
"name": "Proof of Address",
"description": "Utility bill or bank statement (within 90 days)",
"is_required": true,
"allow_multiple": false,
"position": 1,
"status": "pending"
},
{
"id": "019471a4-...",
"name": "Tax Return",
"description": "Last 2 years",
"is_required": true,
"allow_multiple": true,
"position": 2,
"status": "pending"
}
],
"created_at": "2026-02-10T12:00:00.000Z"
}
curl example:
curl -X POST https://your-instance.com/api/v1/requests \
-H "Authorization: Bearer smd_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"recipient_name": "Jane Doe",
"recipient_contact": "jane@example.com",
"items": [
{ "prefab": "photo_id" },
{ "prefab": "proof_of_address" }
]
}'
Get request detail
GET /api/v1/requests/:id
Returns the full detail of a request including items, their statuses, and any uploaded files.
Response (200):
{
"id": "019471a2-b3c4-7d5e-8f6a-1234567890ab",
"token": "abc123def456...",
"recipient_name": "Jane Doe",
"recipient_contact": "jane@example.com",
"recipient_contact_type": "email",
"status": "completed",
"message": "Please upload your documents.",
"revision_count": 0,
"created_at": "2026-02-10T12:00:00.000Z",
"expires_at": null,
"completed_at": "2026-02-11T09:30:00.000Z",
"upload_url": "/u/abc123def456...",
"items": [
{
"id": "019471a2-...",
"name": "Photo ID",
"description": "Driver's license, passport, or state ID",
"is_required": true,
"allow_multiple": false,
"position": 0,
"status": "submitted",
"revision_note": null,
"files": [
{
"id": "019471b5-...",
"filename": "drivers_license.jpg",
"file_size": 2048576,
"content_type": "image/jpeg",
"created_at": "2026-02-11T09:25:00.000Z"
}
]
}
]
}
Request status values:
| Status | Meaning |
|---|---|
pending |
Waiting for the recipient to upload documents |
completed |
Recipient has submitted all required documents |
expired |
Request passed its expiry date or was cancelled |
Item status values:
| Status | Meaning |
|---|---|
pending |
No file uploaded yet |
submitted |
File uploaded by the recipient |
approved |
Approved during revision review (locked — cannot be re-uploaded) |
rejected |
Rejected during revision review (must be re-uploaded) |
curl example:
curl https://your-instance.com/api/v1/requests/019471a2-b3c4-7d5e-8f6a-1234567890ab \
-H "Authorization: Bearer smd_your_key_here"
Download file
GET /api/v1/requests/:id/files/:fileId/download
Returns a presigned S3 download URL for a specific file. The URL is valid for 5 minutes.
Response (200):
{
"download_url": "https://s3.example.com/org123/req456/abcdef.jpg?X-Amz-...",
"filename": "drivers_license.jpg"
}
The download_url is a temporary presigned URL. Download the file immediately or within the 5-minute window. Do not store the URL — request a new one when needed.
Retention: Files from completed requests are automatically deleted after the retention period (Pay as you go: 7 days, $12/mo: 14 days, $25/mo and $60/mo: 30 days). Orgs can configure shorter retention in Settings. Once deleted, this endpoint returns 404. Download files promptly after request completion.
curl example:
# Get the download URL
curl https://your-instance.com/api/v1/requests/019471a2-.../files/019471b5-.../download \
-H "Authorization: Bearer smd_your_key_here"
# Download the file using the presigned URL
curl -o drivers_license.jpg "https://s3.example.com/..."
Cancel request
POST /api/v1/requests/:id/cancel
Cancels a pending request. Only requests with status: "pending" can be cancelled. The request status is set to expired.
Response (200):
{ "success": true }
Error — not pending:
HTTP 400
{ "error": "Can only cancel pending requests" }
curl example:
curl -X POST https://your-instance.com/api/v1/requests/019471a2-.../cancel \
-H "Authorization: Bearer smd_your_key_here"
Error responses
All errors return a JSON object with an error field:
{ "error": "Human-readable error message" }
Status codes
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Validation error (bad input, invalid state transition) |
| 401 | Invalid or missing API key |
| 404 | Resource not found (or does not belong to your organization) |
| 429 | Rate limit exceeded (60 requests per minute per key) |
Prefab item types
Prefab items provide standardized names and descriptions for common document types. Use the prefab field in the items array instead of specifying name and description manually.
| Prefab key | Name | Description | allow_multiple |
|---|---|---|---|
photo_id |
Photo ID | Driver's license, passport, or state ID | false |
proof_of_address |
Proof of Address | Utility bill or bank statement (within 90 days) | false |
proof_of_income |
Proof of Income | Pay stubs, tax returns, or employment letter | false |
w9 |
W-9 | Completed W-9 tax form | false |
When using a prefab, is_required can still be overridden (defaults to true). The allow_multiple and name/description fields come from the prefab definition.
Form field types
In addition to document uploads, items can be form fields that recipients answer inline in the upload portal. Set the type field on an item:
| Type | Recipient sees | Answer format |
|---|---|---|
document |
File upload area (default) | Uploaded file(s) |
text |
Text input / textarea | Free-form text |
date |
Date input (MM/DD/YYYY) | Date string |
select |
Dropdown menu | One of the provided options |
yes_no |
Yes / No toggle buttons | "yes" or "no" |
hidden |
Nothing (not shown) | Pre-set value from the creator |
Notes:
selectrequires anoptionsarray with 2-20 non-empty stringshiddenrequires avaluestring — used for org-side metadata (e.g. CRM IDs, internal reference numbers). Hidden items are auto-submitted at creation and never shown to the recipient.- Form field answers are auto-saved as the recipient types (500ms debounce). When the request is submitted, a "Form Responses" file is generated containing all answers in both
.txtand.jsonformat.
Group labels
Items with the same group_label appear under a shared section header in the upload portal. Use this to organize related items visually:
{
"items": [
{ "name": "Full Legal Name", "type": "text", "group_label": "Personal Information" },
{ "name": "Date of Birth", "type": "date", "group_label": "Personal Information" },
{ "prefab": "photo_id", "group_label": "Identity Documents" },
{ "prefab": "proof_of_address", "group_label": "Identity Documents" }
]
}
Items without a group_label appear ungrouped. Group ordering follows the item order — the header appears before the first item in each group.
Templates
Templates are reusable collections of items. System templates (built-in) cover common workflows for tax, healthcare, legal, and general use. Organizations can also create their own custom templates.
When creating a request with template_id, the template's items become the base. Any items you provide are appended after the template items (useful for adding org-specific metadata via hidden fields).
List templates
GET /api/v1/templates
Returns all templates available to your organization (system templates + your org's custom templates).
Query parameters:
| Parameter | Type | Description |
|---|---|---|
category |
string | Filter by category: tax, healthcare, legal, general |
Response (200):
{
"templates": [
{
"id": "019471a2-...",
"name": "Individual Tax Return (1040)",
"description": "Standard documents for individual tax return preparation",
"category": "tax",
"is_system": true,
"item_count": 12
}
]
}
Get template detail
GET /api/v1/templates/:id
Returns a template with its full list of items.
Response (200):
{
"id": "019471a2-...",
"name": "Individual Tax Return (1040)",
"description": "Standard documents for individual tax return preparation",
"category": "tax",
"is_system": true,
"item_count": 12,
"items": [
{
"id": "019471a3-...",
"name": "W-2 Forms",
"description": "From all employers",
"type": "document",
"options": null,
"is_required": true,
"allow_multiple": true,
"position": 0,
"group_label": "Income Documents",
"value": null
}
]
}
Create template
POST /api/v1/templates
Creates a custom template for your organization.
Request body:
{
"name": "New Client Onboarding",
"description": "Documents needed for new client setup",
"category": "general",
"items": [
{ "name": "Photo ID", "type": "document", "is_required": true },
{ "name": "Date of Birth", "type": "date", "group_label": "Personal Info" },
{ "name": "Preferred Contact Method", "type": "select", "options": ["Email", "Phone", "Text"] }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Template name (max 255 chars) |
description |
string | No | Template description |
category |
string | Yes | One of: tax, healthcare, legal, general |
items |
array | Yes | 1-200 template items (same fields as request items) |
Response (200):
{ "id": "019471a2-..." }
Update template
PATCH /api/v1/templates/:id
Updates a custom template's metadata. System templates cannot be modified.
| Field | Type | Description |
|---|---|---|
name |
string | New template name |
description |
string | New description |
category |
string | New category |
is_active |
boolean | Set false to soft-delete |
Response (200):
{ "success": true }
Delete template
DELETE /api/v1/templates/:id
Permanently deletes a custom template and its items. System templates cannot be deleted.
Response (200):
{ "success": true }
AI integration guide
This section is optimized for AI-assisted development (LLM agents, copilots, code generators).
Implementation checklist — API client
- Store the API key securely — read from environment variable (e.g.
SENDMEDOCS_API_KEY), never hardcode - Set headers on every request:
Authorization: Bearer smd_...andContent-Type: application/json - Create a request via
POST /api/v1/requestswith recipient info and items - Handle the response — store the
idfor later lookups and file downloads - Poll or use webhooks to know when the request is completed (see docs/webhooks.md for webhook setup)
- Fetch request detail via
GET /api/v1/requests/:idto get item statuses and file metadata - Download files via
GET /api/v1/requests/:id/files/:fileId/download— use the presigned URL within 5 minutes - Handle errors — check for
errorfield in all non-200 responses - Respect rate limits — implement backoff on 429 responses
Inputs your integration needs from the human/system
| Input | Source | Format | Example |
|---|---|---|---|
| API key | Dashboard (shown once at creation) | smd_ + 64 hex chars |
smd_a1b2c3d4e5f6... |
| Base URL | Your SendMeDocs instance | HTTPS URL | https://sendmedocs.com |
| Webhook secret (optional) | Dashboard (shown once at endpoint creation) | 64-char hex string | a1b2c3d4e5f6... |
TypeScript types
Use these types when generating an API client:
// ============================================================================
// Query params — GET /api/v1/requests
// ============================================================================
interface ListRequestsParams {
status?: "pending" | "completed" | "expired"
search?: string // partial match on name/email
limit?: number // 1-100, default 20
offset?: number // default 0
}
// ============================================================================
// Response — GET /api/v1/requests
// ============================================================================
interface ListRequestsResponse {
requests: ListRequestItem[]
total: number // total matching (ignoring limit/offset)
}
interface ListRequestItem {
id: string
recipient_name: string
recipient_contact: string
status: "pending" | "completed" | "expired"
created_at: string // ISO 8601
expires_at: string | null
completed_at: string | null
}
// ============================================================================
// Request body — POST /api/v1/requests
// ============================================================================
interface CreateRequestBody {
recipient_name: string // 1-255 chars, required
recipient_contact: string // 1-255 chars, required — email or US phone number
message?: string // max 1000 chars
expires_at?: string // ISO 8601 datetime
locale?: "en" | "es" | "fr" | "de" | "pt" | "zh" | "ja" | "ko" // upload portal + email language
template_id?: string // UUID — use template items as base
items?: CreateRequestItem[] // 1-200 items, required if no template_id
}
interface CreateRequestItem {
prefab?: "photo_id" | "proof_of_address" | "proof_of_income" | "w9"
name?: string // max 255 chars, ignored if prefab is set
description?: string // max 1000 chars, ignored if prefab is set
type?: "document" | "text" | "date" | "select" | "yes_no" | "hidden" // default: "document"
options?: string[] // required for "select" type (2-20 items)
value?: string // required for "hidden" type (max 2000 chars)
is_required?: boolean // default: true
allow_multiple?: boolean // default: false, document type only
group_label?: string // visual grouping in upload portal
}
// ============================================================================
// Response — POST /api/v1/requests
// ============================================================================
interface CreateRequestResponse {
id: string // UUID v7
token: string // upload token
status: "pending"
upload_url: string // "/u/{token}"
items: RequestItemSummary[]
created_at: string // ISO 8601
}
interface RequestItemSummary {
id: string
name: string
description: string | null
type: "document" | "text" | "date" | "select" | "yes_no" | "hidden"
is_required: boolean
allow_multiple: boolean
position: number
group_label: string | null
status: "pending"
}
// ============================================================================
// Response — GET /api/v1/requests/:id
// ============================================================================
interface GetRequestResponse {
id: string
token: string
recipient_name: string
recipient_contact: string
recipient_contact_type: "email" | "sms"
status: "pending" | "completed" | "expired"
message: string | null
revision_count: number
created_at: string // ISO 8601
expires_at: string | null // ISO 8601
completed_at: string | null // ISO 8601
upload_url: string // "/u/{token}"
items: RequestItemDetail[]
}
interface RequestItemDetail {
id: string
name: string
description: string | null
type: "document" | "text" | "date" | "select" | "yes_no" | "hidden"
is_required: boolean
allow_multiple: boolean
position: number
group_label: string | null
status: "pending" | "submitted" | "approved" | "rejected"
revision_note: string | null
draft_value: string | null // form field answer (null for document items)
files: FileDetail[]
}
interface FileDetail {
id: string
filename: string
file_size: number // bytes
content_type: string // MIME type
created_at: string // ISO 8601
}
// ============================================================================
// Response — GET /api/v1/requests/:id/files/:fileId/download
// ============================================================================
interface DownloadResponse {
download_url: string // presigned S3 URL, valid for 5 minutes
filename: string
}
// ============================================================================
// Response — POST /api/v1/requests/:id/cancel
// ============================================================================
interface CancelResponse {
success: true
}
// ============================================================================
// Error response — all endpoints
// ============================================================================
interface ErrorResponse {
error: string
}
Common mistakes to avoid
- Don't hardcode the API key — read it from an environment variable or secrets manager.
- Don't store presigned download URLs — they expire after 5 minutes. Always request a fresh URL when you need to download a file.
- Don't poll aggressively — if checking request status, poll no more than once per minute. Better yet, use webhooks for real-time notifications.
- Don't ignore
is_requireddefaults — items default tois_required: true. Setis_required: falseexplicitly if the item is optional. - Don't send both
prefabandname— ifprefabis set, thenameanddescriptionfields are ignored. Use one approach or the other. - Don't retry on 401 — an invalid API key will never become valid. Check your key configuration.
- Don't assume file order — use the
positionfield on items to determine display order, and therequest_item_idrelationship to associate files with items.
Example integration flow
A typical integration creates a request, listens for completion via webhook, then downloads the files:
1. Your system ──POST /api/v1/requests──> SendMeDocs
<── 200 { id, upload_url } ──
2. SendMeDocs sends upload link email to recipient
3. Recipient uploads documents via upload portal
4. SendMeDocs ──POST webhook──> Your endpoint
{ type: "request.completed", data: { id, items } }
5. Your system ──GET /api/v1/requests/:id──> SendMeDocs
<── 200 { items: [{ files: [...] }] } ──
6. Your system ──GET /api/v1/requests/:id/files/:fileId/download──> SendMeDocs
<── 200 { download_url } ──
7. Your system ──GET download_url──> S3
<── file bytes ──
Without webhooks (polling approach):
1. Create the request (POST /api/v1/requests)
2. Store the request id
3. Periodically GET /api/v1/requests/:id
4. When status changes to "completed", download files
5. When status changes to "expired", handle expiration
See docs/webhooks.md for webhook setup, signature verification, and event types.
Full working example (Node.js)
const API_KEY = process.env.SENDMEDOCS_API_KEY;
const BASE_URL = process.env.SENDMEDOCS_URL; // e.g. "https://sendmedocs.com"
async function createRequest(recipient) {
const res = await fetch(`${BASE_URL}/api/v1/requests`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
recipient_name: recipient.name,
recipient_contact: recipient.email,
message: "Please upload the required documents.",
items: [
{ prefab: "photo_id" },
{ prefab: "proof_of_address" },
{
name: "Signed Agreement",
description: "The signed service agreement we sent you",
is_required: true,
},
],
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Create request failed: ${err.error}`);
}
return res.json(); // { id, token, status, upload_url, items, created_at }
}
async function getRequest(requestId) {
const res = await fetch(`${BASE_URL}/api/v1/requests/${requestId}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Get request failed: ${err.error}`);
}
return res.json();
}
async function downloadFile(requestId, fileId) {
const res = await fetch(
`${BASE_URL}/api/v1/requests/${requestId}/files/${fileId}/download`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
if (!res.ok) {
const err = await res.json();
throw new Error(`Download failed: ${err.error}`);
}
const { download_url, filename } = await res.json();
// Download the actual file from the presigned URL
const fileRes = await fetch(download_url);
const buffer = await fileRes.arrayBuffer();
return { filename, buffer };
}
async function cancelRequest(requestId) {
const res = await fetch(`${BASE_URL}/api/v1/requests/${requestId}/cancel`, {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Cancel failed: ${err.error}`);
}
return res.json(); // { success: true }
}
MCP server
SendMeDocs also provides an MCP server for AI assistants like Claude Desktop and Cursor. It uses the same API key authentication. See the MCP docs for setup instructions.