Formo Contracts API

Manage blockchain contract monitoring

GitHub OpenAPI

Operations 5

GET /v0/contracts List contracts #
POST /v0/contracts Create contract #
GET /v0/contracts/{chain}/{address} Get contract #
PUT /v0/contracts/{chain}/{address} Update contract #
DELETE /v0/contracts/{chain}/{address} Delete contract #

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/formo-so:formo-so-contracts-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

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

formo-so-contracts-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Formo Public Contracts API
  description: 'REST API for managing Formo projects, analytics, alerts, boards, charts, contracts, segments, and AI chat.


    **Auth.** Every endpoint on `api.formo.so` requires a workspace API key with the appropriate scopes (see `x-api-scopes`). The one exception is `POST /v0/raw_events`, which runs on `events.formo.so` and authenticates with the project SDK write key instead.


    **Response shape.** Successful responses return the resource directly (or `{ data: [...], total, page, size, has_more }` for paginated lists). HTTP status carries success/failure; there is no envelope wrapping success bodies.


    **Errors.** Non-2xx responses from `api.formo.so` use the `Error` envelope, with two exceptions: some rate-limit rejections reply in plain text, and `POST /v0/raw_events` returns a plain `{ "error": "..." }` object. Treat the HTTP status as authoritative and parse defensively: `{ error: { code, message, doc_url, param?, details? } }`. Branch on the machine-readable `code` (see `ErrorCode` enum) and follow `doc_url` to the matching section of the errors reference.


    **Idempotency.** Pass an `Idempotency-Key` header on write requests to alerts, boards, charts, contracts, segments, and import to make retries safe; the response is cached for 24 h and replayed on duplicate keys. Profile writes and `POST /v0/query` do not support it.'
  version: 0.1.0
  contact:
    name: Formo
    url: https://formo.so
servers:
- url: https://api.formo.so
  description: API Server (boards, alerts, contracts, segments, profiles, query, import)
- url: https://events.formo.so
  description: Events Server (event ingestion)
security:
- WorkspaceApiKey: []
tags:
- name: Contracts
  description: Manage blockchain contract monitoring
