Skip to content
Developer documentationOperationalv1 · 2026-09-30

ForenShield API

Plug ForenShield’s email analysis into your scripts, SOAR or SIEM: an .eml file in, an explained verdict and every observable out.

per analysis
< 1 s
per analysis
per file
10 MB
per file
files stored
0
files stored
sample languages
4
sample languages
Base URLhttps://api.forenshield.com
curl -X POST "https://api.forenshield.com/v1/eml/analyze?include=report" \ -H "Authorization: Bearer $FORENSHIELD_API_KEY" \ -H "Content-Type: message/rfc822" \ --data-binary @suspect.eml

Introduction

The API is a REST interface: HTTPS requests, UTF-8 JSON responses, standard HTTP codes. It exposes the same engine as the website’s .eml analyzer, without a browser.

  • Secure by design

    Personal keys hashed with bcrypt, per-tool scopes, expiry, instant revocation. Never a key in a URL.

  • No file stored

    The email is analyzed in memory then discarded. Logs keep only the endpoint, status and duration (90 days).

  • Stable contract

    snake_case JSON, date-versioned. Additions are backward compatible; any breaking change will ship as /v2.

  • Built for automation

    Sub-second responses, typed errors, quota headers, a request id for support.

Quickstart

  1. 1

    Create a key

    Portal → Account → Developer. Pick its scopes and lifetime; the key is shown only once.

  2. 2

    Send an .eml

    POST /v1/eml/analyze with the raw file, a multipart form or base64 JSON.

  3. 3

    Act on the verdict

    Score, signals, links, defanged observables: enough to triage, block or open a ticket automatically.

Get an API key
curl -X POST "https://api.forenshield.com/v1/eml/analyze?include=report" \ -H "Authorization: Bearer $FORENSHIELD_API_KEY" \ -H "Content-Type: message/rfc822" \ --data-binary @suspect.eml

Authentication

Every authenticated request carries your key in the Authorization header (Bearer scheme) or X-API-Key. A key looks like fs_3c9e1a7.4f0b…: the prefix (7 characters) identifies it in your portal, the secret (64 characters) is never stored in clear.

  • Keep the key in a secrets vault or an environment variable, never in code.
  • One key per integration, with only the scopes it needs and an expiry.
  • Do not call the API from a public browser app: the key would be visible.
  • Key exposed? Revoke it from the portal: it takes effect immediately.

A key passed in the URL (?api_key=…) is rejected (400 api_key_in_url): URLs end up in logs.

# Recommandé / recommended curl "https://api.forenshield.com/v1/me" -H "Authorization: Bearer fs_3c9e1a7.4f0b…" # Accepté / also accepted curl "https://api.forenshield.com/v1/me" -H "X-API-Key: fs_3c9e1a7.4f0b…"

Scopes

A scope grants access to one tool. A key without the required scope gets 403 insufficient_scope. New tools will join the API with their own scope.

EndpointScopeAuthenticationDescription
GET /v1—NoneAPI index
GET /v1/openapi.json—NoneOpenAPI specification
GET /v1/me—Any valid keyCurrent key
POST /v1/eml/analyzeeml:analyzeBearerAnalyze an email (.eml)

Rate limits & quotas

Each key has a per-minute limit and a daily quota (reset at midnight UTC). Beyond that: 429 with a Retry-After header. Need more? Contact us.

60

requests / minute

1,000

requests / day

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute.
X-RateLimit-RemainingRequests left in the current minute.
X-RateLimit-ResetEnd of the window (Unix timestamp, seconds).
X-Quota-Limit / X-Quota-RemainingDaily quota and requests left.
Retry-AfterSeconds to wait (429 responses).
X-Request-IdUnique identifier, to share with support.
# Les en-têtes indiquent le quota restant et, en cas de 429, le délai à respecter. curl -i "https://api.forenshield.com/v1/me" -H "Authorization: Bearer $FORENSHIELD_API_KEY" # X-RateLimit-Remaining: 59 # X-Quota-Remaining: 998 # Retry-After: 12 (réponse 429 uniquement)

Errors

Errors always have the same shape. Test type (stable family) and code (precise cause); message is human-readable, in French with Accept-Language: fr.

