Admin Tools API

Internal automation API for Lumail administrators - execute admin tools across all organizations with a single endpoint

The Admin Tools API is an internal, admin-only REST interface for platform automation. It exposes a registry of tools behind a single endpoint, secured by dedicated admin API keys.

Warning: Admin API keys can list every organization and, with X-Lumail-Organization, run the public v2/CLI API as that organization. They are reserved for Lumail administrators — this API is not part of the public API and is not available to regular accounts. If you are looking for the customer-facing API, see Tools API (v2).

Quick Start

Both operations live on a single endpoint: /api/v1/admin/tools.

List all available tools:

curl https://lumail.io/api/v1/admin/tools \
  -H "Authorization: Bearer luma_YOUR_ADMIN_KEY"

Execute a tool:

curl -X POST https://lumail.io/api/v1/admin/tools \
  -H "Authorization: Bearer luma_YOUR_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool": "get_org", "input": {"slug": "acme"}}'

Authentication

All requests require an admin API key as a Bearer token:

Authorization: Bearer luma_your_admin_key_here

Admin keys are managed from Admin → API Keys (/admin/api-keys) by platform administrators:

  • Keys use the luma_ prefix followed by 64 hex characters.
  • The plaintext key is shown once at creation — only a sha256 hash is stored.
  • Revoking a key from the admin panel cuts off access immediately.
  • Last used is tracked automatically (background write, throttled to once per minute).

Listing Tools

GET /api/v1/admin/tools is self-describing: it returns every registered tool with its JSON input schema, plus a usage block that shows how to execute them.

Response:

{
  "success": true,
  "usage": {
    "execute": "POST /api/v1/admin/tools",
    "body": { "tool": "<tool name>", "input": "<object matching inputSchema>" },
    "example": { "tool": "list_orgs", "input": { "search": "acme", "pageSize": 20 } }
  },
  "total": 2,
  "tools": [
    {
      "name": "list_orgs",
      "description": "List and search every organization...",
      "inputSchema": { "type": "object" }
    },
    {
      "name": "get_org",
      "description": "Get an organization's general information...",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Organization ID" },
          "slug": { "type": "string", "description": "Organization slug" }
        }
      }
    }
  ]
}

Executing Tools

POST /api/v1/admin/tools with a JSON body:

FieldTypeRequiredDescription
toolstringYesName of the tool to execute (e.g. get_org)
inputobjectNoInput matching the tool's inputSchema

The input is validated against the tool's schema before execution — invalid input returns a 400 with the exact validation issues.

Success response:

{
  "success": true,
  "tool": "get_org",
  "data": {
    /* tool-specific response */
  }
}

Errors

StatusMeaning
400Invalid input — the message lists each failing field and why
401Missing, malformed, or revoked admin API key
404Unknown tool — the message lists the available tool names
429IP rate limit exceeded — check the Retry-After header

Example — unknown tool:

{ "message": "Unknown tool: get_orgs. Available tools: list_orgs, get_org" }

Example — invalid input:

{ "message": "Invalid input: Provide only one of id or slug" }

Available Tools (8)

All tools are read-only.

ToolDescription
list_orgsList and search organizations with the same filters as Admin → Organizations.
get_orgGet an organization's general information (plan, stats, members) and its configured sending domains.
get_org_detailsRead one tab of the admin organization page: overview, resources, members, billing, email-settings, dedicated-ip.
get_org_verificationWhy an organization is blocked or banned: the causing transition and AI review, holds, metadata, full status history.
get_review_emailsThe emails an AI review sampled, with per-email scores, trigger, plain-text body and links.
list_emailsList emails (50 per page) filtered by organization, search, status, or subscriber.
get_emailOne email's delivery lifecycle plus its rendered subject, sender, body and links.
get_userA user account: ban state and reason, providers, sessions, and every organization with its review/sending status.

Admin keys can also call the public org-scoped API (/api/v2/... and the main CLI) by sending X-Lumail-Organization: <id-or-slug>. The CLI flag is --organization.


List Organizations

Tool: list_orgs

Same query as the admin organizations home page.

Parameters:

