---
title: "Add or update user labels"
method: POST
path: "/v0/profiles/{address}/labels"
tags: ["Profiles"]
---

# Add or update user labels

`POST /v0/profiles/{address}/labels`

## Request

- Base URL: `https://api.formo.so`
- URL: `https://api.formo.so/v0/profiles/{address}/labels`
- Auth: HTTP bearer

Upsert one or more labels for a wallet. Accepts either a single label object or an array of labels. Labels with the same tag_id (and chain_id, if provided) are overwritten. Requires profiles:write scope.

## Path parameters

- `address` string, required

## Request body

- union
  - UserLabelInput
    - `tag_id` string, required: Label identifier (lowercased on write). e.g. vip, airdrop_eligible, coinbase.verified_account
    - `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` 0 | 1: 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.
  - UserLabelInput[]
    - `tag_id` string, required: Label identifier (lowercased on write). e.g. vip, airdrop_eligible, coinbase.verified_account
    - `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` 0 | 1: 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.

## Response `200`

Labels upserted. Single label in → bare `UserLabel`; array in → array of `UserLabel`. Echoes the normalised entries (lowercased tag_id; the caller-supplied `timestamp` when present, otherwise the server write time).

- union
  - UserLabel: Canonical user-label resource. Echoed back from upsert calls so callers can cache the normalised entry without a follow-up read.
    - `address` string, required: Wallet address the label applies to
    - `tag_id` string, required: Label identifier (lowercased + trimmed on write)
    - `value` string, required: Label value, empty string when omitted
    - `chain_id` string, required: Chain identifier, empty string when omitted
    - `source` string, required: Origin of the label (e.g. 'import')
    - `timestamp` string, date-time, required: Event-time of the label: the caller-supplied `timestamp` when provided (retrospective backfill), otherwise the server's write time.
  - UserLabel[]
    - `address` string, required: Wallet address the label applies to
    - `tag_id` string, required: Label identifier (lowercased + trimmed on write)
    - `value` string, required: Label value, empty string when omitted
    - `chain_id` string, required: Chain identifier, empty string when omitted
    - `source` string, required: Origin of the label (e.g. 'import')
    - `timestamp` string, date-time, required: Event-time of the label: the caller-supplied `timestamp` when provided (retrospective backfill), otherwise the server's write time.

## Other responses

- `400`: 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.
- `401`: Missing or invalid API key.
- `403`: The API key is valid but lacks the required scope for this endpoint.
- `404`: The requested resource does not exist or is not visible to this API key's workspace.
- `429`: Per-workspace rate limit exceeded. Inspect the `RateLimit-*` response headers and back off.
- `500`: An unexpected error occurred on the server. The error has been logged; retry with exponential backoff.

---

[API](https://skmtc.dev/formo/apis/formo-public-api.md) · [All operations](https://skmtc.dev/formo/apis/formo-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/formo/apis/formo-public-api/revisions/67ada57df63c?raw)
