Audit log

Every administrative change to your account is recorded with who made it, what they changed, when, and from which IP address. Admins see it in Console → Audit log; the API serves the same entries.

What is recorded

ActionWhen
api_key.created, api_key.rolled, api_key.revokedKey changes in the console
team.invite_created, team.invite_accepted, team.invite_revokedInvitations
team.role_changed, team.member_removed, team.member_leftMembership changes
webhook.created, webhook.updated, webhook.deletedWebhook endpoints
webhook.secret_rotatedA webhook endpoint's signing secret was replaced (the secret itself is never logged)
spend_limit.updatedSpend limit changes
number.purchased, number.released, number.assignedNumbers (bought, released, or assigned to a customer)
consent.updated, consent.importedManual opt-out / opt-in overrides and imports
contact.topic_updatedA contact's subscription to a topic changed
contacts.importedA contact import finished (row counts, never the rows)
segment.created, segment.updated, segment.deletedAudience segments
topic.created, topic.updated, topic.deletedSubscription topics
settings.updatedAccount name, auto-replies, verified recipients, live-access requests
number.registration_set, number.sender_linked, number.sender_unlinkedA number was attached to a registration, linked with the carriers, or unlinked (on release or when moved)
registration.approved, live_access.approvedOur team approved a registration, or live access for the account
automation.created, automation.updated, automation.activated, automation.paused, automation.deletedAutomations (name, trigger event and step count; never message text)

Entries never contain message content, secrets, or full invite links.

GET /v1/audit-logs

bash
curl "https://api.joinsimplesms.com/v1/audit-logs?limit=25" -H "Authorization: Bearer ssms_sk_live_..."
json
{
  "data": [
    {
      "id": "aud_a1B2c3D4e5F6g7H8",
      "object": "audit_log",
      "action": "api_key.created",
      "actor": { "type": "user", "id": "uid_...", "email": "dev@example.com", "name": "Dev" },
      "target": { "type": "api_key", "id": "key_x9Y8z7W6v5U4" },
      "metadata": { "mode": "live", "name": "Billing worker", "scopes": ["messages:send"] },
      "ip": "203.0.113.7",
      "created_at": "2026-10-01T17:02:11.000Z"
    }
  ],
  "has_more": true,
  "next_cursor": "..."
}

Newest first. Pass next_cursor back as cursor for the next page; limit is 1 to 100 (default 25). actor.type is user (console) or api_key (with the key's id and name). Needs the audit_logs:read scope on a restricted key.

Filters

ParameterMatches
actionOne action (number.released), or a whole group with a trailing dot (team., api_key.)
actorThe actor's id (exact), or any part of their email, case-insensitive
target_typeThe target's type: api_key, member, invite, webhook, number, phone, ...
created_afterEntries at or after this time (ISO 8601)
created_beforeEntries at or before this time (ISO 8601); a bare date means the end of that day, UTC
bash
curl "https://api.joinsimplesms.com/v1/audit-logs?action=team.&created_after=2026-09-01&created_before=2026-09-30" \
  -H "Authorization: Bearer ssms_sk_live_..."

Filters apply before paging, so a page holds limit matching entries. One request scans at most 5,000 entries: on a long log with a rare filter, a page can come back short (or empty) with has_more: true. Keep following next_cursor until has_more is false. A date range narrows the scan itself, so it is the fastest filter. An invalid filter returns 400 invalid_request.

Export as CSV

GET /v1/exports/audit_log streams the same entries as CSV and takes the same filters. Columns: id, created_at, action, actor_type, actor_id, actor_email, actor_name, target_type, target_id, ip, metadata (JSON). Up to 50,000 rows per export; same audit_logs:read scope. In the console, Export CSV on the Audit log page downloads the current filter (admins only).

bash
curl "https://api.joinsimplesms.com/v1/exports/audit_log?created_after=2026-01-01" \
  -H "Authorization: Bearer ssms_sk_live_..." -o audit-log.csv