paths:
  /v0/contracts:
    get:
      operationId: listContracts
      summary: List contracts
      description: List monitored contracts. Paginated; the canonical `data` array carries the contracts for the current page. The `deploy` sidecar reports the project-wide deploy state (always reflects ALL contracts, not just this page) so callers can render "X contracts pending deploy" without a second request.
      tags:
      - Contracts
      parameters:
      - $ref: '#/components/parameters/Page'
      - $ref: '#/components/parameters/Size'
      responses:
        '200':
          description: Paginated list of contracts plus deploy-state sidecar
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/PaginatedListMeta'
                - type: object
                  required:
                  - data
                  - deploy
                  properties:
                    data:
                      type: array
                      items:
                        $ref: '#/components/schemas/Contract'
                    deploy:
                      type: object
                      required:
                      - last_deployed_at
                      - diff
                      properties:
                        last_deployed_at:
                          type: string
                          format: date-time
                          nullable: true
                          description: Timestamp of the project's last successful deploy, or null if no deploy has run yet.
                        diff:
                          type: array
                          description: Difference between the contracts currently registered for the project and what's actually deployed. Drives the "contracts pending deploy" UI.
                          items:
                            type: object
              example:
                data:
                - name: USD Coin
                  chain: 1
                  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
                  start_block: 6082465
                  abi: '[{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]'
                  events:
                  - anonymous: false
                    name: Transfer
                    type: event
                    inputs:
                    - indexed: true
                      name: from
                      type: address
                    - indexed: true
                      name: to
                      type: address
                    - indexed: false
                      name: value
                      type: uint256
                  include_in_pipeline: false
                - name: WETH (Base)
                  chain: 8453
                  address: '0x4200000000000000000000000000000000000006'
                  start_block: 1
                  abi: '[{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]'
                  events:
                  - anonymous: false
                    name: Transfer
                    type: event
                    inputs:
                    - indexed: true
                      name: from
                      type: address
                    - indexed: true
                      name: to
                      type: address
                    - indexed: false
                      name: value
                      type: uint256
                  include_in_pipeline: false
                page: 1
                size: 100
                total: 2
                has_more: false
                deploy:
                  last_deployed_at: '2026-04-22T08:14:31.000Z'
                  diff: []
      x-required-scope: contracts:read
    post:
      operationId: createContract
      summary: Create contract
      description: Add a blockchain contract to monitor.
      tags:
      - Contracts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                address:
                  type: string
                  description: EVM contract address
                chain:
                  type: integer
                  description: Chain ID
                name:
                  type: string
                abi:
                  type: string
                  description: JSON-stringified ABI
                events:
                  type: array
                  maxItems: 10
                  items:
                    type: object
                    properties:
                      anonymous:
                        type: boolean
                      inputs:
                        type: array
                        items:
                          type: object
                      name:
                        type: string
                      type:
                        type: string
                    required:
                    - anonymous
                    - inputs
                    - name
                    - type
                start_block:
                  type: integer
                  default: 0
                  description: 'Block height recorded on the contract. Note: the events pipeline currently opens every source at the chain head, so this does not backfill historical events. Defaults to 0.'
                  minimum: 0
                  maximum: 9007199254740991
                include_in_pipeline:
                  type: boolean
                  default: false
                  description: Whether to include this contract in the project contract-events pipeline. When false the ABI is still cached for transaction decoding, but no events are indexed. Defaults to false (decode-only).
              required:
              - address
              - chain
              - name
              - abi
              - events
            examples:
              erc20Token:
                summary: Monitor ERC-20 Transfer events
                value:
                  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
                  chain: 1
                  name: USDC
                  abi: '[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]'
                  events:
                  - anonymous: false
                    inputs:
                    - indexed: true
                      internalType: address
                      name: from
                      type: address
                    - indexed: true
                      internalType: address
                      name: to
                      type: address
                    - indexed: false
                      internalType: uint256
                      name: value
                      type: uint256
                    name: Transfer
                    type: event
                  start_block: 18000000
                  include_in_pipeline: true
      responses:
        '201':
          description: Contract created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contract'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'
      x-required-scope: contracts:write
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
  /v0/contracts/{chain}/{address}:
    get:
      operationId: getContract
      summary: Get contract
      description: Fetch a single monitored contract by chain ID and address. Returns the bare `Contract` resource (no envelope).
      tags:
      - Contracts
      parameters:
      - name: chain
        in: path
        required: true
        schema:
          type: integer
          minimum: 1
        description: EVM chain ID (e.g. `1` for Ethereum mainnet, `8453` for Base).
      - name: address
        in: path
        required: true
        schema:
          type: string
          pattern: ^0x[0-9a-fA-F]{40}$
        description: Contract address (0x-prefixed, 40 hex chars).
      responses:
        '200':
          description: Contract found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contract'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
      x-required-scope: contracts:read
    put:
      operationId: updateContract
      summary: Update contract
      tags:
      - Contracts
      parameters:
      - name: chain
        in: path
        required: true
        schema:
          type: string
      - name: address
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                address:
                  type: string
                chain:
                  type: integer
                name:
                  type: string
                abi:
                  type: string
                events:
                  type: array
                  maxItems: 10
                  items:
                    type: object
                start_block:
                  type: integer
                  description: 'Block height recorded on the contract. Omit to preserve the stored value. Note: the events pipeline currently opens every source at the chain head, so this does not backfill historical events.'
                  minimum: 0
                  maximum: 9007199254740991
                include_in_pipeline:
                  type: boolean
                  description: Whether to include this contract in the project contract-events pipeline. Omit to preserve the stored value; send false to keep the ABI cached for transaction decoding without indexing events.
              required:
              - address
              - chain
              - name
              - abi
              - events
      responses:
        '200':
          description: Contract updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contract'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'
      x-required-scope: contracts:write
    delete:
      operationId: deleteContract
      summary: Delete contract
      tags:
      - Contracts
      parameters:
      - name: chain
        in: path
        required: true
        schema:
          type: string
      - name: address
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Contract deleted
          content:
            application/json:
              schema:
                type: 'null'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'
      x-required-scope: contracts:write
