API Reference
Everything the dashboard, the CLI and the MCP server do with tripwires goes through one HTTP API. The same API, with the same parameters and errors, is served by the hosted service and by a self-hosted control server.
Machine-readable spec
The full contract is published as OpenAPI 3.1: openapi.yaml. Browse every endpoint, parameter and schema in the interactive API explorer. Import it into Postman, Insomnia or a code generator. This page is a readable summary of it.
Base URL
| Hosted service | https://api.gettripwires.com |
| Self-hosted | Your control server, e.g. https://tripwire.internal.example.com |
Authentication
Send a scoped API key as a bearer token. Create one under
Settings → API Keys (or tripwire keys create),
granting only the scopes your integration needs. The secret is shown once.
Authorization: Bearer tw_<key_id>.<secret>
| Scope | Allows |
|---|---|
tripwire:read | List and read tripwires, download canary documents, read trips (per tripwire and the org-wide feed) |
tripwire:create | POST /tripwires |
tripwire:update | PATCH /tripwires/{id} (edit, renew, reset) |
tripwire:delete | DELETE /tripwires/{id} |
org:read | GET /orgs, GET /orgs/{id}/members, GET /auth/me |
API keys cannot reach billing, administration or key management, and a key can be restricted to namespaces — it never sees more than the user who created it.
Organizations
Every tripwire belongs to one organization. Send X-Org-ID: <org_id> to work in a shared
organization; leave it out to use your personal organization. GET /orgs lists the IDs you can
use. Namespace grants apply to every endpoint: a tripwire you cannot see is reported as
404, exactly as if it did not exist.
Errors
Every error is JSON with a single error field:
HTTP/1.1 400 Bad Request
{"error": "invalid since: must be an RFC3339 timestamp, e.g. 2026-01-02T15:04:05Z"}
| Status | Meaning |
|---|---|
400 | Invalid input: an unknown sort/status/order, a malformed timestamp, tag or limit, an invalid namespace, or a cursor the server did not issue |
401 | Missing, malformed, revoked or expired credential |
403 | Valid credential that is not allowed: the key lacks the required scope (the message names it), or you are not a member of X-Org-ID |
404 | Not found, or not visible to you |
5xx | Server-side failure; safe to retry reads with backoff |
Hosted service only: a request the API gateway rejects before it reaches the API (no
Authorization header, or a credential the authorizer refuses) gets the gateway's fixed body
— {"message":"Unauthorized"} (401) or {"message":"Forbidden"} (403).
Treat any 401/403 as an authentication failure whatever the body.
Pagination
List responses include next_cursor when there is more. Pass it back unchanged as
?cursor= and stop when it is absent. Cursors are opaque; anything else is a 400.
A page may hold fewer than limit items while next_cursor is still present —
keep going until it disappears.
Endpoints
| Method & path | Scope | Purpose |
|---|---|---|
GET /tripwires | tripwire:read | List tripwires |
POST /tripwires | tripwire:create | Create a tripwire |
GET /tripwires/{id} | tripwire:read | One tripwire, its connection details and 50 most recent trips |
PATCH /tripwires/{id} | tripwire:update | Edit, renew or reset |
DELETE /tripwires/{id} | tripwire:delete | Delete a tripwire and its trips |
GET /tripwires/{id}/download | tripwire:read | Short-lived link to a canary document |
GET /tripwires/{id}/trips | tripwire:read | One tripwire's trips |
GET /trips | tripwire:read | Org-wide trips feed |
GET /orgs | org:read | Your organizations and your role in each |
GET /orgs/{org_id}/members | org:read | Members, roles and namespace grants |
GET /auth/me | org:read | Who the credential belongs to |
GET /tripwires
| Parameter | Description |
|---|---|
q | Free text over name, technology, type and tag keys/values |
type, technology | Exact filters, e.g. type=tcp, technology=postgresql |
status | active | expired | tripped | untripped |
tag | key=value; repeat to AND several |
sort, order | created | name | expires | trips | last_tripped; asc | desc |
limit, cursor | Page size 1–500 (capped). Without limit every match is returned in one response — integrations should always page. |
curl -s -H "Authorization: Bearer $TRIPWIRE_API_KEY" -H "X-Org-ID: $ORG" \
"https://api.gettripwires.com/tripwires?status=tripped&sort=last_tripped&order=desc&limit=100"
POST /tripwires
curl -s -X POST -H "Authorization: Bearer $TRIPWIRE_API_KEY" -H "Content-Type: application/json" \
https://api.gettripwires.com/tripwires \
-d '{"name":"prod-db-decoy","technology":"postgresql","namespace":"prod/payments",
"tags":{"env":"prod"},"ttl":"720h"}'
Returns 201 with the new tripwire, including credentials or a trigger artifact where the
technology has one — for some technologies this is the only time they are returned in full.
namespace is a /-separated path of letters, digits, ., _ and
-; anything else is a 400.
PATCH /tripwires/{id}
Send only what changes: name, destination_url, namespace,
tags, expires_at (RFC3339, or "" to clear). "ttl":"720h"
renews the lease from now. "reset":true re-baselines the trip count; add
"purge":true to also delete the trip records.
The trip object
{
"trip_id": "trp_4f1c2a9b0d3e5f6a7b8c9d0e",
"timestamp": "2026-10-07T09:14:03Z",
"protocol": "postgresql",
"client_ip": "203.0.113.7",
"username": "svc_backup",
"database": "payments"
}
trip_id is stable: the same detection has the same id on every call and every endpoint
(including the trips embedded in GET /tripwires/{id}), so you can de-duplicate on it.
Depending on the protocol a trip may also carry qname, node_id,
edns_subnet, user_agent and path.
GET /tripwires/{id}/trips
One tripwire's trips, newest first.
| Parameter | Description |
|---|---|
since, until | Inclusive RFC3339 instants, e.g. 2026-10-01T00:00:00Z. since after until is a 400. |
limit, cursor | Default 50, maximum 500 |
GET /trips — org-wide feed
Every trip on every tripwire you can see in the active organization, newest first. Each item is a trip
object plus tripwire_id, tripwire_name and technology, so you do not
need a second call to know what was touched.
| Parameter | Description |
|---|---|
since, until | Inclusive RFC3339 window |
tripwire_id | Only this tripwire (404 if you cannot see it) |
protocol | Exact, lower-case, e.g. dns, postgresql, aws |
source_ip | Exact IPv4/IPv6 address |
limit, cursor | Default 50, maximum 500 |
curl -s -H "Authorization: Bearer $TRIPWIRE_API_KEY" -H "X-Org-ID: $ORG" \
"https://api.gettripwires.com/trips?since=2026-10-07T00:00:00Z&limit=200"
{
"trips": [
{
"trip_id": "trp_4f1c2a9b0d3e5f6a7b8c9d0e",
"timestamp": "2026-10-07T09:14:03Z",
"protocol": "postgresql",
"client_ip": "203.0.113.7",
"username": "svc_backup",
"tripwire_id": "9b2e6c1a-3f4d-4e8a-9c7b-1d2e3f4a5b6c",
"tripwire_name": "prod-db-decoy",
"technology": "postgresql"
}
],
"next_cursor": "eyJ2Ijox..."
}
Polling for new activity
- Remember the newest
timestampyou have processed. - Every few minutes, call
GET /trips?since=<that timestamp>and follownext_cursorto the end. - Skip any
trip_idyou have already handled (sinceis inclusive, so the boundary trip comes back once more).
On the hosted service the feed reads each member's trips through an index and merges them, so its cost grows with the number of members who own tripwires in the organization, not with the number of tripwires. Trips are retained for 90 days on the hosted service.
See also: the CLI (tripwire trips wraps the feed) and the MCP server.