---
title: "Release a quarantine with a reason"
method: POST
path: "/v3/labels/sets/{labelSetId}/holds/{holdId}/release"
tags: ["Labels"]
---

# Release a quarantine with a reason

`POST /v3/labels/sets/{labelSetId}/holds/{holdId}/release`

## Request

- Base URL: `https://v3.euler.finance`
- URL: `https://v3.euler.finance/v3/labels/sets/{labelSetId}/holds/{holdId}/release`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Releases one quarantine like DELETE on the hold, and records an optional
`reason` on the hold (`releaseReason`) and in the publication event. An empty
body releases with no reason. Accepts a platform session or a key with
`labels:reject`. Entity holds are released by resuming the entity and answer
409 here. `expectedContentHash` releases only the revision that was reviewed;
a draft that moved since answers 412 `stale-review` and nothing is released.

## Path parameters

- `labelSetId` string, required
- `holdId` string, uuid, required

## Request body

- LabelReleaseRequest
  - `reason` string, nullable: Why the hold is released or the entity resumed. Trimmed; blank or null records no reason.
  - `expectedContentHash` string, nullable: The draft revision the reviewer judged: the change log's `afterContentHash` for the row (null for a row the draft no longer has). Compared with the current draft row inside the write's transaction; when the row moved since, the answer is 412 with `details.reason` `stale-review` and the current `details.contentHash`, and nothing is written. Optional for now; keys will have to send it, and a key write without it is logged.

## Response `200`

Hold released.

