Skip to content

Integrations

The same companies, in your own system.

For your developer, your CRM builder or your email platform. On every plan, including the free one. Ready-made integrations with Slack, Teams, HubSpot, Pipedrive and Teamleader are coming; until then it goes through the webhook below.

Coming

We are building these integrations now, in this order. Until they are there, it runs through the webhook below, which delivers the same content.

The order is by usage and fit, not by what an integration earns us.

For your developer

The key, the five routes and the webhook, for whoever builds it themselves or uses an automation platform.

Everything you see in swungby you can also fetch. And what you put in, contacts and links, comes back as certain recognitions. This page is the whole explanation; there is no separate manual.

A key

Create a key under Integrations in your account. Choose read only if the system only needs to look, or read and write if it must create contacts and links. The key is shown once. Send it with every call: Log in

Authorization: Bearer sk_live_…

Every call goes to https://swungby.com/api/v1/ and returns JSON.

What a response looks like

Always the same envelope: data and meta on success, errors and meta on failure. meta always carries a request_id; that is the number you email us when something is off, and we find it straight away.

{
  "data": { "client": { "id": 5, "name": "Dutch Leads" } },
  "meta": { "request_id": "8a3f…" }
}

GET /api/v1/meWho am I

The client and sites behind this key, what the key may do, and this month's usage. The first call everyone makes.

curl https://swungby.com/api/v1/me \
  -H "Authorization: Bearer sk_live_…"

GET /api/v1/companiesThe companies that visited

The same list as your screen: latest visit first, with per company how certain we are (certain, probable, uncertain), what we recognised it by, the pages viewed, the state, who has it, the contacts with their source, and through which channel the latest visit came in (last_channel: search, social, email, campaign, referral or direct, with the name in last_channel_name). Filter with status=open or all.

curl "https://swungby.com/api/v1/companies?limit=50&status=open" \
  -H "Authorization: Bearer sk_live_…"

GET /api/v1/visitsIndividual visits

Every visit by a recognised company, with the pages, the time and the channel it came in through (channel and channel_name). Filter on one company with company_id, or on a moment with since.

curl "https://swungby.com/api/v1/visits?company_id=cmp_4821&since=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer sk_live_…"

POST /api/v1/contactsContacts in

Your contacts, at most a thousand per call. The same rules as the upload in the screen: a real email address, lower case, and only linked to companies we already know. A wrong address gets a pointer to the row.

curl -X POST https://swungby.com/api/v1/contacts \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "contacts": [ { "email": "jan@acme.nl", "name": "Jan", "job_title": "Purchasing" } ] }'

POST /api/v1/click-tokensTokenised links for your emails

Per recipient a link to a page on your site. Whoever clicks it we recognise with certainty, also from a phone or from home. Unknown addresses are saved as contacts right away, so one call is enough. The campaign name travels inside the token and is not visible in the link.

curl -X POST https://swungby.com/api/v1/click-tokens \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "destination": "https://yoursite.nl/offer", "campaign": "autumn",
        "contacts": [ { "email": "jan@acme.nl" } ] }'

Paging

Lists return at most 200 rows at a time, 25 by default. When meta.pagination has has_more set to true, send next_starting_after as starting_after for the next page. There is no total: that costs a count over a list that only grows, and you do not need it to page through.

When something goes wrong

An error always has a code for your program and a sentence for you. For a wrong field there is a pointer, such as /contacts/12/email.

missing_token
No key sent.
invalid_token
We do not know this key.
token_revoked
The key has been revoked.
insufficient_scope
This key may only read.
validation_failed
A field is wrong; look at the pointer.
not_found
This does not exist, or is not yours. We deliberately do not tell those apart.
rate_limit_exceeded
Too fast in a row; wait the number of seconds in Retry-After.

How fast you may go

Twenty calls per second and six hundred per minute per account. Every response says in X-RateLimit-Remaining how many you have left; that number is approximate, because it is kept per server. Plenty to fetch your whole list every morning.

Webhooks

A message to your address as soon as a new company is recognised: once per company, with the same content as GET /companies. Create it under Integrations or via POST /webhooks; you get the secret once. Every delivery carries a signature in the X-Signature header, and this is how you check it:

// Node.js
const [t, v1] = req.headers["x-signature"].split(",").map((d) => d.split("=")[1]);
const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
const valid = expected === v1 && Math.abs(Date.now() / 1000 - Number(t)) < 300;

Answer with a 2xx within ten seconds. If that fails we retry after 30 seconds, 2 minutes, 10 minutes, an hour, six hours and a day. After seven tries the webhook is marked as struggling and the owner gets an email; after 72 hours without a single success we switch it off, and one button or POST /webhooks/{id}/resume turns it back on. Every delivery with every attempt is in GET /webhooks/{id}/deliveries, and a failed delivery is resent with one button. You recognise duplicate deliveries by their id; keep what you already had.

For your AI assistant

Working with Claude Code, Codex or another assistant? Give it this address and your key, and say what you want: fetch this week's companies, load contacts, create links for your email, or set up a webhook. The same permissions and the same rate as the API.

{
  "mcpServers": {
    "swungby": {
      "url": "https://swungby.com/api/mcp",
      "headers": { "Authorization": "Bearer sk_live_…" }
    }
  }
}

For example: “Fetch the companies that looked at my pricing this week and add them as a note in my CRM.”

Version

This is version 1, in the path as /v1. Fields get added without breaking anything; should anything ever disappear, a /v2 appears next to it and /v1 keeps working for at least a year. Ids carry a prefix (cmp_, vst_, key_) so you never mix them up.