API Reference
API Reference
GitHub
Webhooks and Events

Audit Log

Track project operational and personal security events

Mailyard keeps two trails. Both live in one table and are read through different endpoints with different gates.

Project Activity

Configuration changes inside one project: credentials minted or revoked, templates changed, SMTP servers added, webhooks edited. Requires the project admin or owner role.

GET /api/v1/audit-log?limit=50&offset=0
curl "http://localhost:3000/api/v1/audit-log?limit=50" \
  -H "Authorization: Bearer myk_..." \
{
    "events": [
        {
            "id": "0c1f...",
            "category": "project",
            "type": "apikey.created",
            "project_id": "81af718e-f0ae-4780-a0d7-9f05b34dabcc",
            "actor_id": "c223c373-d501-4643-8cdb-35abb984ed7e",
            "actor_email": "admin@example.com",
            "client_ip": "203.0.113.42",
            "method": "POST",
            "path": "/api/v1/api-keys/",
            "status": 201,
            "created_at": "2026-08-06T02:11:00Z"
        }
    ],
    "limit": 50,
    "offset": 0
}

Account Security

Sign-ins, failed sign-ins, sign-outs, two-factor changes, and completed password resets. Carries no project.

Read it in the console under Developers - Audit Log - Security. Each person sees their own events. A platform admin sees every account, which is what spots a burst of failed sign-ins across addresses.

Types recorded here:

TypeMeaning
auth.login.succeededPassword (and 2FA where enabled) accepted
auth.login.failedRejected. detail says which leg failed
auth.logoutSession cookie cleared
auth.oidc.loginSSO sign-in completed
auth.oidc.deniedSSO identity refused by the allowlist
auth.2fa.enabled / auth.2fa.disabledTwo-factor changed
auth.password_reset.completedA reset link was redeemed

What Gets Recorded

Project events are captured by middleware on every successful mutating request, so a new endpoint is covered the day it is added rather than the day somebody remembers to add a log line. Event types are derived from the route: POST /api/v1/api-keys becomes apikey.created, POST /api/v1/smtp-servers/{id}/test becomes smtpserver.test. Every row also carries the raw method and path, so a derived type that reads oddly is still traceable.

Two consequences worth knowing:

  • Rejected requests are not recorded. A validation error changed nothing, and logging every 400 would bury the configuration changes an auditor is looking for. Failed sign-ins are the deliberate exception, because there the failure is the event.
  • Reads are not recorded. The trail answers “what changed”, not “who looked”.

Writes with no project context - platform settings, user management, plan changes - are filed on the acting user’s security trail rather than inventing a project for them.

Durability

Audit writes are queued and flushed by a background writer, so recording never adds latency to the request that triggered it and never fails that request. If the queue fills because the database is wedged, events are dropped and each drop is logged as an error with a running total. An install that needs guaranteed-durable audit before the action completes needs a different design - this one favors the action succeeding.

Retention

Controlled by audit_log_retention_days in Platform Settings , default 90 days. 0 keeps entries forever. The retention job does the trimming.