PhishGuard API

Submit emails for phishing analysis, retrieve verdicts, and stream verdict updates to your SIEM via signed webhooks. The PhishGuard Gmail add-on uses these same endpoints — it doubles as a reference client you can study.

Base URL: https://api.phishguard.socshield.dz

QUICKSTART

  1. Get your tenant provisioned by emailing sales@socshield.dz.
  2. Sign in to your tenant dashboard and create an API key under /api. Plaintext keys are shown once at creation — copy them then.
  3. POST your first email to the ingest endpoint:
curl
curl -X POST https://api.phishguard.socshield.dz/api/v1/email/ingest \
  -H "X-API-Key: $PHISHGUARD_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message_id": "<abc123@mail.gmail.com>",
    "subject":    "Urgent: verify your account",
    "from":       "no-reply@suspicious.example",
    "to":         "alice@your-company.com",
    "raw":        "Received: from ...\nFrom: ...\nSubject: ...\n\nBody here"
  }'

You get back an email_id and a verdict status that starts as pending. The verdict moves to phishing, safe, or unsure after analysis. Subscribe to the verdict.set webhook to get notified the moment the verdict lands.

AUTHENTICATION

Every request must include your API key in the X-API-Key header. Keys are scoped to a tenant and tied to a list of allowed scopes (ingest, read, etc.) which you set at creation.

header
X-API-Key: pg_<16-char-prefix>_<48-char-secret>
  • IP allowlist — optional, set per key. Requests from outside the allowlist return 403 ip_not_allowed.
  • Expiry — optional, set per key. Expired keys return 401 expired_key.
  • Tenant status — if your tenant is suspended or archived all keys return 403 regardless of validity.
  • Rotation — rotate a key from the dashboard; the old one is revoked immediately and the new plaintext is shown once.

Every response carries an X-Request-Id header. Paste it in any support email and we'll find your request in our logs immediately.

RATE LIMITS & QUOTAS

PhishGuard enforces two limits independently: a per-key sliding-60-second rate limit (set per key, defaults to 60/min) and a per-tenant 30-day email-ingest quota (set per tenant by your account manager).

Every response includes these headers (omitted on keys with no rate limit):

headers
X-RateLimit-Limit:     60
X-RateLimit-Remaining: 47
X-RateLimit-Reset:     1716800000  # unix seconds

When a limit is exceeded the API returns 429 with a JSON body that explains which limit you hit:

json
{
  "error": "Rate limit exceeded",
  "code":  "rate_limited",
  "limit": 60,
  "used":  60,
  "retryAfterSec": 60,
  "request_id": "req_..."
}

The /api/v1/email/ingest endpoint is the only one that counts toward your monthly quota. Read endpoints (e.g. /email/<id>) and notification polls don't.

ENDPOINTS

POST/api/v1/email/ingestscope: ingest

Submit an email for analysis. Returns immediately with a verdict status of pending; subscribe to the verdict.set webhook to be notified once analysis completes.

Request body (JSON)

json
{
  "message_id": "<RFC-822 Message-ID>",   // required, used for dedup
  "subject":    "string",
  "from":       "sender@example.com",
  "to":         "recipient@example.com",
  "raw":        "<full RFC-822 message>",  // required, the engine parses headers + body
  "reporter":   { "email": "user@example.com" }  // optional, attribution
}

Response

json
{
  "email_id": "uuid",
  "verdict":  "pending",
  "request_id": "req_..."
}
curl
curl -X POST https://api.phishguard.socshield.dz/api/v1/email/ingest \
  -H "X-API-Key: $PHISHGUARD_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message_id": "<abc123@mail.gmail.com>",
    "subject":    "Urgent: verify your account",
    "from":       "no-reply@suspicious.example",
    "to":         "alice@your-company.com",
    "raw":        "Received: from ...\nFrom: ...\nSubject: ...\n\nBody here"
  }'
GET/api/v1/email/<email_id>scope: read

Fetch a previously-ingested email, its verdict, and the verdict-setter (if any).

