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 servicehttps://api.gettripwires.com
Self-hostedYour 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>
ScopeAllows
tripwire:readList and read tripwires, download canary documents, read trips (per tripwire and the org-wide feed)
tripwire:createPOST /tripwires
tripwire:updatePATCH /tripwires/{id} (edit, renew, reset)
tripwire:deleteDELETE /tripwires/{id}
org:readGET /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"}
StatusMeaning
400Invalid input: an unknown sort/status/order, a malformed timestamp, tag or limit, an invalid namespace, or a cursor the server did not issue
401Missing, malformed, revoked or expired credential
403Valid 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
404Not found, or not visible to you
5xxServer-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 & pathScopePurpose
GET /tripwirestripwire:readList tripwires
POST /tripwirestripwire:createCreate a tripwire
GET /tripwires/{id}tripwire:readOne tripwire, its connection details and 50 most recent trips
PATCH /tripwires/{id}tripwire:updateEdit, renew or reset
DELETE /tripwires/{id}tripwire:deleteDelete a tripwire and its trips
GET /tripwires/{id}/downloadtripwire:readShort-lived link to a canary document
GET /tripwires/{id}/tripstripwire:readOne tripwire's trips
GET /tripstripwire:readOrg-wide trips feed
GET /orgsorg:readYour organizations and your role in each
GET /orgs/{org_id}/membersorg:readMembers, roles and namespace grants
GET /auth/meorg:readWho the credential belongs to

GET /tripwires

ParameterDescription
qFree text over name, technology, type and tag keys/values
type, technologyExact filters, e.g. type=tcp, technology=postgresql
statusactive | expired | tripped | untripped
tagkey=value; repeat to AND several
sort, ordercreated | name | expires | trips | last_tripped; asc | desc
limit, cursorPage 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.

ParameterDescription
since, untilInclusive RFC3339 instants, e.g. 2026-10-01T00:00:00Z. since after until is a 400.
limit, cursorDefault 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.

ParameterDescription
since, untilInclusive RFC3339 window
tripwire_idOnly this tripwire (404 if you cannot see it)
protocolExact, lower-case, e.g. dns, postgresql, aws
source_ipExact IPv4/IPv6 address
limit, cursorDefault 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

  1. Remember the newest timestamp you have processed.
  2. Every few minutes, call GET /trips?since=<that timestamp> and follow next_cursor to the end.
  3. Skip any trip_id you have already handled (since is 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.