openapi: 3.0.3
info:
  title: AlertKick API
  version: 0.2.0
  description: |
    HTTP API for AlertKick uptime monitoring, heartbeats, and alerting.

    ## Authentication

    Most endpoints take an account API key in the `X-API-Key` header
    (`ak_...`). Create one in **AlertKick → Settings → API Keys**. Keys are
    scoped to a single tenant subdomain: call `https://{tenant}.alertkick.com`
    with a key created on that tenant.

    Heartbeat pings are the exception: `/hb/ping/{uuid}` takes the
    heartbeat's own scoped key in the `X-Heartbeat-Key` header (`hb....`),
    which is safe to embed in cron jobs and scripts because it can only
    record pings for that one heartbeat.

    ## One-line heartbeat provisioning

    `GET /hb/auto/{slug}` creates a heartbeat on first ping and just pings
    it afterwards, so a script or an AI agent can add monitoring in a
    single line:

    ```
    curl -fsS -H "X-API-Key: $ALERTKICK_API_KEY" \
      "https://acme.alertkick.com/api/v1/hb/auto/nightly-backup?interval=86400&grace=3600"
    ```
  contact:
    name: AlertKick
    url: https://alertkick.com
servers:
  - url: https://{tenant}.alertkick.com/api/v1
    description: Production
    variables:
      tenant:
        default: demo
        description: Your tenant subdomain

