12 KiB
Mimecast API Guide — Threat Dashboard Integration
Overview
This guide covers authentication and data access for the Mimecast API v2, oriented toward building a threat dashboard in a client portal. All examples use OAuth 2.0 (the current recommended approach).
1. Authentication (OAuth 2.0 — Client Credentials)
Setup in Mimecast Console
- Navigate to Administration > Services > API and Platform Integrations (or Integrations Hub in newer tenants)
- Create a new API 2.0 application
- Assign only the permission scopes you need (see §3 below)
- Record the
client_idandclient_secret
Getting an Access Token
POST https://api.services.mimecast.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600
}
Cache the token and refresh before expiry. Plan for a 401 → refresh → retry flow.
Using the Token
All API requests:
Authorization: Bearer {access_token}
Content-Type: application/json
x-mc-req-id: {uuid-per-request}
x-mc-req-idshould be a fresh UUID per call — used for idempotency and support tracing.
Region Discovery
Mimecast routes requests by region. If you get unexpected 401s or 404s, call the discovery endpoint first to resolve the correct base URL for a given account:
GET https://api.services.mimecast.com/oauth/discover
Authorization: Bearer {access_token}
2. Request / Response Structure
Nearly all Mimecast endpoints use POST, even for reads. The body follows a consistent envelope:
{
"data": [
{ /* endpoint-specific params */ }
],
"meta": {
"pagination": {
"pageToken": "next-page-token-from-previous-response"
}
}
}
Response:
{
"data": [ /* results */ ],
"meta": { /* status info */ },
"fail": [ /* per-item errors */ ],
"pagination": {
"next": "token_for_next_page",
"previous": "token_for_prev_page"
}
}
Important: HTTP 200 means the request was received. Always check fail array and meta.status for functional errors — a 200 can still contain failures.
3. Threat Dashboard Endpoints
A. SIEM Logs (Core Email Threat Stream)
The broadest threat feed — covers all MTA events including blocked senders, malicious URLs, attachment verdicts, spoofing attempts.
POST /api/audit/get-siem-logs
{
"data": [{
"type": "MTA",
"fileFormat": "json",
"compress": false,
"token": ""
}]
}
- Returns:
application/octet-stream— newline-delimited JSON events - Token-based cursor: response includes
mc-siem-tokenheader — pass this astokenon the next call to get the next batch - Retention: 7 days only — pull at least every 6 hours, ideally hourly
- Update frequency: new batches available every ~30 minutes
- Rate limit: 300 calls/hour for this endpoint
- Required scope:
Gateway | Tracking | Read - Prerequisite: Enable Enhanced Logging in Administration Console > Account Settings
Key fields in each event:
| Field | Description |
|---|---|
Act |
Action taken (Acc=accepted, Rej=rejected, Hld=held) |
ThreatDictionary |
Threat categories triggered |
SpamInfo |
Spam score and verdict |
URL |
Rewritten URL if TTP URL protection triggered |
FileName |
Attachment name if scanned |
SHA256 |
Hash of attachment |
SenderIP |
Originating IP |
B. TTP Attachment Protection Logs
Sandboxing verdicts for email attachments — malware, ransomware, etc.
POST /api/ttp/attachment/get-logs
{
"data": [{
"from": "2026-01-01T00:00:00+0000",
"to": "2026-01-02T00:00:00+0000",
"route": "inbound",
"result": "malicious",
"pageSize": 100
}]
}
route:inbound|outbound|internal|allresult:safe|malicious|timeout|error|unsafe|all- Required scope:
Monitoring | Attachment Protection | Read
Key response fields:
| Field | Description |
|---|---|
result |
Sandbox verdict |
fileName |
Original attachment filename |
sha256 |
File hash (pivot to threat intel) |
senderAddress |
From address |
recipientAddress |
Target mailbox |
actionTriggered |
What Mimecast did (block, sandbox, etc.) |
date |
ISO 8601 timestamp |
C. TTP URL Protection — Managed URL List
Retrieve blocked/tracked URLs from the managed threat list:
GET /api/ttp/url/get-managed-url
Or decode a rewritten Mimecast URL back to the original for IOC extraction:
POST /api/ttp/url/decode-url
{
"data": [{
"url": "https://protect-eu.mimecast.com/s/ABC..."
}]
}
D. Audit Events (Security Configuration Changes)
Policy changes, admin actions — useful for detecting misconfigurations or insider threats:
POST /api/audit/get-audit-events
{
"data": [{
"startDateTime": "2026-01-01T00:00:00+0000",
"endDateTime": "2026-01-02T00:00:00+0000",
"categories": ["policy", "account"]
}]
}
- Required scope:
Account | Logs | Read
E. Held Message Queue (Blast Radius / Phishing Triage)
Used by Pulse's phishing-triage Blast Radius panel (lib/services/mimecast-blast-radius.ts) to check what's sitting in a recipient's spam/policy hold queue.
POST /api/gateway/get-hold-message-list
{
"data": [{
"admin": true,
"start": "2026-07-16T18:10:55+0000",
"end": "2026-07-18T09:38:00+0000",
"searchBy": {
"fieldName": "recipient",
"value": "user@example.com"
}
}]
}
admin:truereturns held messages for all recipients (the client falls back to omitting it on a 403 for tenants lacking that permission);false/omitted returns only the authenticated user's own held mail.start/end: date range, sameyyyy-MM-dd'T'HH:mm:ssZformat as every other endpoint in this guide — confirmed live in 2026-07 against Mimecast's official docs and a real tenant; easy to miss since without it you query a recipient's ENTIRE hold queue with no date bound.searchBy.fieldName: exactly one ofall,subject,sender,recipient,reasonCode,senderIPper call —searchByis a single object, not an array, sosender+recipientcannot be combined in one request.- Required permission:
Account | Dashboard | Read - Pagination:
pageSizein the request is ignored — always returns 10 rows; paginate viameta.pagination.next.
Key response fields:
| Field | Description |
|---|---|
from.emailAddress / fromHeader.emailAddress |
Envelope vs. header sender — use fromHeader when present |
to.emailAddress |
Recipient |
subject |
Message subject |
reason / reasonCode |
Why it's held (e.g. "Message Hold Applied - Spam Signature policy", "... - DMARC Quarantine") |
dateReceived |
ISO 8601 timestamp |
route |
INBOUND | OUTBOUND | INTERNAL | EXTERNAL |
Official docs: https://developer.services.mimecast.com/docs/cloudgateway/1/routes/api/gateway/get-hold-message-list/post
4. Pagination
All paginated endpoints use cursor tokens:
async function fetchAllPages(endpoint: string, basePayload: object): Promise<any[]> {
const results: any[] = [];
let pageToken: string | undefined;
do {
const body = {
...basePayload,
meta: pageToken ? { pagination: { pageToken } } : undefined
};
const res = await fetch(`https://api.services.mimecast.com${endpoint}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'x-mc-req-id': crypto.randomUUID()
},
body: JSON.stringify(body)
});
const json = await res.json();
results.push(...(json.data ?? []));
pageToken = json.pagination?.next;
} while (pageToken);
return results;
}
Default page size: 100. Maximum: 500. Set pageSize in the data array params.
5. Rate Limiting
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Total requests allowed in window |
X-RateLimit-Remaining |
Calls left |
X-RateLimit-Reset |
Milliseconds until reset |
On HTTP 429, back off exponentially. SIEM logs endpoint has a hard limit of 300 calls/hour.
async function fetchWithRetry(url: string, options: RequestInit, retries = 3): Promise<Response> {
const res = await fetch(url, options);
if (res.status === 429 && retries > 0) {
const resetMs = parseInt(res.headers.get('X-RateLimit-Reset') ?? '5000');
await new Promise(r => setTimeout(r, resetMs));
return fetchWithRetry(url, options, retries - 1);
}
return res;
}
6. Multi-Tenant (MSP) Access
When accessing multiple client accounts with a single credential set, pass accountCode in the meta:
{
"data": [{ /* params */ }],
"meta": {
"accountCode": "CLIENT_ACCOUNT_CODE"
}
}
Each Mimecast tenant has a unique account code visible in the administration console.
7. Required Permission Scopes by Endpoint
| Endpoint | Required Scope |
|---|---|
| SIEM Logs | Gateway > Tracking > Read |
| Audit Events | Account > Logs > Read |
| TTP Attachment Logs | Monitoring > Attachment Protection > Read |
| TTP URL Management | Gateway > Policies > Read |
Request only the scopes needed. Grant least privilege per API application.
8. Dashboard Data Model Suggestion
For a threat dashboard showing a summary per client:
-- Suggested local cache table
CREATE TABLE mimecast_threat_events (
id TEXT PRIMARY KEY,
account_code TEXT NOT NULL,
event_type TEXT NOT NULL, -- 'siem_mta' | 'ttp_attachment' | 'ttp_url'
event_ts TIMESTAMPTZ NOT NULL,
sender TEXT,
recipient TEXT,
sender_ip INET,
threat_type TEXT, -- malicious | spam | spoofing | etc.
action TEXT, -- blocked | sandboxed | delivered
sha256 TEXT,
url TEXT,
raw JSONB,
synced_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX ON mimecast_threat_events (account_code, event_ts DESC);
CREATE INDEX ON mimecast_threat_events (event_type, threat_type);
Sync strategy: poll SIEM logs hourly using stored mc-siem-token per account. Store the cursor token per account_code in a config table.
9. Gotchas
- Enhanced Logging must be enabled in the Mimecast console before SIEM data appears — without it the endpoint returns empty.
- SIEM uses a streaming cursor, not date ranges — don't skip token management or you'll re-read old events.
- HTTP 200 ≠ success — always check
fail[]in the response body. - POST for reads — don't expect REST conventions; almost everything is POST.
- Token expiry — implement proactive refresh (check
expires_in, refresh at 80% of TTL). - Region routing — if a client's tenant is in EU/AU, the base URL differs; use the discovery endpoint per account.
- Held-message queue has no combined filter —
get-hold-message-list'ssearchByonly accepts one field at a time (recipient,sender,subject,reasonCode,senderIP, orall). Querying by recipient alone returns their entire hold queue for any sender/reason unless you also passstart/end— and even then, an unrelated held message can coincidentally fall in the same window. Always cross-check the held row's sender against the message you actually care about before treating it as related (this exact false positive surfaced on a real ticket and is now guarded inlib/services/mimecast-blast-radius.ts'sdomainsMatch()check).