CodeHTTPTypeMeaning
missing_api_key401authentication_errorMissing API key. Send it in the Authorization: Bearer <key> or X-API-Key header.
invalid_api_key401authentication_errorInvalid API key.
revoked_api_key401authentication_errorThis API key has been revoked.
expired_api_key401authentication_errorThis API key has expired.
api_key_in_url400invalid_request_errorNever pass an API key in the URL: use the Authorization header. Revoke this key if the URL was logged.
account_disabled403permission_errorThe account behind this key is disabled or no longer has API access.
insufficient_scope403permission_errorThis key does not have the scope required by this endpoint.
rate_limited429rate_limit_errorToo many requests per minute for this key. Retry after the Retry-After delay.
quota_exceeded429rate_limit_errorDaily quota reached for this key. It resets at midnight UTC.
route_not_found404not_found_errorUnknown endpoint. See GET /v1 for the list of endpoints.
method_not_allowed405invalid_request_errorHTTP method not supported by this endpoint.
unsupported_media_type415invalid_request_errorUnsupported content type. Use message/rfc822, application/octet-stream, multipart/form-data or application/json.
payload_too_large413invalid_request_errorFile too large (10 MB maximum).
empty_body400invalid_request_errorNo .eml file received.
invalid_json400invalid_request_errorInvalid JSON body.
invalid_base64400invalid_request_errorThe eml_base64 field is not valid base64.
invalid_parameter400invalid_request_errorInvalid parameter.
invalid_eml422invalid_request_errorThe file is not a readable .eml email (no RFC 5322 headers found).
internal_error500api_errorInternal error. Retry; if it persists, contact support with the request_id.
error
{ "error": { "type": "permission_error", "code": "insufficient_scope", "message": "This key does not have the scope required by this endpoint.", "param": null, "details": { "required_scope": "eml:analyze", "key_scopes": [] }, "doc_url": "https://forenshield.com/developpeurs/api#erreurs", "request_id": "req_4b1e0c77a2d94f6b8e3a9d12" } }

Endpoints

POST/v1/eml/analyzeeml:analyze

Analyze an email (.eml)

Explained verdict, SPF/DKIM/DMARC authentication and alignment, identity consistency, link inventory, defanged observables, Received routing and attachments (MD5, SHA-1, SHA-256). The file is analyzed in memory and never stored.

Request body

Content-TypeDescription
message/rfc822The raw .eml file (recommended).
multipart/form-dataField file (+ include, lang fields).
application/json{ "eml": "…" } (text) or { "eml_base64": "…" }, + filename, include, lang.

Parameters

ParameterTypeDescription
includestring · query | form | JSONOptional sections, comma-separated: headers, body, report.
langfr | enLanguage of signal labels and report (default: fr).
filenamestringFile name, echoed in file.name.
curl -X POST "https://api.forenshield.com/v1/eml/analyze?include=report" \ -H "Authorization: Bearer $FORENSHIELD_API_KEY" \ -H "Content-Type: message/rfc822" \ --data-binary @suspect.eml

JSON variant (base64)

curl -X POST "https://api.forenshield.com/v1/eml/analyze" \ -H "Authorization: Bearer $FORENSHIELD_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"eml_base64\": \"$(base64 -w0 suspect.eml)\", \"include\": [\"headers\"], \"lang\": \"en\"}"

Example response