- object
  - `data` LabelHold, required: A row held out of publication. A quarantine is a rejection of one row; an entity hold is raised by entity suspension and covers every row the entity owns. Held rows keep their published content on every full publish while the draft stays intact.
    - `id` string, uuid, required
    - `kind` 'quarantine' | 'entity-hold', required
    - `resource` union, required: Identifies one publishable row across the draft and every version.
      - object
        - `type` 'entity', required
        - `entityId` string, required
      - object
        - `type` 'product', required
        - `chainId` integer, required
        - `productId` string, required
      - object
        - `type` 'vault', required
        - `chainId` integer, required
        - `address` string, required
    - `reason` string, required
    - `contentHash` string, nullable, required: Hash of the draft row when the hold was raised; null when the draft lacked the row.
    - `heldBy` LabelHoldActor, required
      - `kind` 'platform-admin' | 'api-key' | 'system', required
      - `id` string, nullable, required: Returned to platform sessions only; keys see null.
      - `email` string, nullable, required: The platform administrator's email, for display next to the id. Returned to platform sessions only; null for keys, the system, and key sessions.
    - `createdAt` string, date-time, required
    - `releasedAt` string, date-time, nullable, required
    - `releasedBy` LabelHoldActor, required
      - `kind` 'platform-admin' | 'api-key' | 'system', required
      - `id` string, nullable, required: Returned to platform sessions only; keys see null.
      - `email` string, nullable, required: The platform administrator's email, for display next to the id. Returned to platform sessions only; null for keys, the system, and key sessions.
    - `releaseReason` string, nullable, required: Why the hold was released, when the release gave a reason: a quarantine release, or the entity resume that lifted an entity hold. Null while the hold is active.
  - `meta` PaginationMeta, required
    - `total` integer: Exact total count when the endpoint provides one.
    - `hasMore` boolean: Indicates whether another page exists beyond the current page.
    - `offset` integer
    - `limit` integer: Echoed page size after endpoint-side clamping.
    - `outcome` 'disabled' | 'removed': On a toolbox config DELETE, what happened to the row.
    - `products` ProductLabels[]: On a curation vault `POST`, `PUT`, or `PATCH`: the products the write touched (the one the vault left and the one it joined) as they are after the write, `governanceMode` included, so the caller applies them instead of re-reading.
      - `id` string, required
      - `chainId` integer, required
      - `entityId` string, required
      - `coBrandEntityIds` string[], required: Additional entities whose logos should be displayed. Product management remains exclusively with entityId.
      - `name` string, required
      - `logo` string, nullable
      - `description` string, nullable
      - `url` string, nullable: Absolute https URL.
      - `portfolioNotice` string, nullable
      - `isDeprecated` boolean, required
      - `deprecationReason` string, nullable
      - `notExplorable` boolean, nullable, required: Curator listing flag, outranked by platform overrides and by Euler's deprecation on the assessment. `null` leaves the decision to deprecation, `true` hides the vault(s) from discovery, `false` keeps listed a vault the curator deprecated, but never one Euler deprecated.
      - `governanceMode` 'governed' | 'ungoverned' | 'unknown', required: Derived by the server from the on-chain governor of member vaults; clients cannot set it. Products are created as `unknown`. Every vault assignment recomputes the mode over the resulting member set and rejects sets that mix governed and ungoverned vaults. Layer 2 adopts `governed` or `ungoverned` for an `unknown` product once its membership verifies against chain state. Entity members cannot update or delete an `ungoverned` product or its vaults after the initial assignment.
      - `resolved` ResolvedProductLabels, required: Read-time entity fallbacks for display. Derived, never stored.
        - `entityName` string, nullable, required
        - `url` string, nullable, required: Product url, falling back to the owning entity url.
        - `logo` string, nullable, required: Product logo, falling back to the owning entity logo.
      - `anyExplorableLend` boolean, required: True when at least one member vault is lend-discoverable per the materialized visibility verdicts. Live chain state (e.g. borrowability) is deliberately excluded.
      - `anyExplorableBorrow` boolean, required: True when at least one member vault is borrow-discoverable per the materialized visibility verdicts. Live chain state (e.g. borrowability) is deliberately excluded.
      - `createdAt` string, date-time, required
      - `updatedAt` string, date-time, required
      - `hold` LabelHoldSummary: What a draft read shows on a held row.
        - `id` string, uuid, required
        - `kind` 'quarantine' | 'entity-hold', required
        - `reason` string, required
        - `since` string, date-time, required
      - `platformListing` ProductPlatformListing: The platform-owned discovery decision on a product, as a product row shows it.
        - `notExplorableLend` boolean, required
        - `notExplorableBorrow` boolean, required
        - `reason` string, nullable, required
    - `timestamp` string, date-time, required
    - `chainId` string: Comma-separated chain IDs for multi-chain responses.
    - `degradedProviders` string[]: Live providers whose data is incomplete in this response.
    - `excludedChains` object[]: On a read across chains that names no `chainId`: the served chains it left out because they do not run the capability the operation needs, rather than reading them as empty. Absent when no chain was left out.
      - `chainId` integer, required
      - `capability` 'indexing' | 'activity' | 'prices' | 'historicalPrices' | 'assessments' | 'labels' | 'logos' | 'stats' | 'risk' | 'rewards' | 'apys', required

## Other responses

- `400`: Bad request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `409`: Conflict
- `412`: Precondition failed
- `422`: Validation error
- `500`: Internal server error
- `503`: Service unavailable

## Changes

- **2026-10-19** `f0ed0045ce41`: 1 info
  - added the optional property `meta/excludedChains` to the response with the `200` status
- **2026-10-09** `1b550dddfa46`: 1 warning, 2 info
  - removed the optional property `meta/origin` from the response with the `200` status
  - added the new optional request property `expectedContentHash`
  - added the non-success response with the status `412`
- **2026-10-05** `875b105fe12a`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/euler/apis/euler-data-api-v3/changes/v3/labels/sets/:labelSetId/holds/:holdId/release/post.md)

---

[API](https://skmtc.dev/euler/apis/euler-data-api-v3.md) - [All operations](https://skmtc.dev/euler/apis/euler-data-api-v3/llms.txt) - [OpenAPI document](https://skmtc.dev/euler/apis/euler-data-api-v3/revisions/e987d7695ec1?raw)
