382 lines
12 KiB
Markdown
382 lines
12 KiB
Markdown
# 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
|
|
|
|
1. Navigate to **Administration > Services > API and Platform Integrations** (or Integrations Hub in newer tenants)
|
|
2. Create a new API 2.0 application
|
|
3. Assign only the permission scopes you need (see §3 below)
|
|
4. Record the `client_id` and `client_secret`
|
|
|
|
### Getting an Access Token
|
|
|
|
```http
|
|
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
|
|
```
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```http
|
|
Authorization: Bearer {access_token}
|
|
Content-Type: application/json
|
|
x-mc-req-id: {uuid-per-request}
|
|
```
|
|
|
|
> `x-mc-req-id` should 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:
|
|
|
|
```http
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{ /* endpoint-specific params */ }
|
|
],
|
|
"meta": {
|
|
"pagination": {
|
|
"pageToken": "next-page-token-from-previous-response"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
```http
|
|
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-token` header — pass this as `token` on 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.
|
|
|
|
```http
|
|
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` | `all`
|
|
- `result`: `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:
|
|
|
|
```http
|
|
GET /api/ttp/url/get-managed-url
|
|
```
|
|
|
|
Or decode a rewritten Mimecast URL back to the original for IOC extraction:
|
|
|
|
```http
|
|
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:
|
|
|
|
```http
|
|
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.
|
|
|
|
```http
|
|
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`: `true` returns 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, same `yyyy-MM-dd'T'HH:mm:ssZ` format as every other endpoint in this guide — confirmed live in 2026-07 against [Mimecast's official docs](https://developer.services.mimecast.com/docs/cloudgateway/1/routes/api/gateway/get-hold-message-list/post) 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 of `all`, `subject`, `sender`, `recipient`, `reasonCode`, `senderIP` per call — `searchBy` is a single object, not an array, so `sender` + `recipient` cannot be combined in one request.
|
|
- **Required permission**: `Account | Dashboard | Read`
|
|
- **Pagination**: `pageSize` in the request is ignored — always returns 10 rows; paginate via `meta.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:
|
|
|
|
```typescript
|
|
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**.
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```sql
|
|
-- 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`'s `searchBy` only accepts one field at a time (`recipient`, `sender`, `subject`, `reasonCode`, `senderIP`, or `all`). Querying by recipient alone returns their entire hold queue for any sender/reason unless you also pass `start`/`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 in `lib/services/mimecast-blast-radius.ts`'s `domainsMatch()` check).
|