tags:
  - name: Heartbeats
    description: >
      Dead-man's-switch monitoring for cron jobs and scheduled tasks: the job
      pings on success, and a missing ping past the grace period alerts.
  - name: Monitors
    description: >
      Active checks: HTTP/API endpoints, DNS records, TCP ports, TLS
      certificate expiry, and domain registration expiry.
  - name: Alerts
    description: Query, acknowledge, and resolve alerts.
  - name: Hosts
    description: Servers monitored by the AlertKick agent.

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Account API key (`ak_...`) from Settings → API Keys.
    heartbeatKey:
      type: apiKey
      in: header
      name: X-Heartbeat-Key
      description: >
        Per-heartbeat ping key (`hb....`), returned when the heartbeat is
        created. Only valid for `/hb/ping/{uuid}` on that heartbeat.
  schemas:
    Heartbeat:
      type: object
      properties:
        uuid:
          type: string
          example: d1nq8sg44a5g5h3k2b7g
        slug:
          type: string
          description: >
            Set for heartbeats created through `/hb/auto/{slug}`; empty for
            heartbeats created in the dashboard.
          example: nightly-backup
        name:
          type: string
          example: Nightly database backup
        api_key:
          type: string
          description: Scoped ping key for the `X-Heartbeat-Key` header.
          example: hb.2h9XkPq...
        status:
          type: string
          enum: [initial, ok, delayed, missed, critical, failed, disabled, unknown]
          description: >
            `initial` until the first ping arrives; `ok` while pings arrive
            on schedule; `delayed` when a ping is late but within grace;
            `missed`/`critical` as missed pings accumulate.
        enabled:
          type: boolean
        interval_seconds:
          type: integer
          description: Expected seconds between pings.
          example: 86400
        grace_period_seconds:
          type: integer
          description: Seconds a ping may be late before it counts as missed.
          example: 3600
        escalation_policy_uuid:
          type: string
        last_ping:
          type: integer
          format: int64
          description: Unix milliseconds of the last received ping.
        next_expected_ping:
          type: integer
          format: int64
          description: Unix milliseconds by which the next ping is expected.
        missed_pings:
          type: integer
        successful_pings:
          type: integer
        health_score:
          type: integer
          description: 0-100 health score derived from recent ping history.
        created_at:
          type: string
          format: date-time
      additionalProperties: true
    PingResponse:
      type: object
      properties:
        message:
          type: string
          example: pong
        status:
          type: string
          example: ok
    AutoPingResponse:
      type: object
      properties:
        message:
          type: string
          example: pong
        status:
          type: string
          example: ok
        uuid:
          type: string
          example: d1nq8sg44a5g5h3k2b7g
        slug:
          type: string
          example: nightly-backup
        created:
          type: boolean
          description: True when this request created the heartbeat.
    CreateHeartbeatRequest:
      type: object
      required: [name, interval_seconds, grace_period_seconds]
      properties:
        name:
          type: string
          example: Nightly database backup
        interval_seconds:
          type: integer
          example: 86400
        interval_value:
          type: integer
          description: Display value paired with interval_window (e.g. 24).
        interval_window:
          type: string
          enum: [minutes, hours, days, weeks, months]
        grace_period_seconds:
          type: integer
          example: 3600
        grace_period_value:
          type: integer
        grace_period_window:
          type: string
          enum: [minutes, hours, days, weeks, months]
        escalation_policy_uuid:
          type: string
          description: Defaults to the account's default escalation policy.
        region:
          type: string
          description: >
            Metrics home region key. Defaults to the tenant's home region;
            normally omit this.
    Monitor:
      type: object
      properties:
        uuid:
          type: string
        display_name:
          type: string
          example: Marketing site
        monitor_type:
          type: string
          enum: [http, api, dns, tcp, domain]
        url:
          type: string
          example: https://example.com
        status:
          type: string
        enabled:
          type: boolean
        http_method:
          type: string
        check_interval_seconds:
          type: integer
          example: 300
        timeout_seconds:
          type: integer
          example: 30
        expected_status_code:
          type: integer
          example: 200
        expected_response_contains:
          type: string
        tcp_port:
          type: integer
        dns_record_type:
          type: string
        expected_dns_host:
          type: string
        ssl_cert_monitoring:
          type: boolean
        ssl_cert_expiry_alert_days:
          type: integer
        domain_expiry_alert_days:
          type: integer
        response_time_alert_ms:
          type: integer
        failure_threshold:
          type: integer
        consecutive_failures:
          type: integer
        escalation_policy_uuid:
          type: string
        locations:
          type: array
          items:
            type: string
        last_checked_at:
          type: string
          format: date-time
        last_status_code:
          type: integer
        last_response_time_ms:
          type: integer
        last_error_message:
          type: string
        created_at:
          type: string
          format: date-time
      additionalProperties: true
    CreateMonitorRequest:
      type: object
      required: [display_name, monitor_type, url]
      properties:
        display_name:
          type: string
          example: Marketing site
        monitor_type:
          type: string
          enum: [http, api, dns, tcp, domain]
          description: >
            `http`/`api` check a URL. `dns` checks record resolution. `tcp`
            checks a port. `domain` checks domain registration expiry (use
            `ssl_cert_monitoring` on an http monitor for TLS certificate
            expiry).
        url:
          type: string
          description: Full URL for http/api; hostname for dns/tcp/domain.
          example: https://example.com
        http_method:
          type: string
          enum: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS]
          description: Required for http/api monitors.
        check_interval_seconds:
          type: integer
          description: >
            Seconds between checks. Plans may enforce a minimum; values
            below the plan floor are clamped up, not rejected.
          example: 300
        timeout_seconds:
          type: integer
          example: 30
        expected_status_code:
          type: integer
          description: Required for http/api monitors; defaults to 200 otherwise.
          example: 200
        expected_response_contains:
          type: string
          description: Alert unless the response body contains this string.
        request_body:
          type: string
        headers:
          type: object
          additionalProperties:
            type: string
        auth:
          type: object
          description: Basic or bearer auth for the checked endpoint.
          properties:
            type:
              type: string
              enum: [basic, bearer]
            username:
              type: string
            password:
              type: string
            token:
              type: string
        tcp_port:
          type: integer
          description: Required for tcp monitors.
        dns_record_type:
          type: string
          example: A
        expected_dns_host:
          type: string
        ssl_cert_monitoring:
          type: boolean
        ssl_cert_expiry_alert_days:
          type: integer
          description: Alert this many days before the TLS certificate expires.
        domain_expiry_alert_days:
          type: integer
          minimum: 1
          maximum: 365
          description: >
            For domain monitors: alert this many days before the
            registration expires. Default 30.
        response_time_alert_ms:
          type: integer
          description: >
            Alert when successful checks exceed this response time for
            failure_threshold consecutive checks. 0 or omitted = disabled.
        failure_threshold:
          type: integer
          description: Consecutive failures before alerting. Default 3.
        escalation_policy_uuid:
          type: string
          description: Defaults to the account's default escalation policy.
        locations:
          type: array
          items:
            type: string
          description: Check locations, e.g. ["us-east", "eu-west"].
    CreateMonitorResponse:
      type: object
      properties:
        uuid:
          type: string
        display_name:
          type: string
        label:
          type: string
        message:
          type: string
    Alert:
      type: object
      description: >
        Alert documents carry source-specific payload fields in addition to
        the common fields below.
      properties:
        uuid:
          type: string
        status:
          type: string
          enum: [triggered, acknowledged, resolved]
        severity:
          type: string
        hostname:
          type: string
        source_name:
          type: string
        created_at:
          type: string
          format: date-time
      additionalProperties: true
    AlertUUIDs:
      type: object
      required: [UUIDs]
      properties:
        UUIDs:
          type: array
          items:
            type: string
    Page:
      type: object
      description: Paginated result envelope.
      properties:
        data:
          type: array
          items:
            type: object
            additionalProperties: true
        total:
          type: integer
        offset:
          type: integer
        limit:
          type: integer
      additionalProperties: true
    Host:
      type: object
      properties:
        uuid:
          type: string
        hostname:
          type: string
          example: web-01.prod
        status:
          type: string
        agent_version:
          type: string
        last_checkin:
          type: string
          format: date-time
      additionalProperties: true
    Error:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
      additionalProperties: true

  responses:
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    PaymentRequired:
      description: Plan limit reached — upgrade to add more.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

