Lynkist Developers

Authentication

API keys, scopes, IP allowlists, and rotation for the Lynkist Public API.

The Lynkist Public API uses bearer-token authentication. Every request must include an Authorization: Bearer <api_key> header.

API key format

API keys always have four underscore-separated parts:

lk_sk_<env>_<40-character-hex-secret>
       │     │
       │     └── 40 random hex characters
       └── live | test
TypePrefixUse
Livelk_sk_live_…Production traffic
Testlk_sk_test_…A label for keys you use in dev/CI

There is no separate sandbox environment — live and test keys hit the same API and the same data. The test prefix is purely an organisational label so you can tell keys apart and revoke them independently; it does not route to an isolated environment.

Treat API keys like passwords. Do not commit them to source control, ship them in client-side bundles, log them, or paste them into chat. Store them in environment variables or a secrets manager.

Creating a key

API key creation is dashboard-only (JWT-authenticated). The Public API itself cannot mint keys.

  1. In the dashboard, open Settings → API keys. The mount is /{tenant}/api/v1/settings/api-keys.
  2. Click Create new key.
  3. Pick a name (visible to your team), the environment (live or test) and one or more scopes (see Scopes below).
  4. Copy the secret immediately — it is shown once and never again.

The response contains the metadata plus the plain-text secret:

{
  "api_key": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "name": "Server-side messaging",
    "key_prefix": "lk_sk_live_a",
    "scopes": ["messages:send", "templates:read"],
    "environment": "live",
    "is_active": true,
    "created_at": "2026-05-31T08:00:00Z",
    "last_used_at": null
  },
  "secret": "lk_sk_live_a1b2c3d4…"
}

Only the key_prefix (the first 12 characters) is stored on Lynkist's side after creation; the secret is irrecoverable. If you lose it, rotate the key.

Making authenticated requests

Pass the secret in the Authorization header.

curl https://lynkist.io/api/v1/public/contacts \
  -H "Authorization: Bearer lk_sk_live_..."

If the key is missing, invalid, expired, or revoked, the API returns 401 Unauthorized. If the key is valid but lacks the scope for the route, you get 403 Forbidden — see Errors for the response shape.

Scopes

A key carries a list of resource:action strings. Lynkist enforces the relevant scope on every endpoint; a request missing the required scope is rejected with 403. The wildcard scope * satisfies every check.

Full catalogue (12 scopes):

ScopeAllows
contacts:readList, search, get contacts
contacts:writeCreate, update, bulk-create, archive contacts
templates:readList, get approved templates
templates:writeCreate and submit templates for approval
messages:sendSend template messages
media:readList, get media
media:writeUpload media, create resumable upload sessions, delete media
webhooks:readList endpoints, list deliveries, view metrics
webhooks:writeCreate / update / delete endpoints, rotate secret, retry, send tests
campaigns:readList, get, preview, metrics, errors
campaigns:writeCreate, set audience, render preview, refresh metrics
campaigns:sendSend, schedule, pause, resume, stop

Presets

When creating a key in the dashboard you can pick one of four presets instead of hand-selecting scopes:

PresetScopes
read_onlycontacts:read, templates:read, media:read, webhooks:read, campaigns:read
messagingcontacts:read, templates:read, media:write, messages:send
campaignscontacts:read, templates:read, media:read, campaigns:read, campaigns:write, campaigns:send
full_access* (wildcard — every scope, current and future)

Scope-denial response

When a key is missing a required scope, the response body explicitly lists what was required versus what was held — useful for surfacing "you need to ask your admin for X" prompts inside integrations:

{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "Missing required scope(s): campaigns:send",
    "required_permissions": ["campaigns:send"],
    "current_permissions": ["campaigns:read", "campaigns:write"]
  },
  "meta": { "request_id": "req_…", "timestamp": "…" }
}

IP allowlists & expiry

A key can be locked to a set of source IPs or CIDR ranges, and/or given an expiry. When an allowlist is set, requests from any other address return 403 (Request IP not in allowlist), even if the key and scopes are otherwise valid; an expired key returns 401 API key has expired.

Allowlist entries can be either plain IPs (203.0.113.7) or CIDR (203.0.113.0/24).

The key-creation request currently accepts only name, scopes, and environment. IP allowlists and expiry are enforced when present, but are not yet settable through the create API — contact Lynkist if you need them provisioned on a key. Self-service configuration is on the roadmap; track Changelog.

Rotating a key

  1. From the dashboard, click Rotate on the existing key. Lynkist mints a new secret with the same scopes and environment and returns it once.
  2. Deploy the new secret to every service that uses it.
  3. The old key is revoked immediately as part of the rotate operation — there is no grace period.

The rotate endpoint is POST /{tenant}/api/v1/settings/api-keys/{key_id}/rotate. Plain revoke (without minting a replacement) is POST .../revoke.

Best practices

  • One key per service. Issue a distinct key for each downstream service so you can revoke one without taking another offline.
  • Minimum scopes. A campaign-only service does not need messages:send. Start narrow.
  • IP-lock long-lived keys. Anything running on a known set of servers should be allowlisted.
  • Watch last_used_at. The dashboard shows it on every key. Revoke anything idle for 90+ days.
  • Pair with Idempotency-Key. See API Reference for safe-retry semantics.

OAuth-style delegated access (for building third-party integrations against multiple tenants) is not available yet. Today, all programmatic access is via per-tenant bearer keys.

Dashboard login lockout

The API-key auth above is separate from dashboard sign-in (the email + password login that issues the JWT used to manage keys, templates, and campaigns). To blunt brute-force attempts, dashboard login enforces an account lockout:

  • After 3 consecutive failed password attempts, the account is locked.
  • While locked, POST /api/v1/login/access-token returns 423 Locked — even if the correct password is supplied — until the account is explicitly unlocked. (This is distinct from the IP-based 429 throttle, which is temporary and clears on its own.)
  • A locked account is re-opened in one of two ways, both of which reset the failed-attempt counter:
    1. Phone OTP — request a one-time code to the account's registered phone (POST /api/v1/login/unlock/phone/request), then verify it (POST /api/v1/login/unlock/phone/verify).
    2. Password reset — completing the normal password-recovery flow also lifts the lock.

Lockout applies uniformly to every account, superusers included. There is no time-based auto-unlock — a locked account stays locked until a successful phone-OTP verification or password reset. End-user steps live in the Help Center.

Authentication — Lynkist Developers | Lynkist