Docs / API reference

API reference

The public HTTP API: accounts and tokens, usage, public keys, bundle and license downloads, ingest, and the Intel files. JSON in, JSON out, base URL https://protet.io unless stated.

Status. Account, token, usage, public-key and Intel endpoints are live. The ingest and findings endpoints on ingress.protet.io accept requests, but the path from ingested events to a verdict is being fixed (see the Action page). Do not build on a verdict from them yet.

Conventions

  • Request bodies are JSON with Content-Type: application/json and at most 64 KiB.
  • Session auth. POST /auth/login returns {"access_token": "...", "token_type": "bearer"} and also sets an HttpOnly cs_session cookie. Send the token as Authorization: Bearer <access_token>. It lives for 1 hour. Changing your password invalidates other sessions.
  • Machine auth. The ingest refresh token (from POST /portal/ingest-token) goes in Authorization: Bearer ... too. It is accepted by /v1/ingest/refresh and /v1/usage. The pull token goes in x-license-key and is accepted by /v1/bundle.
  • Errors are {"detail": "..."} (validation errors: a list under detail). 401 not authenticated or bad credentials, 403 not allowed for your plan or suspended, 404 not found, 422 invalid body, 429 rate limited, 503 temporarily unavailable.
  • Rate limits (per hour): 5 sign-ups per source address; 5 verification re-sends and 5 password-reset requests per source address; 10 logins per email and source address. Past the limit you get 429; wait and retry.

Account

RequestBodyResponse
POST /auth/signupemail, password (8 to 256 characters), name (optional){"status": "pending_verification", "customer_id": "..."}. Same response whether or not the address exists.
POST /auth/verifytoken from the email link{"status": "active", "license": null, "pull_token": "..."}
POST /auth/resend-verificationemail{"status": "ok"}, always.
POST /auth/loginemail, password{"access_token": "...", "token_type": "bearer"}
POST /auth/logout{"ok": true}; clears the cookie.
GET /auth/me{"user_id": "...", "role": "...", "customer_id": "..."}
POST /auth/change-passwordcurrent_password, new_password{"status": "ok"}
POST /auth/forgot-passwordemail{"status": "ok"}, always. A reset link goes to the address if it is registered.
POST /auth/reset-passwordtoken, new_password{"status": "ok"}

Tokens and portal

These need session auth. A cloud-plan account mints an ingest token; an on-prem account mints pull tokens.

RequestResponse
POST /portal/ingest-token{"token": "..."}: a refresh token, valid 90 days, shown once. Cloud plans only.
POST /portal/ingest-token/revoke-all204. Every refresh token minted before this call stops working at its next refresh; mint a new one afterwards. Cloud plans.
GET /portal/entitlementsYour plan: tier with key, name, description, capabilities, deployment_mode and gateway_url.
GET /portal/license-summary{"has_license": false} on cloud plans; on-prem: issue and expiry dates, days_remaining, features, limits.
GET /portal/pull-tokensList of {"id", "label", "revoked_at"}. For on-prem bundle pulls.
POST /portal/pull-tokensBody {"label": "ci"}. Returns the token once, in token. For on-prem bundle pulls.
POST /portal/pull-tokens/{id}/revokeThe token with revoked_at set. It stops working immediately.
curl -s -X POST https://protet.io/portal/ingest-token -H "Authorization: Bearer $TOKEN"

Usage

GET /v1/usage with a session token or your ingest refresh token. Cloud plans only (others get 403). If the metering service cannot be reached you get 503.

curl -H "Authorization: Bearer $PROTET_TOKEN" https://protet.io/v1/usage
{
  "period": "2026-10",
  "resets_at": "2026-11-01T00:00:00Z",
  "sessions": { "used": 12, "limit": 500 }
}

Ingest and findings (preview)

RequestAuthDescription
POST https://protet.io/v1/ingest/refreshrefresh tokenReturns {"token": "<access token>", "expires_in": 3600}. Checks your account is active every time; suspended accounts get no new access token.
POST https://ingress.protet.io/v1/ingestaccess tokenBody {"events": [...]}. Returns {"received", "accepted", "rejected_quota"}. Event shape and the 422 rule are on Integrations.
GET https://ingress.protet.io/v1/findings/stream?session=<id>access tokenServer-sent events carrying findings for a session. Send the access token, not the refresh token. At most 10 concurrent streams per account; more get 429. Last-Event-ID resumes. Rejects missing tokens with 401.

The old https://protet.io/v1/findings/stream no longer exists and returns 404.

Keys, license and bundle

RequestAuthDescription
GET /v1/public-keysnonePublic Ed25519 keys, base64. bundle and license at the top level, and keys[] with purpose, kid and public_key.
GET /v1/licensesessionYour signed license.json (on-prem). 404 if you have none.
GET /v1/bundlepull token in x-license-key200 with bundle.tar.gz; 202 while a new bundle is built (retry); 401 bad or revoked token; 403 no plan assigned.
curl -s https://protet.io/v1/public-keys | python3 -m json.tool

Intel files

Public, unauthenticated, cached for 5 minutes. Full field reference and examples: Intel feed guide.

GET /intel/v1/all/findings.ndjson
GET /intel/v1/npm/findings.ndjson
GET /intel/v1/all/feed.atom
GET /intel/v1/npm/feed.atom
GET /intel/v1/npm/osv/PROTET-NPM-YYYY-N.json
GET /intel/v1/stats.json