curl
curl https://api.phishguard.socshield.dz/api/v1/email/<email_id> \
  -H "X-API-Key: $PHISHGUARD_KEY"

Response (truncated)

json
{
  "email": {
    "id":         "uuid",
    "subject":    "...",
    "from":       "...",
    "verdict":    "phishing",        // pending | phishing | safe | unsure
    "verdict_reason":  "Brand impersonation + suspicious link",
    "verdict_set_at":  "2026-05-27T10:13:58Z",
    "verdict_set_by":  "analyst@socshield.dz",
    "created_at": "2026-05-27T09:55:00Z"
  }
}
GET/api/v1/notifications/label-updatesscope: read

Long-polling endpoint used by the Gmail add-on (and any reference client) to fetch verdict updates since a timestamp. Not metered against your monthly quota.

curl
curl "https://api.phishguard.socshield.dz/api/v1/notifications/label-updates?since=2026-05-01T00:00:00Z" \
  -H "X-API-Key: $PHISHGUARD_KEY"

Returns up to 500 verdict-changes newer than ?since, ordered oldest-first. Use the latest verdict_set_at in the response as the since param on your next call.

WEBHOOKS

Register a webhook from your dashboard at /api → Webhooks. We POST a signed JSON payload to your URL whenever a subscribed event fires. The only event currently fired is verdict.set.

Headers we send

headers
X-PhishGuard-Signature: <hex HMAC-SHA256 of the raw body>
X-PhishGuard-Event:     verdict.set
X-PhishGuard-Delivery:  <unique-delivery-id>
User-Agent:             PhishGuard-Webhook/1.0

Sample payload

json
{
  "event":      "verdict.set",
  "timestamp":  "2026-05-27T10:14:00Z",
  "tenant_id":  "uuid",
  "webhook_id": "uuid",
  "data": {
    "email_id":  "uuid",
    "verdict":   "phishing",     // phishing | safe | unsure
    "reason":    "Brand impersonation + suspicious link",
    "set_by":    "analyst@socshield.dz",
    "set_at":    "2026-05-27T10:13:58Z"
  }
}

Verification (compute the HMAC over the RAW body, not the parsed JSON)

javascript
import { createHmac, timingSafeEqual } from 'crypto'

function verify(rawBody, signatureHeader, secret) {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
  // Constant-time compare to avoid timing attacks.
  const a = Buffer.from(signatureHeader, 'hex')
  const b = Buffer.from(expected, 'hex')
  return a.length === b.length && timingSafeEqual(a, b)
}

RELIABILITY

If your endpoint returns non-2xx or fails to respond, we retry on an exponential schedule: 1s → 5s → 30s → 5min → 1h → 6h (six retries, seven total attempts). After that the delivery is marked dead and you can re-fire it manually from the dashboard. 4xx responses are treated as permanent failures and not retried.

ERRORS

Every error response is JSON with the same shape:

json
{
  "error":      "Human-readable message",
  "code":       "machine_code",
  "request_id": "req_..."
}
StatusCodeMeaning
401missing_keyNo X-API-Key header
401invalid_keyKey is not recognised or hash mismatch
401invalid_key_formatKey does not match the pg_<prefix>_<secret> shape
401expired_keyKey passed its expires_at timestamp
403ip_not_allowedSource IP is not on this key's allowlist
403insufficient_scopeKey lacks the scope this endpoint requires
403tenant_suspendedTenant has been suspended — contact sales
403tenant_archivedTenant has been archived
400validation_errorRequest body failed schema validation
409conflictResource conflict (e.g. duplicate slug)
429rate_limitedPer-key rate limit exceeded
429quota_exceededTenant monthly ingest quota exceeded
500internalUnexpected server error — contact support with the request_id

Tailored for your environment

Looking for higher quotas, dedicated infra, on-prem deployment, or a custom SLA? PhishGuard is sold direct. Email sales@socshield.dz with your use case and we'll get back to you within one business day.