security:
  - apiKey: []

paths:
  /hb/auto/{slug}:
    get:
      tags: [Heartbeats]
      summary: Ping a heartbeat by slug, creating it on first ping
      operationId: autoPingHeartbeat
      description: |
        The one-line provisioning path. If no heartbeat with this slug
        exists it is created (respecting plan limits, bound to the
        account's default escalation policy) and the ping is recorded; if
        it exists, the ping is recorded. `interval`, `grace`, `name`, and
        `region` apply only at creation, so configuration cannot drift ping
        by ping.

        ```
        curl -fsS -H "X-API-Key: $ALERTKICK_API_KEY" \
          "https://acme.alertkick.com/api/v1/hb/auto/nightly-backup?interval=86400&grace=3600"
        ```

        For long-lived jobs, prefer fetching the heartbeat's scoped
        `api_key` afterwards and pinging `/hb/ping/{uuid}` with it, so the
        account key does not live in the crontab.
      parameters:
        - in: path
          name: slug
          required: true
          schema:
            type: string
            pattern: '^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$'
          example: nightly-backup
        - in: query
          name: interval
          schema:
            type: integer
            minimum: 60
            maximum: 31536000
            default: 86400
          description: Expected seconds between pings (creation only).
        - in: query
          name: grace
          schema:
            type: integer
            minimum: 60
            maximum: 31536000
            default: 3600
          description: Grace period in seconds (creation only).
        - in: query
          name: name
          schema:
            type: string
          description: Display name (creation only; defaults to the slug).
      responses:
        '200':
          description: Ping recorded against an existing heartbeat.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoPingResponse'
        '201':
          description: Heartbeat created and first ping recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoPingResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
    post:
      tags: [Heartbeats]
      summary: Ping by slug (POST variant)
      operationId: autoPingHeartbeatPost
      description: >
        Same as the GET form; optionally accepts a JSON body with a
        client-side `timestamp` (Unix seconds) used to compute ping latency.
      parameters:
        - in: path
          name: slug
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                timestamp:
                  type: integer
                  format: int64
      responses:
        '200':
          description: Ping recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoPingResponse'
        '201':
          description: Heartbeat created and first ping recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoPingResponse'

  /hb/ping/{uuid}:
    get:
      tags: [Heartbeats]
      summary: Ping a heartbeat with its scoped key
      operationId: pingHeartbeat
      security:
        - heartbeatKey: []
      description: >
        Record a ping using the heartbeat's own key. This is the form to
        embed in cron jobs and scripts: the key can only ping this one
        heartbeat, so leaking it does not expose the account.
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Ping recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PingResponse'
        '403':
          description: Heartbeat key is incorrect.
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      tags: [Heartbeats]
      summary: Ping with a client timestamp
      operationId: pingHeartbeatPost
      security:
        - heartbeatKey: []
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                timestamp:
                  type: integer
                  format: int64
                  description: Unix seconds when the job finished.
      responses:
        '200':
          description: Ping recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PingResponse'

  /heartbeats/all:
    get:
      tags: [Heartbeats]
      summary: List heartbeats
      operationId: listHeartbeats
      responses:
        '200':
          description: Heartbeats for the tenant.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Heartbeat'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /heartbeats/{uuid}:
    get:
      tags: [Heartbeats]
      summary: Get a heartbeat
      operationId: getHeartbeat
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The heartbeat, including its scoped ping key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Heartbeat'
        '404':
          $ref: '#/components/responses/NotFound'

  /heartbeats/create:
    post:
      tags: [Heartbeats]
      summary: Create a heartbeat
      operationId: createHeartbeat
      description: >
        Dashboard-style create. For scripts and agents, `/hb/auto/{slug}`
        is usually better: it is idempotent, returns JSON with the UUID,
        and arms monitoring with its first ping.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateHeartbeatRequest'
      responses:
        '200':
          description: Created (plain-text acknowledgement).
        '402':
          $ref: '#/components/responses/PaymentRequired'

  /heartbeats/delete:
    post:
      tags: [Heartbeats]
      summary: Delete a heartbeat
      operationId: deleteHeartbeat
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [uuid]
              properties:
                uuid:
                  type: string
      responses:
        '200':
          description: The deleted heartbeat.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Heartbeat'

  /heartbeats/{uuid}/enable:
    post:
      tags: [Heartbeats]
      summary: Enable a heartbeat
      operationId: enableHeartbeat
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The updated heartbeat.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Heartbeat'

  /heartbeats/{uuid}/disable:
    post:
      tags: [Heartbeats]
      summary: Disable a heartbeat
      operationId: disableHeartbeat
      description: Missed pings stop alerting until re-enabled.
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The updated heartbeat.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Heartbeat'

  /monitors/all:
    get:
      tags: [Monitors]
      summary: List monitors
      operationId: listMonitors
      responses:
        '200':
          description: Monitors for the tenant.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Monitor'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /monitors/create:
    post:
      tags: [Monitors]
      summary: Create a monitor
      operationId: createMonitor
      description: |
        Create an uptime monitor. Examples:

        HTTPS + certificate expiry:
        ```
        curl -X POST -H "X-API-Key: $ALERTKICK_API_KEY" -H "Content-Type: application/json" \
          "https://acme.alertkick.com/api/v1/monitors/create" -d '{
            "display_name": "Marketing site",
            "monitor_type": "http",
            "url": "https://example.com",
            "http_method": "GET",
            "check_interval_seconds": 300,
            "timeout_seconds": 30,
            "expected_status_code": 200,
            "ssl_cert_monitoring": true,
            "ssl_cert_expiry_alert_days": 14
          }'
        ```

        Domain registration expiry:
        ```
        curl -X POST -H "X-API-Key: $ALERTKICK_API_KEY" -H "Content-Type: application/json" \
          "https://acme.alertkick.com/api/v1/monitors/create" -d '{
            "display_name": "example.com registration",
            "monitor_type": "domain",
            "url": "example.com",
            "check_interval_seconds": 86400,
            "timeout_seconds": 30,
            "domain_expiry_alert_days": 30
          }'
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMonitorRequest'
      responses:
        '200':
          description: Monitor created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateMonitorResponse'
        '400':
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          $ref: '#/components/responses/PaymentRequired'

  /monitors/{uuid}:
    get:
      tags: [Monitors]
      summary: Get a monitor
      operationId: getMonitor
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The monitor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Monitor'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Monitors]
      summary: Update a monitor
      operationId: updateMonitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMonitorRequest'
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The updated monitor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Monitor'
    delete:
      tags: [Monitors]
      summary: Delete a monitor
      operationId: deleteMonitor
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Deleted.
        '404':
          $ref: '#/components/responses/NotFound'

  /monitors/{uuid}/pause:
    post:
      tags: [Monitors]
      summary: Pause a monitor
      operationId: pauseMonitor
      description: Checks stop and no alerts fire until resumed.
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Paused.

  /monitors/{uuid}/resume:
    post:
      tags: [Monitors]
      summary: Resume a paused monitor
      operationId: resumeMonitor
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Resumed.

  /alerts:
    get:
      tags: [Alerts]
      summary: List alerts
      operationId: listAlerts
      description: >
        Paginated. Supports `offset`, `limit`, and field filters such as
        `status` as query parameters.
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [triggered, acknowledged, resolved]
        - in: query
          name: offset
          schema:
            type: integer
        - in: query
          name: limit
          schema:
            type: integer
      responses:
        '200':
          description: A page of alerts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Page'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /alerts/{uuid}:
    get:
      tags: [Alerts]
      summary: Get an alert
      operationId: getAlert
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The alert.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Alert'
        '404':
          $ref: '#/components/responses/NotFound'

  /alerts/acknowledge:
    post:
      tags: [Alerts]
      summary: Acknowledge alerts
      operationId: acknowledgeAlerts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AlertUUIDs'
      responses:
        '200':
          description: Acknowledged.

  /alerts/resolve:
    post:
      tags: [Alerts]
      summary: Resolve alerts
      operationId: resolveAlerts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AlertUUIDs'
      responses:
        '200':
          description: Resolved.

  /hosts:
    get:
      tags: [Hosts]
      summary: List hosts
      operationId: listHosts
      parameters:
        - in: query
          name: offset
          schema:
            type: integer
        - in: query
          name: limit
          schema:
            type: integer
      responses:
        '200':
          description: Hosts visible to the tenant.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Host'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /hosts/{uuid}:
    get:
      tags: [Hosts]
      summary: Get a host
      operationId: getHost
      parameters:
        - in: path
          name: uuid
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The host.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Host'
        '404':
          $ref: '#/components/responses/NotFound'
