openapi: 3.2.0 info: title: HookPulse Endpoints API version: 2d690e87 description: 'Dead-man switch for webhooks/cron. Index: GET /api/.' servers: - url: https://hookpulse.net tags: - name: Endpoints paths: /api/endpoints: get: operationId: list_endpoints summary: Lists the owner's monitors, with the state of each one description: 'Returns: { endpoints[{id,name,interval_sec,alert_to,alert_url,last_event_at,last_status,last_latency_ms,miss_count,alerted_at,active,healthy,overdue,waiting_first_ping,created_at,ingest_url,token?,status_url?,events_url?,curl_example?,templates?}], guest, billing?{provider,mode,network,chain_id,pay_to,homolog,dev,dev_gate,facilitator,asset,asset_address,faucet,wallets,product,free_max_endpoints,free_min_interval_sec,free_email_alerts,prices,usage,trial} }' security: - bearerAuth: [] responses: '200': description: '{ endpoints[{id,name,interval_sec,alert_to,alert_url,last_event_at,last_status,last_latency_ms,miss_count,alerted_at,active,healthy,overdue,waiting_first_ping,created_at,ingest_url,token?,status_url?,events_url?,curl_example?,templates?}], guest, billing?{provider,mode,network,chain_id,pay_to,homolog,dev,dev_gate,facilitator,asset,asset_address,faucet,wallets,product,free_max_endpoints,free_min_interval_sec,free_email_alerts,prices,usage,trial} }' content: application/json: schema: type: object properties: endpoints: type: array items: $ref: '#/components/schemas/Monitor' description: The owner's monitors, without the secret fields. guest: type: string description: The guest that owns this list. nullable: true billing: allOf: - $ref: '#/components/schemas/Billing' description: Prices and allowance, to decide before creating the next one. required: - endpoints - guest '401': description: No credential, or an invalid one. See this endpoint's auth. tags: - Endpoints post: operationId: create_endpoint summary: 'Creates a dead-man switch: silence beyond the interval becomes an alert' description: 'This response is the only one that shows the monitor''s `token` and the `templates` — keep them. The second monitor, or an interval below the free minimum, answers **402 with `accepts[]`**: pay and repeat. A miss alerts at most once per 24h (or per interval, if it is longer). Returns: { id, name, interval_sec, alert_to, alert_url, last_event_at, last_status, last_latency_ms, miss_count, alerted_at, active, healthy, overdue, waiting_first_ping, created_at, ingest_url, token?, status_url?, events_url?, curl_example?, templates?{ingest_curl,ingest_cron,ingest_n8n,miss_json,miss_url_hint} }' security: - bearerAuth: [] requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: Name to recognise the monitor in the alert. interval_sec: type: integer description: Tolerated silence, in seconds. Below the free minimum, it costs. alert_to: type: string description: E-mail to alert on a miss; without it, the account is alerted. alert_url: type: string description: Public HTTPS URL that receives a POST on a miss (Slack Incoming, Discord, n8n). required: - name example: name: prod cron interval_sec: 900 alert_to: optional@email.com alert_url: https://n8n.example/webhook/hp responses: '200': description: '{ id, name, interval_sec, alert_to, alert_url, last_event_at, last_status, last_latency_ms, miss_count, alerted_at, active, healthy, overdue, waiting_first_ping, created_at, ingest_url, token?, status_url?, events_url?, curl_example?, templates?{ingest_curl,ingest_cron,ingest_n8n,miss_json,miss_url_hint} }' content: application/json: schema: $ref: '#/components/schemas/Monitor' '400': description: Empty name, invalid interval or an `alert_url` that is not public HTTPS. '401': description: No credential, or an invalid one. See this endpoint's auth. '402': description: 'Quota exceeded. The response carries `accepts[]` (x402, USDC on Base): pay and repeat the same call with `X-PAYMENT`.' tags: - Endpoints /api/endpoints/{id}: get: operationId: get_endpoint summary: State of one monitor — accepts the owner's token or the monitor's own token description: 'The monitor token only reads: it lets you put the state on a third-party dashboard without handing over the owner''s credential. Returns: { id, name, interval_sec, alert_to, alert_url, last_event_at, last_status, last_latency_ms, miss_count, alerted_at, active, healthy, overdue, waiting_first_ping, created_at, ingest_url, token?, status_url?, events_url?, curl_example?, templates?{ingest_curl,ingest_cron,ingest_n8n,miss_json,miss_url_hint} }' security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string - name: token in: query required: false schema: type: string description: Monitor token, alternative to the `X-Hook-Token` header. responses: '200': description: '{ id, name, interval_sec, alert_to, alert_url, last_event_at, last_status, last_latency_ms, miss_count, alerted_at, active, healthy, overdue, waiting_first_ping, created_at, ingest_url, token?, status_url?, events_url?, curl_example?, templates?{ingest_curl,ingest_cron,ingest_n8n,miss_json,miss_url_hint} }' content: application/json: schema: $ref: '#/components/schemas/Monitor' '401': description: No credential, or an invalid one. See this endpoint's auth. '404': description: The resource does not exist (or is not yours — the API does not tell the two apart on purpose). tags: - Endpoints patch: operationId: patch_api_endpoints_by_id summary: Changes the monitor's name, interval or alert channels description: 'Lowering the interval below the free minimum costs: the response becomes 402 with `accepts[]` until paid. Returns: { ok, endpoint{id,name,interval_sec,alert_to,alert_url,last_event_at,last_status,last_latency_ms,miss_count,alerted_at,active,healthy,overdue,waiting_first_ping,created_at,ingest_url,token?,status_url?,events_url?,curl_example?,templates?} }' security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: New monitor name, as it shows in the alert. interval_sec: type: integer description: New tolerated silence, in seconds. alert_to: type: string description: New alert e-mail; `null` turns it off. alert_url: type: string description: New alert URL; `null` turns it off. example: name: … interval_sec: 300 alert_to: null alert_url: null responses: '200': description: '{ ok, endpoint{id,name,interval_sec,alert_to,alert_url,last_event_at,last_status,last_latency_ms,miss_count,alerted_at,active,healthy,overdue,waiting_first_ping,created_at,ingest_url,token?,status_url?,events_url?,curl_example?,templates?} }' content: application/json: schema: type: object properties: ok: type: boolean description: Always `true`. endpoint: allOf: - $ref: '#/components/schemas/Monitor' description: The monitor with the change applied. required: - ok - endpoint '400': description: Invalid field in the body. '401': description: No credential, or an invalid one. See this endpoint's auth. '402': description: 'Quota exceeded. The response carries `accepts[]` (x402, USDC on Base): pay and repeat the same call with `X-PAYMENT`.' '404': description: The resource does not exist (or is not yours — the API does not tell the two apart on purpose). tags: - Endpoints delete: operationId: delete_endpoint summary: Deactivates the owner's monitor; it stops taking pings and alerting description: 'Returns: { ok }' security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string responses: '200': description: '{ ok }' content: application/json: schema: $ref: '#/components/schemas/Ok' '401': description: No credential, or an invalid one. See this endpoint's auth. '404': description: The resource does not exist (or is not yours — the API does not tell the two apart on purpose). tags: - Endpoints /api/endpoints/{id}/events: get: operationId: list_events summary: The latest pings received at this monitor's ingest description: 'Returns: { events[{at,status,latency_ms,source}] }' security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string - name: token in: query required: false schema: type: string description: Monitor token, alternative to the `X-Hook-Token` header. responses: '200': description: '{ events[{at,status,latency_ms,source}] }' content: application/json: schema: type: object properties: events: type: array items: $ref: '#/components/schemas/Ping' description: The most recent pings, newest first. required: - events '401': description: No credential, or an invalid one. See this endpoint's auth. '404': description: The resource does not exist (or is not yours — the API does not tell the two apart on purpose). tags: - Endpoints components: schemas: Ping: type: object properties: at: type: string description: When it arrived (UTC). status: type: integer description: Status reported by the caller, when it did. nullable: true latency_ms: type: integer description: Latency reported by the caller, in ms. nullable: true source: type: string description: 'Where it came from: `get` or `post`.' nullable: true required: - at - status - latency_ms - source description: A ping received at the ingest — the proof of life. Billing: type: object properties: provider: type: string description: Always `x402` — the only billing protocol accepted. mode: type: string description: 'Seller mode: `live` charges for real, `dev` lets calls through unpaid.' network: type: string description: 'USDC network: `base` in production, `base-sepolia` in staging.' chain_id: type: integer description: EVM chain ID of the network above, so the wallet signs on the right chain. pay_to: type: string description: Address that receives the payment. nullable: true homolog: type: boolean description: 'Staging seam on: the loop can be closed without spending USDC.' dev: type: boolean description: 'Development mode: the 402 is simulated.' dev_gate: type: string description: How dev mode is unlocked, when it exists. nullable: true facilitator: type: string description: URL of the facilitator that verifies and settles the payment. asset: type: string description: Accepted currency — always `USDC`. asset_address: type: string description: USDC contract on the network above. faucet: type: string description: Test-USDC faucet; only on base-sepolia. nullable: true wallets: type: object description: Links to wallets that speak x402 (metamask, coinbase, base_app). product: type: string description: Name of the product charging. free_max_endpoints: type: integer description: Free monitors per owner. free_min_interval_sec: type: integer description: Shortest interval that is still free. Below it, it costs. free_email_alerts: type: integer description: Free e-mail alert registrations; the rest is paid (it is SES cost per miss). prices: allOf: - $ref: '#/components/schemas/Precos' description: What each paid action costs, in USD. usage: type: object description: How much of the allowance the owner has used. trial: allOf: - $ref: '#/components/schemas/Trial' description: The account's trial, when there is a session. required: - provider - mode - network - chain_id - pay_to - homolog - dev - dev_gate - facilitator - asset - asset_address - faucet - wallets - product - free_max_endpoints - free_min_interval_sec - free_email_alerts - prices - usage - trial description: 'Everything that decides whether the next call will cost: x402 configuration, allowance, prices and trial.' Trial: type: object properties: days: type: integer description: Trial length in days. active: type: boolean description: Whether it is in force now. days_left: type: integer description: How many days remain. ends_at: type: string description: When it ends (UTC). nullable: true granted: type: boolean description: '`true` when THIS call granted the trial.' required: - days - active - ends_at description: The period without the usage paywall that confirming the e-mail grants. It is the alternative to paying. Precos: type: object properties: extra_endpoint_usd: type: number description: Monitor beyond the allowance. fast_interval_usd: type: number description: Interval below the free minimum. email_alert_usd: type: number description: E-mail alert registration beyond the first. contact_agent_usd: type: number description: Agent contact. required: - extra_endpoint_usd - fast_interval_usd - email_alert_usd - contact_agent_usd description: Prices in force, in dollars. Read them here, not from the documentation. Monitor: type: object properties: id: type: string description: Monitor ID; it is the `:id` of the ingest URL. name: type: string description: Name you gave it, to recognise it in the alert. interval_sec: type: integer description: Tolerated silence, in seconds. Past that, it is a miss. alert_to: type: string description: E-mail alerted on a miss. nullable: true alert_url: type: string description: HTTPS URL that receives a POST on a miss (Slack, Discord, n8n). nullable: true last_event_at: type: string description: Last ping received (UTC); `null` while it never pinged. nullable: true last_status: type: integer description: HTTP status the last ping sent, when it did. nullable: true last_latency_ms: type: integer description: Latency reported in the last ping, in ms. nullable: true miss_count: type: integer description: How many times this monitor has gone silent. alerted_at: type: string description: When the last alert went out — it is what holds the 1 alert/24h cap. nullable: true active: type: boolean description: Whether the monitor is on. healthy: type: boolean description: '`true` when it has pinged at least once and is not overdue.' overdue: type: boolean description: '`true` when the silence passed `interval_sec`.' waiting_first_ping: type: boolean description: '`true` while it never pinged. Neither healthy nor overdue: nobody has wired it yet.' created_at: type: string description: When the monitor was created (UTC). ingest_url: type: string description: The URL your cron/webhook calls to prove life. token: type: string description: Read token of this monitor. Only comes on creation and to the owner. status_url: type: string description: Status of this monitor with the token already in the query. events_url: type: string description: Latest pings with the token already in the query. curl_example: type: string description: The ingest `curl`, ready to paste in the cron. templates: allOf: - $ref: '#/components/schemas/Templates' description: Ingest snippets and the alert body, with this monitor already in them. required: - id - name - interval_sec - alert_to - alert_url - last_event_at - last_status - last_latency_ms - miss_count - alerted_at - active - healthy - overdue - waiting_first_ping - created_at - ingest_url description: 'A dead-man switch: the thing you make ping. If the ping stops for longer than `interval_sec`, it becomes `overdue` and the alert goes out.' Templates: type: object properties: ingest_curl: type: string description: A `curl` that works as proof of life. ingest_cron: type: string description: The equivalent crontab line. ingest_n8n: type: string description: How to call the ingest from n8n. miss_json: type: string description: The exact JSON we POST to `alert_url` when the silence becomes a miss. miss_url_hint: type: string description: What works as `alert_url` — public HTTPS only. required: - ingest_curl - ingest_cron - ingest_n8n - miss_json - miss_url_hint description: How to ping and what we send when it fails. It is what saves guessing the format. Ok: type: object properties: ok: type: boolean description: Always `true` — failure comes as a 4xx/5xx status, not as `ok:false`. required: - ok description: Write confirmation with no body of its own to return. securitySchemes: bearerAuth: type: http scheme: bearer description: 'Guest token (`POST /api/guest`) in `X-Guest-Token: hp_…` or `Authorization: Bearer hp_…`. A `sess_…` session also works.'