Skip to content

Endpoints: LLM Credentials, Billing & Findings

Store an LLM provider API key for the org, encrypted at rest. Used by /analyze as a fallback when the request doesn’t include an inline api_key. Upserts — storing again for the same provider replaces the previous key. Requires org-admin.

Request body

{"provider": "anthropic", "api_key": "sk-..."}

Response 201

{"provider": "anthropic", "created_at": "2026-07-09T00:00:00Z"}

List providers configured for the org. Never returns the key itself.

{"credentials": [{"provider": "anthropic", "created_at": "2026-07-09T00:00:00Z"}]}

DELETE /orgs/{org_id}/llm-credentials/{provider}

Section titled “DELETE /orgs/{org_id}/llm-credentials/{provider}”

Remove a stored credential. 404 if none exists for that provider.


Server-side analysis (/analyze) can be capped by an optional per-org monthly LLM spend budget. There is no billing-provider (Stripe, etc.) integration — this is internal metering plus a hard block. No cap is set by default (unlimited).

Set or clear the org’s monthly LLM spend cap. Requires org-admin.

Request body

{"monthly_budget_usd": 50.0}

Pass "monthly_budget_usd": null to clear the cap (back to unlimited).

Response 200

{"monthly_budget_usd": 50.0}

Current-month LLM usage and remaining budget for the org, plus its metered resource usage and — on an enterprise plan — where it stands against its commitment. Requires org-viewer.

Response 200

{
"period": "2026-07",
"total_cost_usd": 12.340000,
"budget_usd": 50.0,
"remaining_usd": 37.660000,
"event_count": 84,
"resource_count": 5400,
"metered_cost_usd": 2.4,
"included_resources_per_month": 5000,
"included_resources_remaining": 0,
"overage_resources": 400,
"committed_amount_usd": 1500.0,
"contract_expired": false,
"demo_analyses_used": null,
"demo_analyses_remaining": null
}

budget_usd and remaining_usd are null when the org has no cap set. period is the current calendar month in UTC (YYYY-MM); usage resets automatically at the start of each month — there’s no rollover.

resource_count is metered for every org regardless of plan — only billing is gated. What metered_cost_usd then means depends on the plan:

Planmetered_cost_usd
freenull — never billed
paidThe degressive per-resource price for the whole resource_count, from the first resource
enterpriseThe overage beyond included_resources_per_month, charged flat at the cheapest configured tier — 0.0, not null, while inside the commitment

The four commitment fields (included_resources_per_month, included_resources_remaining, overage_resources, committed_amount_usd) are null/0 on every plan but enterprise, and included_resources_per_month is also null for an enterprise org whose contract sets no ceiling — which means unlimited, not zero.

demo_analyses_used/demo_analyses_remaining are populated only on a cloud deployment for a non-paying plan — null on self-hosted or on any paying plan, where no demo quota applies.


A platform-wide (not per-org) catalog of which (llm_backend, model) pairs POST /{analyzer}/{namespace}/analyze and POST /orgs/{org_id}/integrations/github will accept, plus manually-entered pricing used to cost each analyze call. Superadmin-managed.

Any org member. Returns only enabled rows — this is what the dashboard’s Analyze and GitHub Integration forms fetch to populate their model picker.

Response 200

{
"models": [
{
"llm_backend": "anthropic",
"model": "claude-sonnet-5",
"input_price_per_1m_usd": 3.0,
"output_price_per_1m_usd": 15.0,
"enabled": true,
"created_by": "u_123",
"created_at": "2026-07-15T09:00:00+00:00",
"updated_at": null
}
]
}

Superadmin only. The full catalog, including disabled rows. Optional ?enabled=true|false filter.


Superadmin only. Adds a model to the catalog.

Request bodyapplication/json

{
"llm_backend": "anthropic",
"model": "claude-sonnet-5",
"input_price_per_1m_usd": 3.0,
"output_price_per_1m_usd": 15.0,
"enabled": true
}
StatusDescription
201Created
409(llm_backend, model) already exists

Superadmin only. Partial update — send only the fields to change (typically input_price_per_1m_usd/output_price_per_1m_usd or enabled).


DELETE /admin/models/{llm_backend}/{model}

Section titled “DELETE /admin/models/{llm_backend}/{model}”

Superadmin only. Removes the model from the catalog. Any org still configured to use it (a saved GitHub integration, or a client that hardcodes it in /analyze calls) starts getting 400s the next time it’s used.


Every finding an analysis produces gets a persistent triage record, independent of the raw LLM output — keyed by (resource_hash, rule_id) within the caller’s org. There is no create endpoint: records appear automatically whenever an analysis is submitted (via either PUT /resource/{hash}/analysis or POST .../analyze). Re-analysing the same resource only refreshes last_seen/occurrence_count — it never resets a status you’ve already set.

