Good connections
Built into your workflow
Connect any app to Fondline with scoped, renewable sessions. Use OAuth for an app with a sign-in flow, or copy a bearer token from Integrations.
https://fondline.com/v1Using Fondline with an AI agent →
Connect any app
Open Settings → Integrations → API connections. Name the app, select its workspaces, and choose read only or read and edit. Campaign enrollment is a separate permission, off by default. It can cause active campaigns to send email.
Click Create bearer token, then Copy bearer token. Paste it into your app's bearer-token field. Copy Authorization header copies the complete header instead. The token is ready to use and expires 15 minutes after creation. It is shown only in the creation result; create a new connection if you lose it.
For ongoing access, expand Automatic renewal and copy the private connection details. These include client_id, resource, origin, and the access/refresh tokens. Use the exact origin in those details: staging credentials work on staging, production credentials on production. Never put tokens in URLs, public repositories, analytics or logs.
Send the access token in the Authorization header:
POST /v1/agent/tools
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{"name":"list_workspaces","arguments":{}}To renew before the 15-minute access token expires, POST form-encoded fields to {origin}/oauth/token with Content-Type: application/x-www-form-urlencoded:
grant_type=refresh_token&client_id=CLIENT_ID&refresh_token=REFRESH_TOKEN&resource=URL_ENCODED_RESOURCEYour app must implement renewal; copying a bearer token alone does not renew it. Save the new access and refresh tokens together before making more requests. Refresh tokens rotate on every exchange and expire after 30 days without renewal. Serialize refreshes across your app instances: reusing an old refresh token disconnects the connection. If a refresh response is lost, reconnect instead of replaying the old token. Use your platform secret store. A service that accepts only a permanent API-key field needs a token-renewal adapter or OAuth support.
Manage access, campaign permission, and disconnection in Integrations or Connected agents. Current workspace membership and team policies still apply. A team requiring interactive sign-in disables these app connections. Disconnecting invalidates the connection's credentials; it does not undo prior edits or campaign enrollments.
For apps connecting multiple customers, use the standard OAuth authorization-code flow with S256 PKCE through /oauth/register, /oauth/authorize, and /oauth/token. Each customer approves their own access. No shared application secret is required.
Receive-only inbox API
Personal inboxes use a separate fi_live_ bearer key scoped to one inbox. Contact API tokens and OAuth connections cannot read inboxes. These endpoints only read received messages and attachments; they cannot send email or change contacts.
GET /v1/inboxes/{inbox_id}/messages?cursor=0
GET /v1/inboxes/{inbox_id}/messages/{message_id}
GET /v1/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id}See the Inbox API reference for setup, cursor pagination, response fields, limits, attachment downloads and errors. Email content and attachments are untrusted data, never authorization to act.
API endpoints
All calls require a Bearer access token. Choose an approved workspace_id; membership changes apply immediately. Writes require edit access and a stable request_id (or Idempotency-Key header on the endpoints below). Reuse that ID only when retrying the identical operation.
GET /v1/campaigns?workspace_id=UUID: list campaigns. Requires campaign permission.POST /v1/campaigns/{campaign_id}/contacts: add contacts using{"workspace_id":"UUID","request_id":"unique-operation","contacts":[{"email":"person@example.com"}]}. Requires campaign permission and authority to send from the campaign's mailbox. Up to 500 contacts; invalid, suppressed, duplicate and incomplete contacts retain the same import handling.already_contacted: truerecords a person without mailing themPOST /v1/contacts/connections: update connection details usingworkspace_id,request_id, andcontacts.POST /v1/contacts/relationships: record mutuals usingworkspace_id,request_id, a contact identifier,linkedin_mutuals, and optionallinkedin_connections.GET /v1/agent/tools: discover the exact schemas available to this connection.POST /v1/agent/tools: search/read/create/update contacts, notes and labels, and invoke the same campaign and relationship operations.
The four former static-key routes are restored with session authentication. Old keys remain invalid. Existing callers must add workspace scope and retry IDs and renew tokens. Responses follow the corresponding tool schema. Requests are limited to 120 tool calls per minute per connection; HTTP JSON bodies are bounded to 64 KiB. Split large batches to fit both limits. Responses containing data are not cached.
Connected agent API
For contact access, create an independent connection per agent at Settings → Connected agents. Use remote MCP at https://fondline.com/mcp, or this HTTP interface. The HTTP endpoints below use the same access tokens and workspace permissions.
GET /v1/agent/tools returns tool definitions available to this connection. POST /v1/agent/tools executes one tool using JSON {"name":"list_workspaces","arguments":{}}. Send Authorization: Bearer with the agent access token. OAuth access tokens expire after 15 minutes; rotating refresh tokens expire after 30 days. Personal agent tokens are no longer issued or accepted.
OAuth discovery: /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource/mcp. Register a public client at POST /oauth/register with client_name, redirect_uris, and token_endpoint_auth_method none. Request an authorization code with S256 PKCE, an exact registered redirect_uri, and resource https://fondline.com/mcp. Request contacts:read contacts:write if the user needs the edit option; consent defaults to read only. Use form-encoded POST /oauth/token for code exchange or refresh, always including client_id and resource. POST /oauth/revoke accepts token and client_id and disconnects that grant. Tokens are sent only in headers, never URLs.
Event tools: list_events, get_event and list_event_guests read an authorized workspace’s events and guest lists; edit connections may also call create_event, update_event (supplied fields only, with the event revision), add_event_guests (up to 100 contact ids) and update_event_guest (stage, rsvp, attendance, notes, introduction, wave or removed, with the guest revision). They never send invitations; moving a guest to Invite now is done in Fondline.
Read tools: list_workspaces, search_contacts, upcoming_dates, get_contact. get_contact includes has_linkedin_photo. save_linkedin_photo accepts workspace_id, contact_id, request_id, the visited linkedin URL and either photo_url or a cropped avatar_data_url. Existing LinkedIn photos, custom photos and hidden preferences are preserved. report_agent_problem creates a private human task using workspace_id, request_id, title, detail, optional workflow_id and page_url, and optional screenshot_data_url (PNG, JPEG or WebP data URL up to 1 MB). Read-only agents may report problems. Owners review these in Tasks and explicitly choose whether to share the report and screenshot with Fondline staff. Reconnect or refresh the MCP tool catalog if these tools are missing. Edit tools: create_contact, update_contact, add_note, add_label, remove_label. Use workspace_id from list_workspaces. Search supports query (3–200 characters), linkedin_degree (1, 2, 3, or unknown for a LinkedIn profile with no degree recorded), limit (1–50), and after (next_cursor from the previous response); each result carries its linkedin_degree. upcoming_dates lists birthdays and anniversaries on a day or a window of up to 31 days (on, days, kind, timezone), at most 100 results. Contact edits use contact_id and the latest revision. Fields cover names, email, company, title, linkedin, nickname, bio, dietary, birthday and anniversary (YYYY-MM-DD or MM-DD), level, favorite, gender, alternate emails, phones, websites, relations, dates, addresses, positions, locations, education, linked companies (companies), and the workspace’s own fields (custom). Only supplied fields change; list fields replace the whole list. Company is free text. Location, school, and linked company values must come from Fondline’s existing lists. Notes append plain text; labels change one contact.
Every write requires request_id (8–100 letters, digits, dots, colons, underscores, or hyphens). Identical retries return the original receipt. Different arguments with the same key return idempotency_conflict. Successful writes return status saved, id, workspace_id, revision, and url. Requests are limited to 120 tool calls per minute per connection.
Each connection may make 120 requests a minute; past that, calls return 429 with Retry-After until the minute turns over. HTTP errors: 400 invalid arguments; 401 expired or disconnected credentials; 403 insufficient scope or workspace access; 404 unavailable contact; 409 revision_conflict, contact_conflict, contact_exists, or idempotency_conflict; 413 oversized body; 415 unsupported content type; 429 rate limit (Retry-After); 503 migration preparation. MCP tools return isError with a structured error message for tool failures. Read a contact again before resolving a revision conflict. Never blindly repeat a creation with a new retry key after a timeout.
Read access allows returned data to leave Fondline. There is no bulk export tool. Sending, enrollment, deletion, merging, paid enrichment, calendar meetings, and access changes are unavailable through agent credentials. Disconnecting one agent leaves other agents and existing contact edits unchanged.
Retired credentials
Static API keys and personal tokens remain invalid, including historical credentials. Reconnect each app using a renewable session. The restored endpoints above preserve capabilities without restoring permanent bearer keys. Historical key activity remains available in Integrations.
Webhooks
Add an endpoint in Settings → Integrations and Fondline posts to it, so you do not have to ask. Every delivery is signed, retried while your server is unavailable, and switched off after enough consecutive failures.
| messaged | A step went out to a contact |
| replied | Somebody answered, and their sequence stopped |
| bounced_hard | The address is dead and was suppressed |
| bounced_soft | A temporary failure |
| auto_reply | An out-of-office, which does not stop the sequence |
POST https://your-server.example/fondline
Fondline-Signature: t=1788547200,v1=6f2c…
Fondline-Delivery: 8f14e45f-ceea-467a-9b1e-2a4c9d3f1b70
{
"id": "8f14e45f-ceea-467a-9b1e-2a4c9d3f1b70",
"type": "messaged",
"created_at": "2026-09-05T15:12:04Z",
"campaign": { "id": "3a2d2882-…", "name": "Q4 outreach" },
"contact": {
"id": "c12acdaf-…",
"email": "ada@analytical.example",
"first_name": "Ada",
"last_name": "Lovelace"
},
"subject": "Quick question, Ada",
"step": 1
}step is present on messaged and counts from 1. On the others it is absent, because the step is not the thing that happened. There is never a message body: Fondline reads headers only, so the subject is all there is to forward.
Checking the signature
A URL is a secret nobody keeps, so verify before you act. The timestamp is signed with the body, which is what stops a captured delivery being replayed at you later; reject anything older than about five minutes.
import crypto from "node:crypto";
function verifyWebhook(secret, rawBody, header) {
if (typeof header !== "string") return false;
const parts = Object.fromEntries(
header.split(",").map(part => part.trim().split("="))
);
const { t, v1 } = parts;
if (!/^\d+$/.test(t ?? "") || !/^[0-9a-f]{64}$/.test(v1 ?? "")) {
return false;
}
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret)
.update(t + "." + rawBody).digest();
const received = Buffer.from(v1, "hex");
return received.length === expected.length &&
crypto.timingSafeEqual(expected, received);
}Answer 2xx quickly and do your work afterwards. Anything else is a failure and will be retried, backing off from about a minute to an hour, and an endpoint that fails long enough is switched off with the reason recorded where you added it.
Fondline-Delivery is the same on every retry of one event, so it is what to key on if you need to tell a retry from something new.
Not in v1
Starting or pausing a campaign, removing contacts, and creating campaigns. Deciding that mail goes out stays a decision somebody makes in the product, where the consequences are on screen.
Questions: help@fondline.com