200 OK · application/json
{ "id": "req_7f3a91c2d8e64b05a1c9e2f4", "object": "eml.analysis", "api_version": "2026-09-30", "created_at": "2026-09-30T09:12:04.512Z", "lang": "en", "file": { "name": "demo-hameconnage.eml", "size": 2198, "md5": "66c6044c7b1be88044336e3ad2dcc22e", "sha1": "9124751a51cf0d51eba352560d1e7f5ea90b8110", "sha256": "a92d9083123c1903e233891f5a14b927a5a5768110d913a8d63e1bf76ae1447f" }, "verdict": { "score": 100, "level": "critical", "label": "Highly suspicious", "signals": [ { "id": "double-ext-0-releve-compte.pdf.exe", "label": "Deceptive double extension: releve-compte.pdf.exe.", "points": 35, "severity": "danger" }, { "id": "spf-fail", "label": "SPF fail: the sender is not authorized to send for this domain.", "points": 25, "severity": "danger" }, { "id": "dmarc-fail", "label": "The displayed domain’s DMARC policy failed.", "points": 20, "severity": "danger" }, { "id": "display-name", "label": "The display name contains another address ([email protected]) than the real sender: likely spoofing.", "points": 20, "severity": "danger" } ] }, "message": { "subject": "Urgent : votre compte sera suspendu sous 24 h", "date": "2026-09-26T07:12:00.000Z", "message_id": "<[email protected]>", "mailer": null, "from": { "name": "[email protected]", "address": "[email protected]" }, "reply_to": { "name": null, "address": "[email protected]" }, "return_path": "[email protected]", "to": [ { "name": null, "address": "[email protected]" } ], "cc": [] }, "authentication": { "receiver": "mx.destinataire.example", "arc": false, "spf": { "result": "fail", "domain": "mailer-exemple.test", "aligned": false }, "dkim": { "result": "none", "domain": null, "aligned": false, "signatures": [] }, "dmarc": { "result": "fail", "policy": "reject", "header_from": "banque-exemp1e.com" }, "identities": [ { "role": "from", "address": "[email protected]", "domain": "banque-exemp1e.com", "aligned": true }, { "role": "replyTo", "address": "[email protected]", "domain": "recuperation-compte.test", "aligned": false }, { "role": "returnPath", "address": "[email protected]", "domain": "mailer-exemple.test", "aligned": false }, { "role": "mailfrom", "address": "mailer-exemple.test", "domain": "mailer-exemple.test", "aligned": false }, { "role": "messageId", "address": "[email protected]", "domain": "banque-exemp1e.com", "aligned": true } ] }, "links": [ { "url": "https://banque-exemp1e.com/verification", "url_defanged": "hxxps[://]banque-exemp1e[.]com/verification", "host": "banque-exemp1e.com", "base_domain": "banque-exemp1e.com", "scheme": "https", "sources": [ "href", "text" ], "displayed_texts": [ "https://www.banque-exemple.fr/espace-client" ], "hidden_destination": null, "flags": [ "deceptive" ], "risk": "danger", "occurrences": 2 }, { "url": "https://recuperation-compte.test/collect", "url_defanged": "hxxps[://]recuperation-compte[.]test/collect", "host": "recuperation-compte.test", "base_domain": "recuperation-compte.test", "scheme": "https", "sources": [ "form" ], "displayed_texts": [], "hidden_destination": null, "flags": [ "form", "external" ], "risk": "danger", "occurrences": 1 } ], "observables": { "urls": [ { "value": "https://banque-exemp1e.com/verification", "defanged": "hxxps[://]banque-exemp1e[.]com/verification", "context": [ "href", "text" ], "risk": "danger" }, { "value": "https://recuperation-compte.test/collect", "defanged": "hxxps[://]recuperation-compte[.]test/collect", "context": [ "form" ], "risk": "danger" } ], "domains": [ { "value": "banque-exemp1e.com", "defanged": "banque-exemp1e[.]com", "context": [ "link", "from" ], "risk": "none" }, { "value": "recuperation-compte.test", "defanged": "recuperation-compte[.]test", "context": [ "link", "replyTo" ], "risk": "none" } ], "ips": [ { "value": "198.51.100.23", "defanged": "198[.]51[.]100[.]23", "context": [ "origin" ], "risk": "none" } ], "emails": [ { "value": "[email protected]", "defanged": "alerte[@]banque-exemp1e[.]com", "context": [ "from", "content" ], "risk": "none" }, { "value": "[email protected]", "defanged": "support[@]recuperation-compte[.]test", "context": [ "replyTo", "content" ], "risk": "none" } ], "hashes": [ { "value": "4656fc54d9b6b16f3fca03f6564348f15afea6af886ca04daaac3f080a095d10", "algorithm": "sha256", "file": "releve-compte.pdf.exe", "risk": "danger" }, { "value": "d10d4660e6b37078cac4d083de2496fb0796aa94", "algorithm": "sha1", "file": "releve-compte.pdf.exe", "risk": "danger" } ], "total": 21 }, "routing": { "origin_ip": "198.51.100.23", "hop_count": 2, "total_seconds": null, "anomalies": 0, "hops": [ { "index": 1, "from_host": "192.168.1.20", "from_reverse_dns": null, "ip": "10.0.0.8", "ip_public": false, "by_host": "relay.mailer-exemple.test", "protocol": null, "tls": false, "date": null, "delay_seconds": null, "anomaly": null }, { "index": 2, "from_host": "relay.mailer-exemple.test", "from_reverse_dns": "relay.mailer-exemple.test", "ip": "198.51.100.23", "ip_public": true, "by_host": "mx.destinataire.example", "protocol": "ESMTPS", "tls": true, "date": "2026-09-26T07:12:00.000Z", "delay_seconds": null, "anomaly": null } ] }, "attachments": [ { "filename": "releve-compte.pdf.exe", "content_type": "application/octet-stream", "size": 50, "inline": false, "md5": "c98b22d6321a7f9f894ebed205b78237", "sha1": "d10d4660e6b37078cac4d083de2496fb0796aa94", "sha256": "4656fc54d9b6b16f3fca03f6564348f15afea6af886ca04daaac3f080a095d10", "dangerous": true, "double_extension": true } ] }

