Skip to main content
POST
/
v0
/
profiles
/
labels
curl --request POST \
  --url https://api.formo.so/v0/profiles/labels \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
[
  {
    "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "tag_id": "vip",
    "value": "tier-1"
  },
  {
    "address": "EPjFWaYbrgqCC2Qbg4EV4FjUreEMKwMn1zNbiboXXKV",
    "tag_id": "airdrop_eligible",
    "chain_id": "1"
  }
]
'
{
  "successful_rows": 123,
  "quarantined_rows": 123,
  "errors": [
    {
      "index": 123,
      "reason": "<string>",
      "address": "<string>"
    }
  ]
}

Historical (backfilled) labels

Each item extends the single-wallet label schema, so it accepts the same optional timestamp (ISO-8601). When set, that row is recorded at the given time instead of the server’s current time, letting you backfill historical values per wallet in one batch. Label-based retention then evaluates each value at the correct point in time.
curl -sS -X POST \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  -H "Content-Type: application/json" \
  -d '[
    { "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "tag_id": "open_interest", "value": "1200", "timestamp": "2026-01-05T00:00:00Z" },
    { "address": "EPjFWaYbrgqCC2Qbg4EV4FjUreEMKwMn1zNbiboXXKV", "tag_id": "open_interest", "value": "800", "timestamp": "2026-01-12T00:00:00Z" }
  ]' \
  "https://api.formo.so/v0/profiles/labels"
timestamp must not be in the future. Unlike an invalid address (which is quarantined for that row only), a future timestamp fails request validation and rejects the whole batch with 400. When omitted, the row is recorded at server time.

Backdated removals (historical tombstones)

Set the optional _is_deleted flag to 1 on a row to backfill a removal at that row’s timestamp instead of writing a live value. This records a tombstone marking the label as removed at time T, so point-in-time retention drops the wallet from that historical week. _is_deleted is an optional integer, either 0 or 1, and defaults to 0 (a live label). This is the same per-row flag as on the single-wallet endpoint, so a batch can carry a full value-then-removal time series across many wallets in one request:
curl -sS -X POST \
  -H "Authorization: Bearer <your_workspace_api_key>" \
  -H "Content-Type: application/json" \
  -d '[
    { "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "tag_id": "open_interest", "value": "high", "timestamp": "2026-01-15T00:00:00Z" },
    { "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "tag_id": "open_interest", "value": "low",  "timestamp": "2026-02-15T00:00:00Z" },
    { "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "tag_id": "open_interest", "_is_deleted": 1, "timestamp": "2026-03-15T00:00:00Z" }
  ]' \
  "https://api.formo.so/v0/profiles/labels"
A tombstone follows the same validation as any backfilled row: a future timestamp rejects the whole batch with 400 BAD_REQUEST. Order of ingest does not matter, since reads resolve to the value with the latest timestamp. For a chain-scoped label, the tombstone must carry the same chain_id as the label it removes; a tombstone’s value is irrelevant. The storage-only _is_deleted flag is never echoed back in the response.

Authorizations

Authorization
string
header
required

Workspace API key (e.g. formo_xxx). Create one in the Formo dashboard under Team Settings > API Keys.

Body

application/json
Required array length: 1 - 100 elements
tag_id
string
required

Label identifier (lowercased on write). e.g. vip, airdrop_eligible, coinbase.verified_account

address
string
required

Wallet address the label applies to. Literal EVM (0x...) or Solana address only; ENS names are not resolved in batch requests.

value
string

Optional label value (e.g. tier name, country code)

chain_id
string

Optional chain identifier the label applies to

timestamp
string<date-time>

Optional ISO-8601 event-time for the label. When provided, the label is recorded at this time instead of the server's current time; used to backfill historical values (e.g. an open_interest reading from a past week) so label-based retention can evaluate them at the right point in time. Must not be in the future. Defaults to server time when omitted.

_is_deleted
enum<integer>

Optional tombstone flag for backfilled removals. 1 records the row as a soft-delete (label removed) instead of a live value; pair it with a past timestamp to express "label removed at past time T" so point-in-time retention drops the wallet from that week. The future-timestamp guard still applies. Defaults to 0 (a live label) when omitted.

Available options:
0,
1

Response

Batch processed. Returns counts of forwarded vs quarantined rows, with a per-row errors entry for each quarantined row.

Acknowledgement for a batch write. successful_rows counts rows accepted and forwarded to ingest after a 2xx (ingestion is async/eventually-consistent; there is no follow-up read). quarantined_rows counts rows skipped for an invalid address or no valid keys; errors (omitted when nothing was quarantined) maps each skipped row back to its request index.

successful_rows
integer
required

Number of rows accepted and forwarded to ingest.

quarantined_rows
integer
required

Number of rows skipped (invalid address, or no valid keys).

errors
object[]

One entry per quarantined row. Omitted when quarantined_rows is 0.