components:
  schemas:
    Error:
      type: object
      description: Standard error envelope returned by every public API endpoint for any non-2xx response. The HTTP status code carries success/failure; the body provides a machine-readable `code`, a human-readable `message`, and a `doc_url` pointing at the matching section of the docs so agents can fetch context on the fly.
      properties:
        error:
          type: object
          required:
          - code
          - message
          - doc_url
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
              description: Human-readable error description. Wording may change between releases, so branch on `code`, not `message`.
            doc_url:
              type: string
              format: uri
              description: Link to the matching section of the errors reference at https://docs.formo.so/api/errors.
            param:
              type: string
              description: When the error pertains to a specific request field, the dotted path to that field (e.g. `body.trigger_filters.0.value`).
            details:
              type: object
              additionalProperties: true
              description: 'Code-specific extra context. For `INVALID_VALIDATION_REQUEST` this is a `{ fieldPath: message }` map of every Zod validation failure.'
      required:
      - error
    Contract:
      type: object
      properties:
        name:
          type: string
        chain:
          type: integer
        address:
          type: string
        start_block:
          type: integer
        abi:
          type: string
        events:
          type: array
          items:
            type: object
            properties:
              anonymous:
                type: boolean
              inputs:
                type: array
                items:
                  type: object
              name:
                type: string
              type:
                type: string
            required:
            - anonymous
            - inputs
            - name
            - type
        include_in_pipeline:
          type: boolean
          description: 'Whether the contract is configured for the project contract-events pipeline. This is the desired membership, not live deployment state: use the `deploy` sidecar on the contract list to see what is currently deployed.'
      required:
      - name
      - chain
      - address
      - abi
      - events
      - include_in_pipeline
    ErrorCode:
      type: string
      description: Stable, enumerated error codes. New codes may be added in any release; clients should treat unknown codes as the closest matching HTTP status family.
      enum:
      - INTERNAL_SERVER_ERROR
      - INVALID_VALIDATION_REQUEST
      - UNAUTHORIZED
      - BAD_REQUEST
      - FORBIDDEN
      - NOT_FOUND
      - CONFLICT
      - INVALID_CHAIN_ID
      - CONTEXT_LIMIT_EXCEEDED
      - SERVICE_UNAVAILABLE
      - TOO_MANY_REQUESTS
      - IDEMPOTENCY_IN_PROGRESS
      - INVALID_IDEMPOTENCY_KEY
    PaginatedListMeta:
      type: object
      description: 'Pagination cursor returned alongside `data` on every paginated list endpoint. Use these to walk pages: `has_more` is true while `page * size < total`. Combine with the matching `Page` and `Size` query parameters to request the next page.'
      required:
      - page
      - size
      - total
      - has_more
      properties:
        page:
          type: integer
          description: 1-indexed page number echoed from the request.
        size:
          type: integer
          description: Page size echoed from the request.
        total:
          type: integer
          description: Total row count across all pages.
        has_more:
          type: boolean
          description: True when more pages remain (`page * size < total`).
  parameters:
    Size:
      name: size
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 100
      description: Page size. Defaults to 100, capped at 200.
    Page:
      name: page
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        default: 1
      description: 1-indexed page number. Defaults to 1.
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
      description: Optional unique value (e.g. a UUID v4) that lets you safely retry POST/PUT/PATCH/DELETE requests. The first request runs normally; subsequent requests with the same key replay the stored response (status + body) for 24 hours, so retries can never double-create or double-charge. Two concurrent requests with the same key return `409 IDEMPOTENCY_IN_PROGRESS`. Generate a fresh key per logical operation.
  responses:
    BadRequest:
      description: 'The request was rejected. `code` is either `INVALID_VALIDATION_REQUEST` (Zod schema mismatch; `details` carries a `{ fieldPath: message }` map) or `BAD_REQUEST` (semantic validation failure outside Zod, e.g. mismatched IDs, business-rule violations). Branch on `code`, not status, to tell the two apart.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            validation:
              summary: Zod schema mismatch
              value:
                error:
                  code: INVALID_VALIDATION_REQUEST
                  message: Invalid request data
                  doc_url: https://docs.formo.so/api/errors#invalid_validation_request
                  details:
                    body.name: String must contain at least 1 character(s)
            semantic:
              summary: Semantic validation failure
              value:
                error:
                  code: BAD_REQUEST
                  message: Target board must be different from the current board
                  doc_url: https://docs.formo.so/api/errors#bad_request
    NotFound:
      description: The requested resource does not exist or is not visible to this API key's workspace.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: NOT_FOUND
              message: Alert not found
              doc_url: https://docs.formo.so/api/errors#not_found
    Conflict:
      description: The request conflicts with current resource state, or an `Idempotency-Key` request with the same key is currently in flight.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: IDEMPOTENCY_IN_PROGRESS
              message: A request with this Idempotency-Key is already in progress. Retry shortly.
              doc_url: https://docs.formo.so/api/errors#idempotency_in_progress
    InternalServerError:
      description: An unexpected error occurred on the server. The error has been logged; retry with exponential backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INTERNAL_SERVER_ERROR
              message: Internal Server Error
              doc_url: https://docs.formo.so/api/errors#internal_server_error
    Forbidden:
      description: The API key is valid but lacks the required scope for this endpoint.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: FORBIDDEN
              message: 'API key missing required scope: alerts:write'
              doc_url: https://docs.formo.so/api/errors#forbidden
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: UNAUTHORIZED
              message: Invalid API key
              doc_url: https://docs.formo.so/api/errors#unauthorized
  securitySchemes:
    WorkspaceApiKey:
      type: http
      scheme: bearer
      description: Workspace API key (e.g. `formo_xxx`). Create one in the Formo dashboard under Team Settings > API.
    X402Payment:
      type: apiKey
      in: header
      name: X-PAYMENT
      description: x402 payment payload generated after the gateway returns a 402 challenge.
    MPPPayment:
      type: apiKey
      in: header
      name: Authorization
      description: MPP payment authorization header, formatted as `Payment <payload>`, generated after the gateway returns a 402 challenge.
    SdkWriteKey:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Project SDK write key (a JWT), sent as `Authorization: Bearer <write key>`. This is NOT the `formo_...` workspace API key used on api.formo.so. Find it in the Formo dashboard under the project’s settings.'
x-api-scopes:
  description: API key scopes control access to endpoints. Create keys with the required scopes in Team Settings > API.
  scopes:
    alerts:read: List and get alerts
    alerts:write: Create, update, delete alerts (requires alerts:read)
    boards:read: List and get boards and charts
    boards:write: Create, update, delete boards and charts (requires boards:read)
    contracts:read: List contracts
    contracts:write: Create, update, delete contracts (requires contracts:read)
    segments:read: List segments
    segments:write: Create and delete segments (requires segments:read)
    profiles:read: Search and get wallet profiles
    profiles:write: Update wallet properties, manage labels, and import addresses (requires profiles:read)
    query:read: Execute SQL analytics queries and read pre-built analytics endpoints (KPIs, top pages, lifecycle, retention, revenue, etc.)