Monitive Keys API

The Keys API from Monitive — 3 operation(s) for keys.

Operations 4

GET /api/v1/keys List API keys #
POST /api/v1/keys Create API key #
DELETE /api/v1/keys/{keyID} Delete API key #
POST /api/v1/keys/{keyID}/rotate Rotate API key #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/uptimemonitoring-com:uptimemonitoring-com-keys-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

uptimemonitoring-com-keys-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: UptimeMonitoring.com Keys API
  version: 0.1.0
  description: 'API-first uptime monitoring for developers, deploy pipelines, and AI agents.


    ## Authentication


    All `/api/v1` endpoints require a Bearer token — either an API key (`umk_live_*`)

    or a session token obtained via GitHub OAuth. The `/mcp` endpoint additionally

    accepts OAuth 2.1 JWT access tokens (`eyJ...`) issued via the self-hosted

    authorization server documented under the `OAuth` tag.


    ```

    Authorization: Bearer umk_live_abc123...

    ```


    ## Rate limits


    200 requests/minute per account. Exceeding the limit returns `429` with a

    `Retry-After` header.


    Responses that pass through the per-account rate-limiter carry the

    `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`

    headers (decimal integers; `Reset` is a Unix epoch second at which

    the bucket would be full again). In practice this is every

    authenticated response under `/api/v1/*` whose request reached the

    handler — earlier failures from the auth middleware (e.g. 401 missing

    credentials, 403 suspended account) return before the limiter runs

    and do NOT carry the trio. Routes mounted outside `/api/v1`

    (notably `GET /api/v1/events/stream`, which has its own SSE

    middleware stack) are not subject to this limiter and likewise do

    not emit these headers. Successful responses on

    `POST /api/v1/monitors` reflect the **global** 200/min bucket; the

    per-route 20/min creation limit only surfaces its own headers when

    it 429s. The trio is documented once here rather than referenced

    into every individual response — see `components.headers` for the

    schemas.


    One exception: once a request presenting an API key enters an

    authenticated route group through a registered route or mounted

    subtree, while that key remains in the bounded pre-auth cache

    (entries expire after 5 minutes, but may be evicted sooner once the

    cache is at capacity), the global bucket is charged BEFORE

    authentication and authorization run (issue #47) — so for that

    cached key only, a `401` (key revoked while still cached), `403`,

    `500`, or a `404` for an unmatched path inside a mounted subtree

    also carries the `X-RateLimit-*` trio and spends one token. `429`

    still comes first whenever the bucket is already empty. An uncached

    key (first seen, expired, or evicted early at capacity), a session

    token, an OAuth JWT, and any path outside every authenticated group

    are all unaffected.


    ## Browser push notifications


    Subscriptions registered via `POST /api/v1/push/subscribe` receive

    fire-and-forget Web Push notifications on monitor state transitions

    (down/up/flapping); per-monitor 5-minute throttle. No public API change

    in this release — dispatch is internal to `internal/push.Dispatcher`.'
  contact:
    name: UptimeMonitoring.com
    url: https://uptimemonitoring.com
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
- url: https://api.uptimemonitoring.com
  description: Production
tags:
- name: Keys
paths:
  /api/v1/keys:
    get:
      operationId: listKeys
      summary: List API keys
      description: 'Returns all API keys for the authenticated account, ordered by creation

        date (newest first). Key hashes and plaintexts are never exposed.'
      tags:
      - Keys
      security:
      - bearerAuth: []
      responses:
        '200':
          description: List of API keys.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/APIKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/AccountSuspended'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      operationId: createKey
      summary: Create API key
      description: 'Creates a new API key. The plaintext key is returned **once** in the `key`

        field — it cannot be retrieved again. Max 10 keys per account.'
      tags:
      - Keys
      security:
      - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
                  minLength: 3
                  maxLength: 100
                  description: 'Optional human-readable label for the key. Trimmed of

                    surrounding whitespace before persistence. Must be 3..100

                    characters after trimming, or omitted entirely.

                    '
                  example: production
      responses:
        '201':
          description: Key created. The `key` field contains the plaintext (shown once).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyCreated'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/AccountSuspended'
        '409':
          description: Account has reached the 10-key limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: key limit reached
        '413':
          $ref: '#/components/responses/RequestEntityTooLarge'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: "Two distinct failure shapes share this status, distinguishable\nby the `error` body:\n\n1. **Generic pre-persistence failure** — key material generation\n   or an unclassified store error before any commit was\n   attempted. Body: `{\"error\": \"internal error\"}`. No key was\n   written; safe to retry.\n2. **Commit outcome unknown** (e.g. a connection drop\n   mid-commit) — the message names the candidate key's ID and\n   prefix; a key with that ID/prefix may or may not have been\n   created. A `GET /api/v1/keys` that doesn't show it is not\n   proof it was never created (a commit can become visible\n   moments after a read on another connection), and this stays\n   true even after waiting and listing again: negative reads\n   remain inconclusive. Do not automatically create another\n   key. If the candidate does appear and another usable key\n   already exists, revoke the candidate directly; if it is the\n   account's only key, authenticate via a dashboard session\n   (not the withheld candidate) to create and verify a\n   replacement first — `DELETE /api/v1/keys/{keyID}` refuses to\n   remove an account's last key regardless of whether its\n   plaintext was ever received.\n"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                genericFailure:
                  summary: Generic pre-persistence failure (no key written)
                  value:
                    error: internal error
                commitOutcomeUnknown:
                  summary: Commit outcome unknown (candidate may exist)
                  value:
                    error: 'key creation outcome unknown: a key with id 42 and prefix umk_live_ab12cd may or may not have been created. Listing keys and not seeing it does not prove it was not created, even after waiting and listing again — negative reads remain inconclusive. Do not automatically create another key. If it does appear and you already have another usable key, revoke the candidate directly. If it is your only key, deleting it will fail with 409 cannot delete last key: first sign in to the dashboard (GitHub OAuth session, not this key) and create a verified replacement key there, then revoke the candidate. Otherwise contact an operator to confirm the outcome before creating a replacement.'
  /api/v1/keys/{keyID}:
    delete:
      operationId: deleteKey
      summary: Delete API key
      description: 'Deletes an API key. Deletion is rejected while the account has only

        one `api_keys` row, but that guard counts raw rows, not usable

        (non-revoked) keys — a key freshly rotated out and still in its

        grace window still counts. Confirm a working replacement key before

        revoking a candidate key.'
      tags:
      - Keys
      security:
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/keyID'
      responses:
        '204':
          description: Key deleted.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/AccountSuspended'
        '404':
          description: Key not found or belongs to another account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Cannot delete the last key on the account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: cannot delete last key
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/v1/keys/{keyID}/rotate:
    post:
      operationId: rotateKey
      summary: Rotate API key
      description: 'Creates a new key and schedules the old key for revocation after a 5-minute

        grace window. During the grace window both keys are valid. After the window,

        the old key stops working. The rotated key counts toward the 10-key limit

        during the grace period.'
      tags:
      - Keys
      security:
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/keyID'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
                  minLength: 3
                  maxLength: 100
                  description: 'Optional label for the new key. Trimmed of surrounding

                    whitespace before persistence. Must be 3..100 characters

                    after trimming, or omitted entirely.

                    '
      responses:
        '201':
          description: New key created. The `key` field contains the plaintext (shown once).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyCreated'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/AccountSuspended'
        '404':
          description: Key not found, belongs to another account, or already revoked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Account has reached the 10-key limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: key limit reached
        '413':
          $ref: '#/components/responses/RequestEntityTooLarge'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: "Two distinct failure shapes share this status, distinguishable\nby the `error` body:\n\n1. **Generic pre-persistence failure** — key material generation\n   or an unclassified store error before any commit was\n   attempted. Body: `{\"error\": \"internal error\"}`. No rotation\n   was committed — the old key is untouched; safe to retry.\n2. **Commit outcome unknown** (e.g. a connection drop\n   mid-commit) — the message names the old key's ID and the\n   candidate replacement's ID/prefix — the old key may already\n   be revoked (entering its 5-minute grace window) and the new\n   key may or may not have been created. A `GET /api/v1/keys`\n   that doesn't show the new key is not proof it was never\n   created, even after waiting and listing again: negative\n   reads remain inconclusive. Retrying this same rotation (same\n   old key ID) is safe and cannot mint a second replacement —\n   it either mints exactly one new key (the ambiguous attempt\n   rolled back) or fails with 404 (the ambiguous attempt\n   already landed, in which case the ID/prefix named in this\n   message is the real replacement). Before revoking the\n   candidate, confirm you have a working replacement key —\n   deletion is only rejected while the account has a single\n   `api_keys` row, and that count includes an old key still in\n   its post-rotation grace window, so the guard does not always\n   prevent ending up without a usable key.\n"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                genericFailure:
                  summary: Generic pre-persistence failure (no rotation committed)
                  value:
                    error: internal error
                commitOutcomeUnknown:
                  summary: Commit outcome unknown (candidate replacement may exist)
                  value:
                    error: 'key rotation outcome unknown: the old key (id 41) may already be revoked and entering its 5-minute grace window, and a new key with id 42 and prefix umk_live_ab12cd may or may not have been created. Listing keys and not seeing the new key does not prove it was not created, even after waiting and listing again — negative reads remain inconclusive. Retrying this same rotation (same old key ID) is safe and will not create a second replacement: it will either succeed with exactly one new key (the first attempt rolled back) or fail with 404 key not found (the first attempt already landed — in that case the key with id 42 and prefix umk_live_ab12cd above is your real replacement; if you still need its plaintext, rotate again using that key''s ID before the old key''s 5-minute grace window expires). Before revoking the new key, confirm you have a working replacement: the last-key guard only blocks deletion when the account has a single api_keys row, which still counts an old key inside its grace window, so it will not always stop you from ending up without a usable key.'
components:
  schemas:
    APIKey:
      type: object
      description: API key metadata (plaintext and hash are never exposed).
      properties:
        id:
          type: integer
          format: int64
        account_id:
          type: integer
          format: int64
        key_prefix:
          type: string
          description: 'Prefix for identification: `umk_live_` + first 8 hex chars.'
          example: umk_live_a1b2c3d4
        label:
          type:
          - string
          - 'null'
          description: 'User-assigned label. New labels (created on or after the #115

            tightening) are trimmed to 3..100 characters. Legacy labels

            persisted before that rule may fall outside this range or carry

            untrimmed surrounding whitespace and are returned verbatim.

            '
          example: production
        revoked_at:
          type:
          - string
          - 'null'
          format: date-time
          description: If set, the key will stop working after this timestamp.
        last_used_at:
          type:
          - string
          - 'null'
          format: date-time
        last_used_ip:
          type:
          - string
          - 'null'
          description: IP address of the last request authenticated with this key.
        created_at:
          type: string
          format: date-time
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
      example:
        error: something went wrong
    APIKeyCreated:
      description: API key with the one-time plaintext included.
      type: object
      properties:
        id:
          type: integer
          format: int64
        account_id:
          type: integer
          format: int64
        key_prefix:
          type: string
          example: umk_live_a1b2c3d4
        label:
          type:
          - string
          - 'null'
        revoked_at:
          type:
          - string
          - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        key:
          type: string
          description: 'Full plaintext API key. **Shown once** — store it securely.

            '
          example: umk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
  parameters:
    keyID:
      name: keyID
      in: path
      required: true
      schema:
        type: integer
        format: int64
      description: API key ID.
  responses:
    RateLimited:
      description: 'Per-account dispatcher rate limit exceeded (200 requests/minute).

        The `Retry-After` header gives the integer seconds until at least

        one token has refilled. The `X-RateLimit-*` trio reflects the

        bucket whose rejection produced this response — usually the global

        200/min cap; on `POST /api/v1/monitors` a 429 may instead reflect

        the per-route 20/min creation limit.

        '
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the next allowed request.
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: account rate limit exceeded; retry in 1s
    RequestEntityTooLarge:
      description: 'Request body exceeds the 64 KB hard limit. Per docs/SPEC.md

        §Hard Limits the dispatcher rejects any payload larger than

        65,536 bytes before it is decoded.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: request body exceeds 64 KB limit
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: invalid credentials
    AccountSuspended:
      description: 'Account is currently suspended. On `POST /api/v1/monitors`, and on

        a `PUT /api/v1/monitors/{monitorID}` that changes `url` or `type`

        (issue #335), this can also be returned by the per-account

        port-scan heuristic when the triggering request crossed the

        port-fan threshold inside the sliding window (10 minutes by

        default; `SCAN_WINDOW`). A host-fan trip does NOT suspend the

        account — see the 429 response instead.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: account suspended; contact support
    UnsupportedMediaType:
      description: 'The request `Content-Type` is not `application/json`. Write

        operations (POST, PUT, PATCH) require that header; a missing or

        non-JSON media type is rejected before the body is read.

        The `charset` parameter is accepted and ignored, so

        `application/json; charset=utf-8` is valid.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: unsupported media type, expected application/json
    BadRequest:
      description: Invalid request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  headers:
    XRateLimitReset:
      schema:
        type: integer
      description: 'Unix epoch seconds at which the bucket would be full again if no

        further requests are made. Sub-second fragments are rounded up so

        `Reset - now` is never less than the actual refill duration.

        '
    XRateLimitLimit:
      schema:
        type: integer
      description: 'Bucket capacity for the limiter that produced this response. `200`

        for the global per-account dispatcher cap; `20` on a 429 from the

        per-route monitor-create limiter.

        '
    XRateLimitRemaining:
      schema:
        type: integer
      description: 'Tokens left in the bucket after this request was charged

        (floor of the post-charge token count). `0` on a 429 means the

        request was rejected with the bucket empty.

        '
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'API key (`umk_live_*`) or session token from OAuth exchange.

        '