---
title: "Revoke assets (batch)"
method: POST
path: "/api/v1/me/assets/revoke"
tags: ["Assets"]
---

# Revoke assets (batch)

`POST /api/v1/me/assets/revoke`

## Request

- Base URL: `https://api.proof.holdings`
- URL: `https://api.proof.holdings/api/v1/me/assets/revoke`
- Auth: API key in cookie `auth_token`

Revoke the proofs of up to 25 assets — any mix of phone, email and domain — with ONE step-up code. Revoking withdraws PROOFS, not the identifier: a phone or email row stays active (signing in with it again issues a new proof). Revoking one asset of a phone or email revokes every asset of that identifier. Revoking a DOMAIN asset runs the whole-domain cascade: every proof of the domain and every delegation issued from it are revoked, and the domain is demoted to `reverification_required` with its DNS challenge rotated. Cannot be undone. Owner only: an API key answers `403 owner_session_required` for every asset type (revoke the proof itself with `POST /proofs/{id}/revoke` instead). A session needs a single-use `revocation_bulk` step-up grant bound to exactly the ids it sends (`403 2fa_required`; `reason: target_mismatch` when the grant names other ids). A notice goes to the owner. Per-item outcomes; idempotent on retry (an already-revoked id counts as done, an unknown one as `not_found`, and a batch with nothing left to revoke spends no grant). Owner session only — this route sits below the session gate, so an API key is refused 401. Projects and the account have no batch form.

## Request body

- object
  - `asset_ids` string[], required: Asset ids, 1–25. Order and duplicates do not matter to the grant.

## Response `200`

Per-item outcomes

- object
  - `success` true, required
  - `items` AssetRevocationItem[], required: One per id sent, in the order sent (duplicates collapsed).
    - `asset_id` string, required
    - `outcome` 'revoked' | 'already_revoked' | 'not_found', required: `not_found` is also the answer for an asset the account does not own.
    - `type` 'phone' | 'email' | 'domain' | 'social' | 'wallet' | 'telegram_bot'
    - `identifier` string: Masked for phones and emails; a domain travels in clear.
    - `revoked_asset_ids` string[]: Every asset of the same identifier this call revoked with it, the named one included — a phone holds one asset per channel, a domain one per verification.
    - `delegations_revoked` object[]: Domains only: the delegations revoked with the proofs. Re-verifying does not bring them back.
      - `proof_id` string, required
      - `delegate` string, required
      - `delegate_type` string, required
      - `principal` string, required
    - `domain_status` 'pending' | 'verifying' | 'verified' | 'failed' | 'expired' | 'revoking' | 'reverification_required' | 'null', nullable: Domains only: `reverification_required` — the domain stays on the account and needs re-verification before anything can be issued from it.
  - `revoked_asset_ids` string[], required: Everything revoked across all items, siblings included.
  - `notice` object[], required
    - `kind` 'email' | 'telegram' | 'whatsapp', required
    - `address` string, required: Masked.
    - `outcome` string, required: `sent`, `skipped:<why>` or `failed:<why>`. A notice is a courtesy after an act that already committed; a failed one never fails the request.

## Other responses

- `400`: `validation_error` — not 1–25 well-formed asset ids
- `401`: No owner session (an API key is refused here)
- `403`: `2fa_required` — a `revocation_bulk` grant bound to exactly these ids is required (`reason: target_mismatch` when the grant names other ids); nothing was changed
- `409`: `domain_revoking` — a revocation of this domain is already running; nothing was changed and no step-up code was spent. Retry in a moment

---

[API](https://skmtc.dev/proof/apis/proof-holdings-api.md) · [All operations](https://skmtc.dev/proof/apis/proof-holdings-api/llms.txt) · [OpenAPI document](https://skmtc.dev/proof/apis/proof-holdings-api/revisions/b15f506de696?raw)
