Billing & Audit
Approvals
When a token has requires_approval set, destructive or high-stakes actions are queued as held actions instead of executing immediately. The inbox owner reviews them here — approving executes the action, rejecting discards it. Held actions expire after 24 hours if not reviewed. All endpoints require a user auth session token or a token with approvals:read / approvals:write scope.
GET
/v1/approvals/?inbox_id=
List pending held actions scope: approvals:read
[
{
"approval_id": "...",
"inbox_id": "...",
"action_type": "email:send",
"token_label": "My Agent",
"summary": "To: alice@example.com — Project update",
"status": "pending",
"queued_at": "2026-05-29T10:00:00Z",
"expires_at": "2026-05-30T10:00:00Z"
}
]
GET
/v1/approvals/{approval_id}/?inbox_id=
Retrieve a held action scope: approvals:read
{
"action_id": "...",
"inbox_id": "...",
"action_type": "email:send",
"token_label": "My Agent",
"summary": "To: alice@example.com — Project update",
"status": "pending",
"queued_at": "2026-05-29T10:00:00Z",
"expires_at": "2026-05-30T10:00:00Z"
}
POST
/v1/approvals/{approval_id}/approve/
Approve and execute a held action scope: approvals:write
Request
{ "inbox_id": "..." }
Response
# 204 No Content — action executed successfully
# 400 Bad Request — action is expired or already processed
# 404 Not Found — approval_id not found or belongs to a different inbox
# 502 Bad Gateway — upstream service error
POST
/v1/approvals/{approval_id}/reject/
Reject and discard a held action scope: approvals:write
Request
{ "inbox_id": "..." }
Response
# 204 No Content — action discarded
# 404 Not Found — approval_id not found or belongs to a different inbox
How it works: When an agent token has requires_approval: ["email:send"], any send attempt returns 202 Accepted with an approval_id instead of sending immediately. The inbox owner receives a task notification and can approve or reject via these endpoints. Approving executes the original action exactly as the agent submitted it. Held actions expire automatically after 24 hours — approving an expired action returns 400.
Approvable action types: email:send, email:delete, contacts:delete, calendar:delete — these appear in a token's requires_approval list. Workflow writes can also be held as workflow:write when review policy requires human confirmation. Approving a held workflow contract commits the reviewed result without re-running extraction.
→ Held action field reference
Billing & Audit
Stats
Aggregate performance metrics scoped to the caller's access level. Inbox owners see their own broadcast, suppression, workflow, approval, file, and mailbox stats. Tenant admins see aggregates across all inboxes in their tenant with optional per-inbox filtering.
Date range: All endpoints accept optional since and until query params (ISO date YYYY-MM-DD or datetime YYYY-MM-DDTHH:MM:SSZ). Maximum window is 90 days. Omit both for all-time stats.
Auth: Session auth (all scopes) or agent token with stats:read scope (inbox scope only).
GET
/v1/stats/
Inbox stats scope: stats:read
Query params
| Parameter | Description |
| inbox_id | Required for session auth; omit for token auth. |
| since | optional | ISO date or datetime, optional — max 90 days from since. |
200 OK
{
"period": { "since": "2026-05-01T00:00:00+00:00", "until": "2026-05-31T00:00:00+00:00" },
"broadcasts": {
"sends": 3,
"recipients_total": 1500,
"delivered": 1450,
"opened": 620,
"clicked": 180,
"bounced": 35,
"spam_complaints": 2,
"open_rate": 0.4276,
"click_rate": 0.1241,
"bounce_rate": 0.0233,
"spam_rate": 0.0013
},
"suppression": {
"total": 42,
"hard_bounce": 28,
"spam_complaint": 8,
"unsubscribed": 6
},
"workflows": {
"rules_active": 5,
"executions_total": 234,
"by_rule": [{
"rule_id": "<rule-id>",
"name": "urgent-flag",
"enabled": true,
"executions": 87,
"last_matched_at": "2026-05-30T14:22:00+00:00"
}]
},
"approvals": {
"queued": 3,
"approved": 12,
"rejected": 2,
"expired": 1
},
"files": {
"total": 18,
"total_size_bytes": 2097152,
"total_rows": 344,
"by_type": { "spreadsheet": 6, "document": 8, "skill": 4 }
},
"mailbox": {
"storage": {
"usage_period": "2026-05",
"total_size_bytes": 1503238554,
"total_size_gb": 1.4,
"updated_at": "2026-05-22T00:00:00+00:00"
},
"folders": {
"total_emails": 420,
"unread_emails": 38,
"total_threads": 310,
"unread_threads": 27,
"by_role": {
"inbox": { "total_emails": 120, "unread_emails": 22, "total_threads": 92, "unread_threads": 18 }
}
}
}
}
| Field | Type | Required | Description |
| click_rate | number | optional | Clicks divided by delivered count. |
| bounce_rate | number | optional | Bounces divided by total recipients. |
| spam_rate | number | optional | Spam complaints divided by total recipients. |
| mailbox | object | required | Point-in-time message and attachment storage plus folder-level email/thread counters. |
GET
/v1/stats/tenant/
Tenant aggregate stats tenant admin only
Query params
| Parameter | Description |
| tenant_id | Required — must match the caller's tenant. |
| inbox_id | optional | — |
| until | — |
200 OK
{
"period": { "since": null, "until": null },
"inboxes": { "total": 8, "active": 7 },
"broadcasts": {
"sends": 12, "recipients_total": 6200,
"by_inbox": [{ "inbox_id": "<inbox-id>", "sends": 3, }]
},
"suppression": {
"total": 95,
"by_inbox": [{ "inbox_id": "<inbox-id>", "total": 42, }]
},
"workflows": { "rules_active": 23, "executions_total": 1205, "by_rule": [] },
"approvals": { "queued": 3, "approved": 48, "rejected": 7, "expired": 2 },
"files": { "total": 84, "total_size_bytes": 10485760, "total_rows": 1820, "by_type": { } },
"dmarc": { "reports": 6, "total_messages": 1840, "aligned_messages": 1816, "failed_messages": 24, "failure_rate": 0.013, "by_domain": { } },
"mailbox": {
"storage": { "usage_period": "2026-05", "total_size_bytes": 6442450944, "total_size_gb": 6.0, "updated_at": "2026-05-22T00:00:00+00:00", "by_inbox": [] },
"folders": { "total_emails": 2400, "unread_emails": 180, "total_threads": 1900, "unread_threads": 125, "by_role": { }, "by_inbox": [] }
}
}
GET
/v1/dmarc/reports/
List DMARC aggregate reports tenant admin only
Query params
| Parameter | Description |
| tenant_id | Required — must match the caller's tenant. |
| since | Optional ISO date/datetime. Maximum date window is 90 days. |
| until | Optional ISO date/datetime. Maximum date window is 90 days. |
| domain | Optional verified domain filter. |
| limit | Optional integer, clamped to 1-100. Defaults to 50. |
| sort_by | Optional: received_at, failed_messages, failure_rate, or total_messages. Defaults to received_at. |
| order | Optional: desc or asc. Defaults to desc. |
Tenant admins can query recent reports for their own tenant to investigate deliverability issues.
200 OK
{
"count": 1,
"results": [{
"report_key": "<report-key>",
"domain": "mail.yourco.com",
"org_name": "Mailbox Provider",
"received_at": "2026-06-02T08:10:00+00:00",
"total_messages": 1840,
"failed_messages": 24,
"failure_rate": 0.013,
"csv_available": true
}]
}
GET
/v1/dmarc/reports/{report_key}/csv/
Download DMARC summary CSV tenant admin
Pass tenant_id as a query parameter. The response is text/csv with one row per parsed aggregate-report source row.
Billing & Audit
Usage Caps
Set send limits and mailbox storage limits per inbox, plus workspace Files storage limits. Send and storage caps can be hard limits or flexible billing thresholds, depending on the cap mode.
PUT
/v1/billing/caps/send/
Set send cap
Request
{
"period": "monthly",
"limit": 1000,
"behavior": "reject",
"mode": "enforce"
}
| Field | Type | Required | Description |
| inbox_id | string | optional (session auth only) | Session auth must pass inbox_id as query or body; token auth can omit. |
| period | string | optional | One of: daily, weekly, monthly, none. |
| limit | number | optional | Max sends per period; null removes the cap. |
| behavior | string | optional | One of: reject, queue. Used only in enforce mode. |
| mode | string | optional | enforce rejects or queues sends after the limit. allow_and_bill keeps sending and tracks additional usage for billing. Defaults to the tenant setting; explicit mode changes require a tenant admin session. |
200 OK
{
"inbox_id": "...",
"period": "monthly",
"limit": 1000,
"behavior": "reject",
"mode": "enforce",
"current_count": 47,
"resets_at": "2026-06-01T00:00:00Z"
}
GET
/v1/billing/caps/send/?inbox_id={id}
Get send cap for an inbox scope: billing:read · PUT/DELETE require billing:write
{
"inbox_id": "...",
"period": "monthly",
"limit": 1000,
"behavior": "reject",
"mode": "enforce",
"current_count": 47,
"resets_at": "2026-06-01T00:00:00Z"
}
DEL
/v1/billing/caps/send/
Remove the send cap
# 204 No Content — empty body
PUT
/v1/billing/caps/mailbox/
Set mailbox storage cap
Request
{
"limit_gb": 5.0,
"mode": "enforce",
"alert_thresholds": [80, 95]
}
| Field | Type | Required | Description |
| inbox_id | string | optional (session auth only) | Session auth must pass inbox_id as query or body; token auth can omit. |
| limit_gb | number | required | Message and attachment storage limit in GB. Files API storage is separate. |
| mode | string | optional | enforce applies a mailbox quota. allow_and_bill keeps receiving data and tracks extra storage for alerts/billing. Defaults to the tenant setting; explicit mode changes require a tenant admin session. |
| alert_thresholds | array | optional | Percentage values that trigger storage threshold alerts. |
200 OK
{
"inbox_id": "...",
"resource": "mailbox",
"description": "Per-inbox mailbox message storage. This is separate from Files storage.",
"enforcement_note": "Switching to enforce while usage is already over the cap does not delete existing data. It blocks future mailbox growth until usage is reduced or the cap is changed.",
"limit_gb": 5.0,
"mode": "enforce",
"alert_thresholds": [80, 95],
"current_gb": 1.4,
"pct_used": 28.0,
"last_measured_at": null
}
GET
/v1/billing/caps/mailbox/?inbox_id={id}
Get mailbox storage cap scope: billing:read · PUT/DELETE require billing:write
{
"inbox_id": "...",
"resource": "mailbox",
"description": "Per-inbox mailbox message storage. This is separate from Files storage.",
"enforcement_note": "Switching to enforce while usage is already over the cap does not delete existing data. It blocks future mailbox growth until usage is reduced or the cap is changed.",
"limit_gb": 5.0,
"mode": "enforce",
"alert_thresholds": [80, 95],
"current_gb": 1.4,
"pct_used": 28.0,
"last_measured_at": null
}
DEL
/v1/billing/caps/mailbox/
Remove the mailbox storage cap
# 204 No Content — empty body
PUT
/v1/billing/caps/files/
Set workspace Files storage cap
Request
{
"limit_gb": 25.0,
"mode": "enforce",
"alert_thresholds": [80, 95]
}
| Field | Type | Required | Description |
| limit_gb | number | required | Workspace Files storage limit in GB. |
| mode | string | optional | enforce blocks future Files growth once enforcement is wired into Files writes. allow_and_bill keeps Files writes flexible and tracks usage for alerts/billing. Mutations require a tenant admin session. |
| alert_thresholds | array | optional | Percentage values that trigger storage threshold alerts. |
200 OK
{
"tenant_id": "...",
"resource": "files",
"description": "Tenant-level Files storage for uploaded files, generated documents, spreadsheets, and promoted attachments. This is separate from mailbox message storage.",
"enforcement_note": "Switching to enforce while usage is already over the cap does not delete existing Files data. Billing can continue until an admin deletes files or changes the cap.",
"limit_gb": 25.0,
"mode": "enforce",
"alert_thresholds": [80, 95],
"current_gb": 1.4,
"current_bytes": 1503238554,
"pct_used": 5.6,
"last_measured_at": null
}
GET
/v1/billing/caps/files/
Get workspace Files storage cap scope: billing:read · PUT/DELETE require billing:write and a tenant admin session
{
"tenant_id": "...",
"resource": "files",
"description": "Tenant-level Files storage for uploaded files, generated documents, spreadsheets, and promoted attachments. This is separate from mailbox message storage.",
"enforcement_note": "Switching to enforce while usage is already over the cap does not delete existing Files data. Billing can continue until an admin deletes files or changes the cap.",
"limit_gb": 25.0,
"mode": "allow_and_bill",
"alert_thresholds": [80, 95],
"current_gb": 1.4,
"current_bytes": 1503238554,
"pct_used": 5.6,
"last_measured_at": null
}
DEL
/v1/billing/caps/files/
Remove the workspace Files storage cap
# 204 No Content — empty body
GET
/v1/billing/caps/held/?inbox_id={id}
List held messages (queued sends) scope: billing:read · POST (release all) requires billing:write
[
{
"held_id": "held_01HX...",
"inbox_id": "...",
"queued_at": "2026-05-20T10:00:00Z",
"period_key": "2026-05"
}
]
POST
/v1/billing/caps/held/?inbox_id=
Release all held sends for an inbox
# No request body — pass inbox_id as a query parameter.
{ "released": 3 }
POST
/v1/billing/caps/held/{held_id}/
Release a single held send scope: billing:write
# No request body — pass inbox_id as a query parameter.
# 204 No Content on success.
DEL
/v1/billing/caps/held/{held_id}/
Discard a specific held send scope: billing:write
# 204 No Content — empty body
→ Usage cap field reference
Auth: Token auth requires billing:read for read endpoints and billing:write for mutations (set/delete caps, release/discard held sends). Inbox-scoped send/mailbox/held endpoints require inbox_id as a query param or request body. Workspace Files caps use the caller's tenant and do not take inbox_id. Explicit cap mode changes and Files cap mutations require a tenant admin session.
Held sends are queued outbound messages blocked by an active send cap with mode: "enforce" and behavior: "queue" — releasing them re-attempts delivery immediately. With mode: "allow_and_bill", sends continue and additional usage is billed.
Mailbox storage caps cover messages and attachments. Files storage caps cover workspace Files API objects. Enabling mode: "enforce" never deletes existing data, even if current usage is already above the limit; it blocks future growth for the configured storage dimension until the tenant deletes data, raises the limit, or switches back to flexible billing.
Billing & Audit
Usage & Costs
Monitor LLM token consumption, retrieval activity, email sending, and message and attachment storage per inbox. Costs are tracked for all LLM providers — Gent-hosted usage is billed at provider token cost plus 5%; BYOK providers are tracked for visibility at no charge. Retrieval indexing and run counters appear in the usage breakdown so clients can explain activity even when the direct retrieval unit cost is zero. Summaries are pre-aggregated hourly.
GET
/v1/billing/usage/?inbox_id=&[period=YYYY-MM]
Usage breakdown for an inbox user auth
{
"inbox_id": "...",
"period": "2026-05",
"total_cost": 4.82,
"storage_gb": 1.4,
"breakdown": {
"llm_tokens": { "quantity": 128000, "cost": 1.92 },
"retrieval_runs": { "quantity": 14, "cost": 0.00 },
"retrieval_index_chunks": { "quantity": 420, "cost": 0.00 },
"email_sent": { "quantity": 240, "cost": 0.00 }
},
"provider_breakdown": {
"gent": { "quantity": 98000, "cost": 1.47 },
"openai_byok": { "quantity": 30000, "cost": 0.00 }
},
"updated_at": "2026-05-16T11:00:00Z"
}
Auth: Session auth only — agent tokens are not accepted. Supply inbox_id as a query param.
period defaults to the current calendar month. LLM, retrieval, and email summaries are refreshed hourly; storage_gb is a point-in-time message and attachment storage reading updated daily. Retrieval answers record normal AI token usage under llm_tokens; retrieval runs and indexed chunks are reported separately for visibility. Vector embedding usage follows provider cost plus 5% when an indexing job invokes embeddings.
Storage above included mailbox or Files allocations is priced at $0.50 per GB when flexible billing is enabled. Set a billing.cost_threshold or billing.projected_overage event notification to receive a webhook when spend crosses a USD threshold.
Billing & Audit
Audit Log
A paginated, append-only log of user-initiated actions taken within an inbox or across a tenant — messages sent, tokens created or revoked, contact mutations, label events, retrieval events, and more. Useful for compliance, debugging, and oversight.
Auth: Session auth only — agent tokens are not accepted. Supply inbox_id for the inbox view. Tenant-wide audit is owner/admin only and derives the tenant from the session.
GET
/v1/audit/?inbox_id=
List audit events (paginated) user auth
{
"events": [
{
"id": "aud_01HX9K2NPM",
"action": "message.sent",
"actor": "tok_aB3xKz9m",
"resource_id": "msg_01HX9K2NPM",
"occurred_at": "2026-05-15T10:00:00Z"
}
],
"cursor": "eyJwb3MiOjUwfQ..."
}
GET
/v1/audit/tenant/
List tenant-wide audit events owner / admin only
{
"events": [
{
"id": "aud_01HX9K2NPM",
"action": "retrieval.index.completed",
"actor": "system",
"resource_type": "retrieval_bucket",
"resource_id": "rb_01HX9K2NPM",
"occurred_at": "2026-05-15T10:00:00Z"
}
],
"cursor": null
}
→ Audit log query params
Pagination: Results are returned newest-first. Pass the cursor value from the previous response as the cursor query param to fetch the next page. When cursor is null the last page has been reached.
Retention defaults to 90 days. Team tenant admins can change this via PATCH /v1/tenants/ using the audit_retention_days field (integer, 1–3650). Personal tenants always use the 90-day default. Recorded action types include: message.sent, token.created, token.rotated, token.revoked, contact.created, contact.updated, contact.deleted, label.applied, label.removed.
Plan gate — audit retention: Configuring audit_retention_days (and mfa_required) via PATCH /v1/tenants/ requires the governance tier to be full (Scale plan and above). Attempting to set either field on a lower plan returns 403 Forbidden.
Billing & Audit
Compliance (GDPR / SOC2)
GDPR subject access requests, data deletion, consent management, and SOC2 audit bundle export. All endpoints require session authentication and act on the authenticated user's own data.
Deletion: Deletion requests are not immediate — a retention hold is placed and the purge runs automatically once it expires. The expires_at field in the response tells you when data will be permanently removed.
LLM consent: Revoking consent stops all AI enrichment immediately and removes any AI-generated content already stored against your contacts. To re-grant consent use POST /v1/tenants/llm-consent/ (owner only).
GET
/v1/compliance/export/
GDPR Subject Access Request — export all personal data
Response
{
"user": { "email": "...", "display_name": "...", "status": "active", "created_at": "..." },
"tenant": { "tenant_id": "...", "type": "team", "plan": "startup", ... },
"inboxes": [{ "inbox_id": "...", "address": "...", "status": "active" }],
"relationships": [{ "inbox_id": "...", "address": "...", "strength": 0.8 }],
"timeline_entries": [{ "inbox_id": "...", "occurred_at": "...", "summary": "..." }],
"events": [{ "event_id": "...", "inbox_id": "...", "type": "email.received", "created_at": "..." }],
"audit_events": [...]
}
Message content and contact cards are held in your mailbox and are accessible via your inbox credentials — they are not duplicated into this export.
POST
/v1/compliance/deletion/
GDPR erasure request
202 Accepted
{
"detail": "Deletion request recorded.",
"expires_at": "2026-06-29T12:00:00+00:00"
}
Permanent purge runs after the time shown in expires_at.
GET
/v1/compliance/consent/
Current consent state
Response
{
"terms_accepted": true,
"llm_consent": true,
"llm_consented_at": "2026-04-01T09:00:00+00:00",
"marketing_emails": null
}
DEL
/v1/compliance/consent/
Revoke LLM processing consent (GDPR Art. 7(3))
No request body required
Response
{
"revoked_at": "2026-05-30T14:22:00+00:00"
}
Returns 200 with "detail": "LLM consent already revoked." if consent was not active.
GET
/v1/compliance/audit-bundle/?period=
SOC2 audit bundle export owner / admin only
Query params
| Parameter | Description |
| period | Required — "2024", "2024-06", or "2024-Q2". |
Response
{
"tenant_id": "<tenant-id>",
"period": "2024-Q2",
"generated_at": "2026-05-30T14:22:00+00:00",
"event_count": 87,
"events": [...]
}