ParameterTypeDescription
searchstringName, slug, tracking hash, or URL
planstringfree, creator, pro, premium, business
statusstringunverified, automated, verified, blocked
activitystringactive or all
clickDomainstringenabled
doubleOptInstringenabled or disabled
limitsstringapproaching
sortstringasc or desc (default desc)
sortBystringcreatedAt, name, plan, members, emails
pagenumberDefault 1
pageSizenumberDefault 10, max 50
curl -X POST https://lumail.io/api/v1/admin/tools \
  -H "Authorization: Bearer luma_YOUR_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool": "list_orgs", "input": {"search": "acme", "plan": "pro", "pageSize": 20}}'

Get Organization

Tool: get_org

Get an organization's general information (plan, stats, members) and its configured sending domains with DNS verification status. Provide either id or slug — never both.

Parameters:

ParameterTypeRequiredDescription
idstringNo*Organization ID
slugstringNo*Organization slug

* Exactly one of id or slug is required.

Example:

curl -X POST https://lumail.io/api/v1/admin/tools \
  -H "Authorization: Bearer luma_YOUR_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool": "get_org", "input": {"slug": "acme"}}'

Response:

{
  "success": true,
  "tool": "get_org",
  "data": {
    "organization": {
      "id": "aBcD1234...",
      "name": "Acme",
      "slug": "acme",
      "logo": "https://...",
      "email": "[email protected]",
      "timezone": "Europe/Paris",
      "verified": true,
      "createdAt": "2026-01-01T00:00:00.000Z"
    },
    "subscription": {
      "plan": "PREMIUM",
      "status": "active",
      "periodStart": "2026-07-01T00:00:00.000Z",
      "periodEnd": "2026-08-01T00:00:00.000Z",
      "cancelAtPeriodEnd": false,
      "customLimits": null
    },
    "stats": {
      "members": 3,
      "subscribers": 1200,
      "campaigns": 42
    },
    "domains": [
      {
        "id": "dom_...",
        "domain": "mail.acme.com",
        "status": "VERIFIED",
        "region": "EU_WEST_1",
        "sesRecords": [
          {
            "name": "_amazonses.mail.acme.com",
            "type": "TXT",
            "value": "...",
            "status": "verified"
          }
        ],
        "fallbackPriority": null,
        "verificationRequestedAt": null,
        "createdAt": "2026-02-01T00:00:00.000Z"
      }
    ]
  }
}
  • subscription is null when the organization has no active, trialing, or past-due subscription (free plan).
  • domains includes the full SES DNS records with per-record verification status — useful to debug deliverability without impersonating the account.

Explaining a Block or Ban

The typical agent flow to answer "why is this account banned?":

  1. get_user with { "email": "..." } → account ban (banned, banReason) and each organization's reviewStatus / sendingStatus.
  2. get_org_verification with { "slug": "..." } → blockReason is the transition that moved the organization into BLOCKED/BANNED (actor, reasonCode, adminNote, customerExplanation) with its AI review (verdict, explanation, maxScores, per-email scores).
  3. get_review_emails with { "slug": "...", "onlyFlagged": true } → the flagged emails' subject, plain-text body and links, to quote concrete examples to the customer.
curl -X POST https://lumail.io/api/v1/admin/tools \
  -H "Authorization: Bearer luma_YOUR_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool": "get_review_emails", "input": {"slug": "acme", "onlyFlagged": true, "maxChars": 3000}}'

get_org_verification — id or slug. Returns status, blockReason (or null when not blocked), openHolds, verificationMetadata (refuseReason, adminMessage, defensiveFlag), reputation, members (with account ban fields), transitions (50 latest), holds, reviews (20 latest) and milestoneClaims.

get_review_emails — id or slug, plus:

ParameterTypeDescription
reviewIdstringReview from get_org_verification. Default: the review behind the current block, else the latest non-LEGIT one
onlyFlaggedbooleanOnly emails the AI flagged. Default false
maxCharsnumberMax body characters per email, 200 to 50,000. Default 4000

get_org_details — id or slug, plus section (default overview).

list_emails — optional id or slug, page, query (subject, recipient, or email ID), status (ALL, SENT, DELIVERED, BOUNCED, COMPLAINED, CANCELLED, FAILED, …), subscriberId.

get_email — emailId, optional organizationId, includeContent (default true), format (text, html, both; default text), maxChars (default 20000).

get_user — id or email.

Adding New Tools

Tools live in an extensible registry in src/lib/admin-tools/. See src/lib/admin-tools/README.md in the repository for the step-by-step guide — new tools automatically appear in the GET listing, the execution endpoint, and the /admin/api-keys usage panel.