The AIRGAP public API. REST over HTTPS, plus a server-sent-events stream. JSON in, JSON out. Versioned at /api/v1/.
Authentication
Every request must carry a bearer token in the Authorization header:
Authorization: Bearer airgap_<64-hex-chars>
Mint a token at airgap.finance/settings → API Tokens → New Token. The plaintext token is shown once at creation time — copy it then. Tokens are stored hashed (SHA-256); the plaintext never round-trips again. Format: the literal prefix airgap_ followed by 32 random bytes hex-encoded (71 characters total).
Tokens are scoped to your user. Authenticated requests are rate-limited at 60 requests per minute per token in a sliding window. Anonymous (bad-auth) requests are limited at 10 per minute per IP to keep token-guessing cheap. When you hit the limit:
- Status
429. - Response:
{"error": "Rate limit exceeded"}. - Headers:
X-RateLimit-Remaining,X-RateLimit-Reset(seconds until window resets),Retry-After.
Revoke a token any time from the same Settings panel. Once revoked, the next request with that token returns 401 Unauthorized.
GET /api/v1/agents
List your agents. Returns a compact summary suitable for sidebars and dashboards.
Method. GET
Path. /api/v1/agents
Auth. Required.
Query params. None.
Request body. None.
Response 200.
{
"agents": [
{
"id": "uuid",
"name": "string",
"type": "x | farcaster | launch-scanner | wallet-watcher | account-stalker | contract-monitor | custom | discord | telegram",
"status": "idle | running | paused | error",
"intensity": "light | standard | aggressive",
"signal_count": 0,
"last_run": "2026-05-10T12:34:56.000Z | null"
}
]
}
Agents are returned newest-first (by created_at).
Errors. 401 (missing/invalid token), 429 (rate limited).
Example.
curl -H "Authorization: Bearer airgap_xxxxxxxx" \
https://airgap.finance/api/v1/agents
POST /api/v1/agents/:id/pause
Pause a running agent. The worker stops polling on the next tick. Idempotent — pausing an already-paused agent is a no-op.
Method. POST
Path. /api/v1/agents/{id}/pause
Auth. Required.
Path params. id — the agent UUID.
Query params. None.
Request body. None.
Response 200. The updated agent summary.
{
"agent": {
"id": "uuid",
"name": "string",
"type": "...",
"status": "paused",
"intensity": "...",
"signal_count": 0,
"last_run": "..."
}
}
Errors.
401— missing/invalid token.404— agent not found, or not owned by you.429— rate limited.501— agent type istelegram. Telegram user-account agents require a self-hosted process; pause/resume via the API is not supported. Use other agent types or contact support.
Example.
curl -X POST \
-H "Authorization: Bearer airgap_xxxxxxxx" \
https://airgap.finance/api/v1/agents/AGENT_ID/pause
POST /api/v1/agents/:id/resume
Resume a paused agent. Re-runs the access check at resume time — if your subscription lapsed or your token balance dropped below threshold, the resume is rejected. On success, status moves to running and any prior error_message is cleared.
Method. POST
Path. /api/v1/agents/{id}/resume
Auth. Required.
Path params. id — the agent UUID.
Query params. None.
Request body. None.
Response 200. The updated agent summary, with status: "running".
{
"agent": {
"id": "uuid",
"name": "string",
"type": "...",
"status": "running",
"intensity": "...",
"signal_count": 0,
"last_run": "..."
}
}
Errors.
401— missing/invalid token, or the user record backing the token is gone.403— access denied. Subscription inactive, or token holding fell below the holder threshold for a holder-only tier.404— agent not found, or not owned by you.429— rate limited.501— agent type istelegram; not supported via API (see pause).
Example.
curl -X POST \
-H "Authorization: Bearer airgap_xxxxxxxx" \
https://airgap.finance/api/v1/agents/AGENT_ID/resume
GET /api/v1/signals
List signals delivered to your account, newest first. Supports filtering and pagination.
Method. GET
Path. /api/v1/signals
Auth. Required.
Query params.
| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| agent_id | UUID | optional | Restrict to one agent. |
| agent_type | string | optional | Restrict to one agent type (e.g. wallet-watcher). |
| min_confidence | number 0–1 | optional | Floor on AI confidence. Coerced from string. |
| limit | int 1–100 | optional | Page size. Default 50. |
| offset | int ≥ 0 | optional | Page offset. Default 0. |
| after | ISO 8601 datetime | optional | Only signals with created_at >= after. |
Invalid params return 400 with a zod-formatted details object.
Request body. None.
Response 200.
{
"signals": [
{
"id": "uuid",
"user_id": "uuid",
"agent_id": "uuid",
"agent_type": "wallet-watcher",
"title": "string",
"body": "string",
"confidence": 0.82,
"source_url": "https://...",
"source_platform": "string",
"rich_data": { },
"enrichments": { },
"is_house_signal": false,
"token_ca": "0x... | null",
"created_at": "2026-05-10T12:34:56.000Z"
}
],
"total": 1234,
"has_more": true
}
rich_data is shaped by agent_type — see the Signal*RichData types in the source for narrowed shapes (Telegram, X, Farcaster, Launch Scanner, Wallet Watcher, Account Stalker, Contract Monitor, Custom).
total is the exact count for the current filter set; has_more is true when total > offset + signals.length.
Errors.
400— invalid query. Body:{"error": "Invalid query", "details": { ... }}.401— missing/invalid token.429— rate limited.500— query failed. Body:{"error": "Query failed"}.
Example.
curl -G \
-H "Authorization: Bearer airgap_xxxxxxxx" \
--data-urlencode "agent_type=wallet-watcher" \
--data-urlencode "min_confidence=0.7" \
--data-urlencode "limit=20" \
https://airgap.finance/api/v1/signals
GET /api/v1/signals/:id
Fetch one signal in full. Returns every column on the row, including read/dismissed flags and the full rich_data/enrichments blobs.
Method. GET
Path. /api/v1/signals/{id}
Auth. Required.
Path params. id — the signal UUID.
Query params. None.
Request body. None.
Response 200.
{
"signal": {
"id": "uuid",
"user_id": "uuid | null",
"agent_id": "uuid | null",
"agent_type": "...",
"title": "string",
"body": "string",
"confidence": 0.82,
"source_url": "...",
"source_platform": "...",
"rich_data": { },
"enrichments": { },
"is_house_signal": false,
"read": false,
"dismissed": false,
"token_ca": "0x... | null",
"created_at": "..."
}
}
Errors.
401— missing/invalid token.404— signal not found, or not owned by you.429— rate limited.
Example.
curl -H "Authorization: Bearer airgap_xxxxxxxx" \
https://airgap.finance/api/v1/signals/SIGNAL_ID
GET /api/v1/stream
Server-Sent Events stream of new signals for the authenticated user. Subscribes to Supabase Realtime server-side and forwards inserts as SSE.
Method. GET
Path. /api/v1/stream
Auth. Required. Note: this endpoint authenticates but does not apply the per-minute rate limit — it's a long-lived single connection.
Query params. None.
Request body. None.
Response. 200 with Content-Type: text/event-stream. The stream stays open until the client disconnects.
Event shape.
- On connect, the server sends a single SSE comment to flush headers and prove the connection is live:
: connected - Every 20 seconds, a heartbeat comment keeps proxies from killing the connection:
: ping - For each new signal inserted into your account, the server emits a named event:
Theevent: signal data: { ...signal row JSON... }datapayload is the raw row from thesignalstable — same shape as the object returned byGET /api/v1/signals/:idundersignal, minus the wrapper.
Concurrency. No hard limit is enforced, but channels are scoped per-token, so a second token from the same user gets its own independent stream. Don't open dozens of these — one per process is the intended pattern.
Errors.
401— missing/invalid token. Returned as a plainUnauthorizedbody, not JSON.503— server misconfigured (Supabase URL or service-role key missing in the deployment).
Example.
curl -N \
-H "Authorization: Bearer airgap_xxxxxxxx" \
-H "Accept: text/event-stream" \
https://airgap.finance/api/v1/stream
-N disables curl's output buffering so events appear in real time.
