Authentication
Every ContactFollowUp API call needs a bearer token. Tokens belong to a service account — never to a user directly.
Service accounts
A service account is a non-human identity in your workspace. It has a role (ADMIN, MANAGER, AGENT, or READONLY) and a set of issued tokens. The role decides what the token can do — it maps to the same capabilities the user roles use, so an AGENT-scoped token can write contacts but not manage users.
Create one in Settings → Service accounts. You get the plaintext token once, at issuance; we only store its hash. Lose it and you have to rotate.
Token format
Tokens look like hns_<saId>.<secret>. The prefix hns_ makes them grep-able in logs and the embedded service-account id lets the server look up the account before doing the constant-time secret comparison.
hns_clm5kqe5d000308l2hga09c2x.NDk2NWYzZGY4MmYyNzgyNzAzMzZkYTM4Sending a request
Set the header on every request:
GET /api/contacts HTTP/1.1
Host: app.contactfollowup.com
Authorization: Bearer hns_clm5kqe5d000308l2hga09c2x.NDk2NWYzZGY4MmYyNzgyNzAzMzZkYTM4
Accept: application/jsonexport HEARTHNOTE_TOKEN=hns_xxxxxxx.yyyyyyyyyyyyyyyy
curl https://app.contactfollowup.com/api/contacts \
-H "Authorization: Bearer $HEARTHNOTE_TOKEN"If the header is missing or wrong
Missing or malformed tokens return 401 Unauthorized. A token that was once valid but has since been revoked or expired also returns 401 — never 403. A valid token whose role lacks the capability returns 403 Forbidden.
Scopes (capabilities)
ContactFollowUp uses role-based capabilities rather than per-token scope flags. The role attached to the service account decides what it can do. Endpoint pages call out the capability they require; the full matrix:
| Capability | ADMIN | MANAGER | AGENT | READONLY |
|---|---|---|---|---|
| contacts.read | ✓ | ✓ | ✓ | ✓ |
| contacts.write | ✓ | ✓ | ✓ | |
| contacts.delete | ✓ | |||
| deals.read | ✓ | ✓ | ✓ | ✓ |
| deals.write | ✓ | ✓ | ✓ | |
| sequences.manage | ✓ | ✓ | ||
| campaigns.manage | ✓ | ✓ | ||
| audit.read | ✓ | ✓ | ||
| users.manage | ✓ |
Rotation
Tokens have no required expiry — leave them alone forever if you want — but you can set a TTL per token at issuance. To rotate without downtime: issue the new token, deploy your services with both old and new available, switch traffic, then revoke the old one.
# Settings → Service accounts → pick account → tokens → "Revoke"
# Revoked tokens 401 on every subsequent call; the revoke event is audited.Can I use a user session instead?
Sort of. The same routes accept the cookie session you'd get from logging in to the web app, because the UI uses these endpoints too. But it's not a stable contract — login cookies are rotated, CSRF-protected, and tied to a browser. For programmatic access always use a service account.