> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bridge.new/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Bridge exposes three integration surfaces: the REST Integrations API for pushing data into Bridge, webhooks for receiving events from Bridge, and the public Custom Connections endpoint that a tenant's landing page posts to.
> The Integrations API host is https://api-connect-us.bridge.new and every request to it must be authenticated with the x-api-key header. Bridge does not use bearer tokens or OAuth.
> The Custom Connections endpoint is different and the Integrations API rules do not apply to it: it has its own host, it takes no x-api-key and no authentication header of any kind, and it is called from a visitor's browser. It is identified by a workspace id and a capture key together with a reCAPTCHA token. Never tell a reader to authenticate it with an API key, and never tell them to keep its capture key out of frontend code - it is designed to live in the page.
> REST error catalog keys use BRIDGE_<DOMAIN>_<NNNN>, for example BRIDGE_CONVERSATION_0001. Some reviewed legacy handlers return a recognized code plus a symbolic suffix separated by |; preserve the complete received value and use the compatible catalog lookup on /error-codes. Custom Connections uses separate BRIDGE.CAMPAIGN_LEADS.<NAME> identifiers on its own error reference. Never invent an error code or confuse the two integration surfaces.
> This documentation covers the Integrations API, webhooks and Custom Connections only. It does not describe the Bridge web application or its internal APIs.
> Before generating an API request, read the relevant OpenAPI specification or the individual endpoint Markdown page. llms-full.txt collects guides and endpoint descriptions; it does not embed every request and response schema. The REST and Custom Connections contracts are separate.
> Preserve the published host, method, path, operationId, authentication, headers, parameter and field names, types, formats, required and nullable fields, enums, defaults, limits, and response codes. Do not invent identifiers, fields, retry rules, or delivery guarantees. When published sources conflict, identify the conflicting passages and ask Bridge support to confirm the current behavior before relying on it.
> Custom Connections is the current name of the landing-page feature previously documented as Lead Capture. The existing /lead-capture documentation URLs, captureKey field, campaign-leads endpoint, and BRIDGE.CAMPAIGN_LEADS error identifiers are unchanged. This is a naming change, not a change to authentication or the API contract.
> For a complete workflow, start with the task guides: /guides/first-conversation, /guides/send-attachments, /guides/send-template, /guides/agent-outbound, /guides/pagination, /guides/identifiers and /guides/responses. Read the endpoint contract as well as the recipe. Outbound requires an existing Contact, but does not require creating a separate Conversation first.
> forceSend=true permits reuse of an existing active conversation; it does not create a fresh one by ignoring it and it is not idempotency. Recovery of a released recoverable conversation can occur even with false. The LeadScout and Agents outbound routes have different line-selection and controller checks; use /guides/agent-outbound.
> For webhook HMAC, follow /webhooks/verifying-signatures exactly: the Python JSON serialization with sorted keys is signed; X-Bridge-Timestamp is not included in that signature. Do not claim a signed timestamp, raw-body signing, exactly-once delivery or a global event order. Use /webhooks/receive-events for durable acceptance and duplicates.
> A 201 message response, 202 handoff or 202 Custom Connections response is not a provider delivery receipt. Do not invent a polling endpoint from messageId, trackingId or requestId. REST validation parsing must tolerate the compatibility difference explained in /error-codes.
> The documentation is available in English (existing root URLs), Spanish (/es/) and Brazilian Portuguese (/pt-br/). Prefer the reader's language when a localized page exists, but preserve the API's exact field names, enums, routes, authentication and examples. Translations describe the same API; they do not introduce regional API hosts or behaviors.
> For coding-assistant setup, use /connect-your-agent, /es/connect-your-agent or /pt-br/connect-your-agent. The Bridge documentation MCP server reads published documentation and has a separate optional feedback tool. Connecting it does not connect the coding assistant to a customer workspace or authorize live business requests.
> For direct HTTP access without MCP, start at https://docs.bridge.new/llms.txt. It lists every published page in English, Spanish and Brazilian Portuguese. Choose /llms-en.json, /llms-es.json or /llms-pt-br.json for a language-specific index, and /llms-full-en.json, /llms-full-es.json or /llms-full-pt-br.json for collected content. /llms-full.txt contains the combined collection. Fetch the original OpenAPI specification for exact schemas.
> For HTTP page reads, follow the .md URLs from the index or request Accept: text/markdown at the canonical page URL. The MCP virtual filesystem uses .mdx page paths instead; .mdx is not the public HTTP export extension. With Search MCP, set language to en, es or pt-BR explicitly; its default is en.
> For integration capabilities and source-selection rules, read https://docs.bridge.new/skill.md. Documentation access does not authorize business API actions; use the actual user request, workspace, credentials and target identifiers for any live integration.