List/filter finding records for the caller’s org. Requires member.

ParameterTypeDefaultDescription
statusstringopen, needs_review, acknowledged, suppressed, or false_positive
severitystringe.g. CRITICAL, HIGH
rule_idstringExact rule ID
limitint100Page size (1–1000)
offsetint0Pagination offset

Response 200

{
"findings": [
{
"resource_hash": "a3f1c2d4e5b6",
"rule_id": "TFSEC-040",
"resource_address": "aws_db_instance.orders",
"severity": "HIGH",
"title": "S3 bucket allows public read",
"status": "open",
"first_seen": "2026-07-09T00:00:00Z",
"last_seen": "2026-07-09T00:00:00Z",
"occurrence_count": 1,
"note": null,
"updated_by": null,
"updated_at": null
}
]
}

Aggregated counts for the caller’s org. Requires member.

ParameterTypeDefaultDescription
daysint30Window for new_in_period/resolved_in_period (1–365)

Response 200

{
"period_days": 30,
"total": 42,
"by_status": {"open": 28, "needs_review": 2, "acknowledged": 5, "suppressed": 6, "false_positive": 1},
"by_severity_open": {"CRITICAL": 1, "HIGH": 10, "MEDIUM": 15, "LOW": 4},
"new_in_period": 8,
"resolved_in_period": 3
}

by_severity_open only counts findings currently status: "open". resolved_in_period counts findings whose status left open for anything other than needs_review within the window — a finding awaiting a human decision isn’t “resolved” just because it’s no longer open.

Set a finding’s triage status. Requires member — triage is treated as routine day-to-day work, not an admin action.

Request body

{"status": "suppressed", "note": "accepted risk — internal tool, not internet-facing"}

status is one of open, needs_review, acknowledged, suppressed, false_positive. Setting status: "open" reopens a previously triaged finding. note is optional. Setting needs_review fires this org’s review webhook, if one is configured — the same notification an auto-flagged finding fires at creation time.

Response 200 — the updated finding record (same shape as GET /findings)

StatusDescription
200Status updated
404No finding record exists for this (resource_hash, rule_id) pair

An org-level outbound notification fired whenever a finding enters needs_review — automatically (compute_initial_status, on low LLM confidence or a rule marked requires_review) or manually (PATCH /findings/{resource_hash}/{rule_id} above). This is the only outbound notification in this codebase — there’s no generic email/Slack system, and this isn’t one either; it’s scoped to exactly this one event. Delivery is best-effort: a slow or failing endpoint never fails the request that triggered it, and there’s no retry queue.

Reuses the same (org_id, provider) shape as the GitHub integration (provider: "review_webhook") — one config per org, admin-only, same trust boundary as custom rules and LLM credentials.

PUT /orgs/{org_id}/integrations/review-webhook

Section titled “PUT /orgs/{org_id}/integrations/review-webhook”

Create or update. Requires admin role. Upserts — calling again replaces the URL/secret/enabled state entirely; omitting secret clears any previously configured signing secret rather than leaving the old one in place.

Request body

{"url": "https://example.com/hooks/opentremor", "secret": "whsec_...", "enabled": true}
FieldTypeDefaultDescription
urlstringPOSTed with a JSON body on every needs_review transition
secretstring | nullnullIf set, the request is signed with HMAC-SHA256 over the raw body and sent as X-OpenTremor-Signature: sha256=<hex> — the same scheme GitHub itself uses for inbound webhooks, just outbound here. Encrypted at rest, never returned by GET.
enabledbooltrue

Response 200

{"url": "https://example.com/hooks/opentremor", "enabled": true, "has_secret": true, "created_at": "2026-01-01T00:00:00Z", "updated_at": null}

has_secret is the only signal GET gives about the secret — the value itself is never redisplayed after saving.

GET /orgs/{org_id}/integrations/review-webhook

Section titled “GET /orgs/{org_id}/integrations/review-webhook”

Current config. Requires admin role. 404 if none configured.

DELETE /orgs/{org_id}/integrations/review-webhook

Section titled “DELETE /orgs/{org_id}/integrations/review-webhook”

Remove it. Requires admin role. 404 if none configured.

Payload sent on delivery

{
"event": "finding.needs_review",
"org_id": "8f2a...",
"resource_hash": "a3f1c2d4e5b6",
"rule_id": "approved-module-only",
"resource_address": "aws_db_instance.orders",
"severity": "HIGH",
"title": "Direct Resource Instead Of Approved Module",
"namespaces": ["production-deploy-42"],
"trigger": "auto",
"timestamp": "2026-07-28T00:00:00Z"
}

trigger is "auto" (routed at analysis time) or "manual" (a human PATCHed the status directly).