Dashboard

REST API

The REST API is plain HTTP with bearer-token auth — call it from any backend language that can send an HTTP request and parse JSON. There's no dedicated Laravel or PHP SDK package; a plain HTTP client is all you need.

Two credential types

 Public keySecret key
Formatpub_test_... / pub_live_...sec_test_... / sec_live_...
Belongs inBrowser code (SDK publicKey)Your own backend only — never shipped to a client
Can callPOST /v1/events onlyEverything below: devices, events, risk assessments, network, overview

Every endpoint other than POST /v1/events requires Authorization: Bearer sec_live_xxx (or an authenticated dashboard session) — a public key is rejected outright with a 401.

Ingesting events

The only endpoint callable with a public key — this is what the browser SDK calls, and what a server-side integration can call too:

POST /v1/events
Authorization: Bearer pub_test_xxx
Content-Type: application/json

{
  "event": "login",
  "account": { "id": "usr_123" },
  "signals": { ... }
}

Server / dashboard endpoints

All list endpoints are paginated ({ data, links, meta }). A secret key or dashboard session only ever sees data within its own environment/organization — cross-tenant access returns 403.

Devices

  • GET /v1/environments/{env}/devices — list devices in an environment.
  • GET /v1/devices/{device} — single device.
  • GET /v1/devices/{device}/events — events for this device.
  • GET /v1/devices/{device}/accounts — accounts seen on this device.
  • GET /v1/devices/{device}/signals — current normalized signal profile.
  • GET /v1/devices/{device}/resolutions — resolution audit history.
  • POST / DELETE /v1/devices/{device}/trust — control-plane-only, audited.

Accounts

  • GET /v1/environments/{env}/accounts/{externalAccountId}/devices — devices this account has used.

Events

  • GET /v1/environments/{env}/events — filterable by event type, device, account, decision, level, date range.
  • GET /v1/events/{event} — full investigation detail (see example below).

Risk assessments, network, overview

  • GET /v1/environments/{env}/risk-assessments — filterable, full signal breakdown.
  • GET /v1/environments/{env}/network — one row per distinct IP, exposed only as a masked IP (e.g. 203.0.113.xxx).
  • GET /v1/environments/{env}/overview — dashboard only. Today's aggregate counts.

Example: GET /v1/events/{event}

Condensed response shape (this endpoint is the one place the raw IP address and full per-signal metadata are returned):

{
  "data": {
    "id": "evt_...",
    "event_type": "login_failed",
    "external_account_id": "usr_123",
    "ip_address": "203.0.113.42",
    "device": { "id": "dev_...", "is_trusted": false, "first_seen_at": "...", "last_seen_at": "..." },
    "session": { "id": "ses_...", "event_count": 3, "started_at": "...", "last_seen_at": "..." },
    "resolution": {
      "method": "exact_identifier", "confidence": 91, "candidate_count": 1,
      "reason": "Resolved via browser_instance (confidence 91).",
      "matched_signals": ["os_family", "browser_engine", "device_class"],
      "changed_signals": [{ "signal": "browser_major_version", "from": "138", "to": "139" }],
      "conflicting_signals": [], "unknown_signals": ["device_memory_bucket"]
    },
    "network": {
      "classification": "public", "enrichment_status": "complete",
      "country_code": null, "asn": null, "organization": null,
      "is_vpn": null, "is_proxy": null, "is_tor": null, "is_hosting": null
    },
    "automation": { "webdriver": false, "headless_hint": false, "runtime_inconsistency": false },
    "risk": {
      "score": 65, "level": "high", "decision": "challenge",
      "signals": [
        { "code": "BRUTE_FORCE_SUSPECTED", "weight": 30,
          "description": "8 failed logins against this account from 1 source(s) in the last velocity window." }
      ]
    }
  }
}
Network fields are null by design
country_code, asn, is_vpn, is_proxy, is_tor, and is_hosting are reserved fields for IP geolocation/VPN/ proxy detection — no provider is bound yet, so they are always null, never guessed.

Calling from your own backend

Any HTTP-capable language works, including PHP/Laravel — there is no dedicated Sentriq PHP or Laravel package, so use your framework's standard HTTP client:

Http::withToken(config('services.sentriq.secret_key'))
    ->get('https://api.sentriq.example/v1/environments/env_xxx/devices');