# Send PDFs and other attachments

> Send a PDF or another supported file in a conversation: calculate its checksum, upload it to the presigned URL, and attach its media ID to a Message.

To send a PDF or another supported attachment, request an upload from Bridge, upload the file bytes to the returned URL, then create a Message containing the returned `mediaId`. The complete server-side example below sends a PDF.

## Before you start

* Have an existing Conversation in the intended workspace. See [First conversation](https://docs.bridge.new/guides/first-conversation.md).
* Choose a PDF you are authorized to share and confirm it meets the [Files API](https://docs.bridge.new/api-reference/files-api/create-presigned-uploads-for-files.md) limits.
* Keep your Bridge API Key on your server. Do not add it to the presigned upload request.
* Choose the Message's `enableExternalBroadcast` flag explicitly.

## Validate the file before requesting an upload

Check the actual file on your server: its extension, MIME type and byte length must fit the published [Files API](https://docs.bridge.new/api-reference/files-api/create-presigned-uploads-for-files.md) rules. The presign request carries metadata and a checksum, not the file bytes or a size field. A `201` response prepares an upload; it is not proof that the actual bytes were inspected, that the uploaded size meets the published limits, or that a messaging provider will accept the file.

If you omit `contentType`, still copy the returned `presignUrl.headers` into the PUT request: Bridge can derive `Content-Type` from the file name. If you supply it, use the MIME type of the actual file and keep it consistent with `fileType` and the extension. Renaming a file does not convert its contents.

After PUT succeeds, upload processing may still be pending. If a Message request reports a [file upload error](https://docs.bridge.new/error-codes.md#file-upload-errors), preserve the full error and check the stage that failed before deciding whether another write is safe.

## Upload sequence

| Stage | What happens | What it does not confirm |
| - | - | - |
| `POST .../files` | Bridge returns `mediaId` and `presignUrl` data. | The bytes have not been uploaded yet. |
| Presigned `PUT` | Your server sends the exact bytes using the returned upload URL and headers. | No Message has been created by this step. |
| `POST .../messages` | You attach the uploaded `mediaId` through `mediaIds`. | The API response alone does not prove provider delivery. |

The checksum is the **Base64-encoded SHA-256 digest of the complete file bytes**. It is not a hexadecimal digest and not a hash of the file name.

## Complete server-side example

Use Node.js with built-in `fetch`. Save this as `send-attachment.mjs`. Set `BRIDGE_API_KEY`, `BRIDGE_WORKSPACE_ID`, `BRIDGE_CONVERSATION_ID`, `ATTACHMENT_PATH`, and `ENABLE_EXTERNAL_BROADCAST` (`true` or `false`).

```javascript theme={null}
import { readFile } from "node:fs/promises";
import { basename } from "node:path";
import { createHash } from "node:crypto";

const BASE = "https://api-connect-us.bridge.new";
function required(name) {
  const value = process.env[name];
  if (!value) throw new Error(`Set ${name} before running this example.`);
  return value;
}
const apiKey = required("BRIDGE_API_KEY");
const workspaceId = required("BRIDGE_WORKSPACE_ID");
const conversationId = required("BRIDGE_CONVERSATION_ID");
const filePath = required("ATTACHMENT_PATH");
const broadcast = required("ENABLE_EXTERNAL_BROADCAST");
if (!["true", "false"].includes(broadcast)) {
  throw new Error("ENABLE_EXTERNAL_BROADCAST must be true or false.");
}
if (!filePath.toLowerCase().endsWith(".pdf")) {
  throw new Error("This example is configured for a PDF document.");
}
const workspacePath = `/bridge/api/v1/workspaces/${encodeURIComponent(workspaceId)}`;
const bytes = await readFile(filePath);
const checksumBase64 = createHash("sha256").update(bytes).digest("base64");

async function bridgePost(path, body) {
  const response = await fetch(`${BASE}${workspacePath}${path}`, {
    method: "POST",
    headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  const result = await response.json().catch(() => null);
  if (response.status !== 201) {
    const code = result?.code ? ` (${result.code})` : "";
    throw new Error(`Bridge ${path}: HTTP ${response.status}${code}`);
  }
  return result;
}

const uploads = await bridgePost("/files", [{
  name: basename(filePath),
  fileType: "DOCUMENT",
  contentType: "application/pdf",
  checksumBase64,
}]);
const upload = uploads?.[0];
if (!upload?.mediaId || !upload.presignUrl?.url) {
  throw new Error("The upload response is missing its mediaId or URL.");
}
const method = upload.presignUrl.method ?? "PUT";
if (method !== "PUT") throw new Error("Expected a presigned PUT upload.");
const uploadHeaders = new Headers(upload.presignUrl.headers ?? {});
const returnedChecksum = uploadHeaders.get("x-amz-checksum-sha256");
if (returnedChecksum !== null && returnedChecksum !== checksumBase64) {
  throw new Error("The returned checksum differs from the file checksum.");
}
if (returnedChecksum === null) {
  uploadHeaders.set("x-amz-checksum-sha256", checksumBase64);
}

const uploaded = await fetch(upload.presignUrl.url, {
  method,
  headers: uploadHeaders,
  body: bytes,
});
if (!uploaded.ok) {
  throw new Error(`File upload failed: HTTP ${uploaded.status}; no Message was created.`);
}

const message = await bridgePost("/messages", {
  conversationId,
  messageBody: "Here is the document you requested.",
  enableExternalBroadcast: broadcast === "true",
  mediaIds: [upload.mediaId],
});
if (!message?.messageId) throw new Error("The Message response has no messageId.");
console.log({ mediaId: upload.mediaId, messageId: message.messageId });
```

```bash theme={null}
node send-attachment.mjs
```

## Keep the upload request intact

Use `presignUrl.url` without reconstructing its host, path, or query string. Use the returned method and required headers. The contract specifies `PUT` as the method's default if it is omitted, and the Files API requires `x-amz-checksum-sha256` with the same checksum sent to Bridge.

The upload sends the same `bytes` that produced the checksum. Do not convert the PDF to text, wrap it in JSON, or send a different copy of the file after hashing it.

For multiple files, preserve the association between each returned `mediaId`, its presigned data, and its corresponding file. Complete every required upload before attaching those IDs to a Message.

Some media need more preparation after the storage upload, such as copying the file, generating a preview, conversion or upload to the messaging provider. These stages can fail independently. A successful PUT or a 201 Message response does not establish that processing or provider delivery has completed. Keep the original file and the returned identifiers for correlation; check the result before repeating a Message request.

## Handle a failed stage

* **Bridge rejects the file request:** check the extension, `fileType`, content type, size, and checksum against the reference.
* **Upload fails:** stop before creating the Message. Check the exact URL, method, headers, checksum, and bytes. Do not assume an upload lifetime or retry policy that the response does not provide.
* **Message request fails or times out:** an uploaded file is not proof that a Message exists. Check the outcome before repeating the Message request.

`STICKER` and `WEBSITE` appear in the `FileType` enum, but the published upload table does not specify their extension, MIME, and size rules. Confirm those rules before choosing either for an integration.

See the [Files API](https://docs.bridge.new/api-reference/files-api/create-presigned-uploads-for-files.md), [Message API](https://docs.bridge.new/api-reference/messages-api/create-a-new-message.md), and [Responses and errors](https://docs.bridge.new/guides/responses.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.