Skip to content

Examples use fictional data. Every company, person, address and count in them is invented, and their domains use the reserved .example TLD.

Getting started

Authentication

Two credential types, one header each. Session tokens are for humans in a browser; API keys are for your servers. Everything else on this page is a consequence of that split.

Bearer JWT vs. x-api-key

A session token is a short-lived HS256 JWT carrying the user, workspace and role. An API key is a workspace credential with no user attached — it never expires on its own and is revoked explicitly. In this deployment session tokens stay inside the web app (httpOnly cookies); integrations use API keys. If both are present, the session is used.

Bearer JWTx-api-key
Headerauthorization: Bearer …x-api-key: sk_live_…
Issued byPOST /v1/auth/login or /signupPOST /v1/api-keys
Lifetime30 minutes, refreshed with a 30-day refresh tokenUntil revoked
IdentityUser + workspace + roleWorkspace only, member role: cannot invite people or change workspace settings
Use forBrowser sessions, dashboards, anything a person clicksServers, cron jobs, data pipelines
Rate limit300 requests / minute300 requests / minute
Both forms
# server-to-server
curl "https://beamro.com/api/v1/me" -H "x-api-key: $SALVO_API_KEY"
POST/v1/auth/signup

Create a workspace

Registers the user, creates their organisation, grants the starter sending wallet and returns a signed session in one round trip.

No auth requiredNo credit charge

Body parameters

FieldTypeDescription
emailreqstringWork email. Unique across Salvo.
passwordreqstringMinimum 8 characters. Stored only as a bcrypt hash.
namestringDisplay name of the person signing up.
org_namestringWorkspace name. Defaults to the email domain when omitted.
invite_tokenstringThe invite parameter of a join link. Joins that workspace instead of creating one; must match the invited email.
curl -X POST "https://beamro.com/api/v1/auth/signup" \
  -H "content-type: application/json" \
  -d '{
    "email": "priya@harlowmeadow.example",
    "password": "a-long-passphrase",
    "name": "Priya Whitcombe",
    "org_name": "Harlow Meadow Events"
  }'
200 response
{
  "user": {
    "id": "usr_7d2m41ka9xq0zv3b8n",
    "email": "priya@harlowmeadow.example",
    "name": "Priya Whitcombe",
    "avatar_url": null,
    "email_verified": false,
    "created_at": "2026-08-31T11:04:02.000Z"
  },
  "org": {
    "id": "org_4k1p8w2ne6ts0ybc7j",
    "name": "Harlow Meadow Events",
    "slug": "harlow-meadow-events",
    "plan": "free",
    "credit_balance": 0,
    "email_balance_cents": 3000,
    "trial_granted": true,
    "created_at": "2026-08-31T11:04:02.000Z"
  },
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfN2QybTQxa2E5eHEwenYzYjhuIiwib3JnIjoi…",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfN2QybTQxa2E5eHEwenYzYjhuIiwidHlwIjoi…"
}
Duplicate emails come back as 409 conflict. The starter grant of $30 lands on email_balance_cents, not on data credits — see credits and metering.
POST/v1/auth/login

Exchange credentials for a session

Returns the same envelope as signup. Wrong credentials are a 401 with no hint about which half was wrong.

No auth requiredNo credit charge

Body parameters

FieldTypeDescription
emailreqstringRegistered email address.
passwordreqstringPlaintext password, compared against the stored hash.
curl -X POST "https://beamro.com/api/v1/auth/login" \
  -H "content-type: application/json" \
  -d '{
    "email": "priya@harlowmeadow.example",
    "password": "a-long-passphrase"
  }'
200 response
{
  "user": {
    "id": "usr_7d2m41ka9xq0zv3b8n",
    "email": "priya@harlowmeadow.example",
    "name": "Priya Whitcombe",
    "avatar_url": null,
    "email_verified": false,
    "created_at": "2026-08-31T11:04:02.000Z"
  },
  "org": {
    "id": "org_4k1p8w2ne6ts0ybc7j",
    "name": "Harlow Meadow Events",
    "slug": "harlow-meadow-events",
    "plan": "free",
    "credit_balance": 0,
    "email_balance_cents": 3000,
    "trial_granted": true,
    "created_at": "2026-08-31T11:04:02.000Z"
  },
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"
}
POST/v1/auth/refresh

Rotate an expired access token

Trades a refresh token for a fresh pair. The old refresh token stops working the moment the new one is issued.

No auth requiredNo credit charge

Body parameters

FieldTypeDescription
refresh_tokenreqstringThe refresh token from signup, login or a previous refresh.
curl -X POST "https://beamro.com/api/v1/auth/refresh" \
  -H "content-type: application/json" \
  -d '{
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"
  }'
200 response
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"
}
POST/v1/auth/logout

End the current session

Invalidates the refresh token behind the presented session. API keys are unaffected — revoke those explicitly.

Session token onlyNo credit charge
curl -X POST "https://beamro.com/api/v1/auth/logout" \
  -H "authorization: Bearer $SALVO_ACCESS_TOKEN"
200 response
{
  "ok": true
}
GET/v1/me

Who am I

Resolves whichever credential you sent into a user, a workspace and a role. The cheapest way to check that a key still works.

API key or sessionNo credit charge
curl "https://beamro.com/api/v1/me" \
  -H "x-api-key: $SALVO_API_KEY"
200 response
{
  "user": {
    "id": "usr_7d2m41ka9xq0zv3b8n",
    "email": "priya@harlowmeadow.example",
    "name": "Priya Whitcombe",
    "avatar_url": null,
    "email_verified": false,
    "created_at": "2026-08-31T11:04:02.000Z"
  },
  "org": {
    "id": "org_4k1p8w2ne6ts0ybc7j",
    "name": "Harlow Meadow Events",
    "slug": "harlow-meadow-events",
    "plan": "growth",
    "credit_balance": 38420,
    "email_balance_cents": 4180,
    "trial_granted": true,
    "created_at": "2026-08-31T11:04:02.000Z"
  },
  "role": "owner"
}
Requests authenticated with an API key report role: "owner" and a synthetic service user.

Unauthenticated endpoints

A small public surface powers the marketing tools — company and people search, lookalike lookup, segment and geography stats. It takes no credential, returns capped result sets, never exposes contact details, and is limited to 30 requests an hour per IP. Everything under the public prefix mirrors an authenticated equivalent, so promoting a prototype to production is a base-path change and a header.

Public search, no credential
curl -X POST "https://beamro.com/api/v1/public/search/companies" \
  -H "content-type: application/json" \
  -d '{
    "filters": {
      "q": "boutique hotels in Lisbon",
      "limit": 5
    }
  }'