Real response for the demo email, arrays shortened.

GET/v1/me

Current key

Prefix, name, scopes, expiry and remaining quotas of the key in use.

curl "https://api.forenshield.com/v1/me" \ -H "Authorization: Bearer $FORENSHIELD_API_KEY"
GET/v1/openapi.jsonNone

OpenAPI specification

OpenAPI 3.1 document to import into Postman, Insomnia or a client generator.

curl -o forenshield-openapi.json "https://api.forenshield.com/v1/openapi.json"
GET/v1None

API index

Version, documentation links and endpoint list. No authentication.

curl "https://api.forenshield.com/v1"

Analysis response

Main fields

FieldTypeDescription
idstringRequest identifier (= X-Request-Id header).
fileobjectName, size and MD5, SHA-1, SHA-256 hashes of the received file (chain of custody).
verdict.scoreinteger 0–100Suspicion score: capped sum of the signals.
verdict.levelenumcritical (≥ 70), high (≥ 40), moderate (≥ 15), low.
verdict.signals[]arrayEach retained indicator: stable id, label, points, severity.
messageobjectSubject, date, Message-ID, mailer, From, Reply-To, Return-Path, To, Cc.
authenticationobjectSPF, DKIM (signatures, selectors), DMARC (policy), ARC, alignment of each identity with From.
links[]arrayEvery URL: displayed text, real destination, hidden destination, sources, flags, risk.
observablesobjectURLs, domains, IPs, emails and hashes, raw and defanged, with their context.
routingobjectOrigin IP, chronological Received hops, TLS, delays and anomalies.
attachments[]arrayName, type, size, MD5 / SHA-1 / SHA-256, risky extension, double extension.
headers[]array · includeRaw headers { name, value } (include=headers).
bodyobject · includeText body and raw HTML, never rendered (include=body).
report.markdownstring · includeDefanged report ready to paste into a ticket (include=report).

Link flags (links[].flags)

FlagRiskDescription
deceptivedangerThe displayed text shows another domain than the destination.
userinfodanger“https://[email protected]”: the destination is after the @.
ipdangerRaw IP address instead of a domain name.
punycodedangerInternationalized domain (xn--), possible homoglyphs.
formdangerTarget of an embedded form.
shortenerwarningURL shortener.
redirectwarningAnother URL as a parameter (SafeLinks, open redirect).
suspiciousTldwarningHeavily abused domain extension.
portwarningNon-standard port.
httpwarningUnencrypted link.
trackinginfoTracking pixel (tiny or hidden image).
externalinfoDifferent domain from the sender’s.

Try-it console

Call the API live with your key. It stays in this page’s memory: never saved, never sent anywhere but the API.

Kept in this page’s memory only: never saved, never sent anywhere but the API.

Endpoint
Email
Optional sections
Label language

Each call counts toward your key’s quota.

The response will appear here.
cURL equivalent
curl -X POST "https://api.forenshield.com/v1/eml/analyze?include=report&lang=en&filename=demo-hameconnage.eml" \ -H "Authorization: Bearer $FORENSHIELD_API_KEY" \ -H "Content-Type: message/rfc822" \ --data-binary @demo-hameconnage.eml

OpenAPI & tooling

The OpenAPI 3.1 specification describes every endpoint, parameter and schema. Import it into Postman, Insomnia, Bruno or a client generator (openapi-generator, orval…).

  • Postman: Import → Link → paste the specification URL.
  • Insomnia / Bruno: import from a URL.
  • Typed clients: openapi-generator-cli generate -i <url> -g python.
Download openapi.json
curl -o forenshield-openapi.json "https://api.forenshield.com/v1/openapi.json"

Changelog

  1. 2026-09-30

    • API v1 launch.
    • POST /v1/eml/analyze: verdict, authentication, links, observables, routing, attachments, Markdown report.
    • GET /v1/me, GET /v1, GET /v1/openapi.json.
    • Personal keys with scopes, expiry, per-minute limits and a daily quota.

Support & security

A question, a quota request, a bug? Write to us with the relevant request_id. A vulnerability? Use our responsible disclosure program.