Delete log rows in bounded batches.
start_time / end_time carry the same inclusive semantics as GET /api/logs: a row is deleted when its createdAt falls in [start_time, end_time], at millisecond granularity. Note that this operation removes every log row in that window, while GET /api/logs returns audit logs only.
Webhook delivery logs (TriggerHook.*) share the logs table but are read through GET /api/hooks/{id}/recent-logs rather than by GET /api/logs, so deletedCount can exceed the number of rows an export returned. A window covering the last 24 hours also removes the deliveries counted by the execution stats on GET /api/hooks.
end_time is required, so a bare DELETE /api/logs never means "delete everything". end_time must not be in the future and is rejected with 400 otherwise; a common cause is passing microseconds or nanoseconds instead of milliseconds, or a miscomputed timezone offset. Passing seconds instead yields a boundary in the past, which deletes nothing rather than deleting too much.
This operation is irreversible and reachable by any Management API token. Each call deletes at most limit rows in a single statement and reports hasMore. Keep both bounds fixed across calls and repeat while hasMore is true; recomputing a boundary between calls would skip rows. Run one cleanup at a time: a concurrent purge can make hasMore under-report.
Performance
DELETE marks rows dead and leaves index cleanup to autovacuum, so writes are dominated by heap tuples and WAL. The statement is matched on ctid, so the outer side is a Tid Scan over exactly limit rows and the inner select reads those row ids from logs__created_at_id; expect the cost of a call to track limit rather than the size of the table. Measure with EXPLAIN (ANALYZE, BUFFERS) on your own data before running a large purge.
Freed space is reused by later inserts but the table file does not shrink, so disk usage will not drop immediately after a purge.
Query parameters
-
Inclusive upper bound on
createdAt, in unix milliseconds, with the same semantics asGET /api/logs. Only log rows withcreatedAt <= end_timeare deleted. Returns400if the value is in the future. -
Inclusive lower bound on
createdAt, in unix milliseconds, with the same semantics asGET /api/logs. Only log rows withcreatedAt >= start_timeare deleted. Omit to purge everything up toend_time. Returns400if it is afterend_time. -
Maximum rows deleted by this call. Default
1000, maximum10000. Returns400for values outside that range.
Responses
-
Summary of the rows deleted by this call.
deletedCountis the number of log rows deleted, anddeletedRangeis an optional{ oldest, newest }object holding the unix-millisecond timestamps of the deleted rows; it is omitted when nothing was deleted.hasMoreistruewhendeletedCountequalslimit, meaning this batch was full rather than that more rows definitely exist, so the caller should repeat the request with the same bounds. -
Returned when
end_timeis missing, empty, negative, outside the safe integer range, in the future, or not a finite number; whenstart_timeis malformed or afterend_time; or whenlimitis outside1..10000. -
Unauthorized
-
Forbidden
curl \
--request DELETE 'https://[tenant_id].logto.app/api/logs?end_time=string' \
--header "Authorization: Bearer $ACCESS_TOKEN"
{
"deletedCount": 42.0,
"hasMore": true,
"deletedRange": {
"oldest": 42.0,
"newest": 42.0
}
}