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.
Works today
With these tools it works now, without building anything.
- SlackReady-madeA message in the channel of your choice as soon as a new company is recognised.
- Microsoft TeamsReady-madeA card in your Teams channel with the company, the certainty and what they looked at.
- GoHighLevelReady-madeThe company, and the person if they clicked, as a contact in GoHighLevel, ready for your workflow.
- ClayReady-madeThe company as a row in your Clay table, with the domain first so Clay can enrich it.
- Google SheetsReady-madeEvery recognised company as a new row in a Google spreadsheet.
- InstantlyThrough the link in your emailWhoever clicks a link in your Instantly campaign, you recognise by name, even at home and on their phone.
- SmartleadThrough the link in your emailWhoever clicks a link in your Smartlead campaign, you recognise by name.
- lemlistThrough the link in your emailWhoever clicks a link in your lemlist campaign, you recognise by name.
- EmailBisonThrough the link in your emailWhoever clicks a link in your EmailBison campaign, you recognise by name.
- MailchimpThrough the link in your emailWhoever clicks a link in your Mailchimp newsletter, you recognise by name.
- ZapierThrough the webhookEvery newly recognised company as a trigger in Zapier, on to thousands of other apps.
- MakeThrough the webhookEvery newly recognised company as the start of a scenario in Make.
- n8nThrough the webhookEvery newly recognised company as the start of a workflow in n8n, also when you host it yourself.
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.
- HubSpotWith one buttonThe company in HubSpot, with every visit as a note and the person added if they clicked themselves.
- PipedriveWith one buttonThe organisation in Pipedrive, with every visit as an activity on the organisation.
- Teamleader FocusWith one buttonThe company as a contact in Teamleader, with the visit as a note.
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.