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.
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
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.)