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 (a key created by textkit login carries via: cli_login)
api_key.viewedAn admin viewed or copied a key in the console (the key itself is never logged)
api_key.paused, api_key.resumedOur key monitor paused a live key (a sudden spike from a new country: requests today and the countries), or an admin resumed it
cli_login.approved, cli_login.deniedSomeone approved or denied a CLI login request (with the machine name the CLI sent)
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 (console or API)
webhook.secret_rotatedA webhook endpoint's signing secret was replaced (the secret itself is never logged)
webhook.secret_viewedSomeone revealed a webhook endpoint's signing secret in the console (the secret itself is never logged)
spend_limit.updatedSpend limit changes
credits.topped_upCredits were bought (by a person or by auto-recharge)
credits.settings_updatedAuto-recharge or the low-balance alert changed, including auto-recharge switching itself off after a declined card
number.purchased, number.released, number.assignedNumbers (bought, released, or assigned to a customer)
consent.updated, consent.importedManual opt-out / opt-in overrides and imports
migration.opt_outs_attestedAn admin confirmed there are no opt-outs to carry over from a previous provider
quiet_hours.updatedThe account's quiet-hours default changed
reconfirmation.updatedThe account's re-confirmation setting changed; turning it off records the acknowledgment
contact.topic_updatedA contact's subscription to a topic changed
link_domain.created, link_domain.deleted, link_domain.activated, link_domain.deactivatedLink domains added or deleted, and turned on or off (on by itself once its DNS checks out)
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, when moved, or when our team undid an approval)
number.attachedOur team added a number we ordered for you (a toll-free number, say) to your account, with or without the monthly number fee
number.toll_free_updatedOur team recorded a toll-free verification result for a number: status, business and use case
registration.approved, live_access.approvedOur team approved a registration, or live access for the account
live_access.deniedOur team denied a live-access request, with the reason. The account stays in the sandbox and can ask again
registration.approval_undoneOur team undid an approval it recorded by mistake: the registration went back to the filing queue or was returned to you with a reason, its numbers stopped texting the public, and the registration fees that approval started were waived. Live access is unchanged
registration_fees.invoicedOur team billed carrier registration fees: the invoice id, the amount and whether the saved card was charged
business.attested, business.verified, business.rejectedSomeone signed the business attestation (who, and for which registration), or our team verified or could not verify the business (with the reason code). Never the full EIN
opt_in_page.enabled, opt_in_page.disabledA hosted opt-in page for a registration was turned on or off
registration.marketing_paused, registration.marketing_resumedMarketing on a registration was paused for a high opt-out rate, or our team resumed it (with the reason)
registration.sending_paused, registration.sending_resumedOur team paused every message from a registration's numbers for review, or resumed it (with the reason)
registration.content_approved, registration.content_approval_revokedOur team approved a restricted content category for a registration, or revoked it (category, with the reason)
registration.link_domain_added, registration.link_domain_removedOur team allowed a domain in a registration's links, or removed it (domain, with the reason)
template.approved, template.rejectedOur team reviewed a new kind of message for a registration (fingerprint, the reason, how many held messages were sent or dropped)
template_review.updatedOur team changed whether the account's new kinds of message are reviewed (setting: auto for the first 30 days of live sending, always or off, with the reason)
account.suspended, account.unsuspendedOur team suspended the account, or lifted the suspension (with the reason)
account.sending_paused, account.sending_resumedAll live sending was paused (automatically, after messages blocked as phishing, impersonation or fraud, or by our team), or the pause was lifted (with the reason)
account.strikes_resetOur team cleared the account's strikes (with the reason)
account.security_code_sentTextKit texted a sign-in or phone-verification code for the account (the phone's last four digits)
registration.ladder_raised, account.ladder_raisedThe enforcement ladder moved a registration (or the account) to a warning, half pace, or paused, with the reasons
registration.ladder_lowered, account.ladder_loweredOur team moved it back down (with the reason)
account.appealedSomeone on your team appealed a restriction from the console
automation.created, automation.updated, automation.activated, automation.paused, automation.deletedAutomations (name, trigger event and step count; never message text)
automation.identity_unlinkedA user_id to phone link was removed with DELETE /v1/automations/identities/{user_id} (the user_id; never the number)
privacy.phone_erasedTextKit staff deleted one phone number's data from your account on a privacy request (the number's last four digits, what was removed as counts, and the reason). The number's opt-out is kept
message.deleted, message.redactedA message was deleted or redacted through the API (its direction and status, the other number's last four digits, and how many events and request logs were changed; never the text)
privacy.email_erasedTextKit staff deleted one email address's data (its Verify email codes and the events about them) from your account on a privacy request (the address's domain only, what was removed as counts, and the reason)
schedule.created, schedule.updated, schedule.paused, schedule.resumed, schedule.deletedRecurring schedules (frequency, time, time zone and which fields changed; never the message text or the recipient)
account.closed, account.reopenedThe account was closed, by its owner or by our team (with what closing stopped and when the data is deleted), or our team reopened it before then. The log is readable in the console until the account's data is deleted, and is deleted with it

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

GET /v1/audit-logs

bash
curl "https://api.jointextkit.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.jointextkit.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.jointextkit.com/v1/exports/audit_log?created_after=2026-01-01" \
  -H "Authorization: Bearer ssms_sk_live_..." -o audit-log.csv