Asset Details Endpoints
Public reads: search and fetch assets in the v2 shape, icons and the append-only audit log. No authentication; CORS enabled.
| Network | Base URL |
|---|---|
| Liquid | https://assets.blockstream.info/v2 |
| Liquid Testnet | https://assets-testnet.blockstream.info/v2 |
Examples use the Liquid (mainnet) base URL. For testnet, swap in the testnet base URL.
Endpoints
| Method | Endpoint | Path |
|---|---|---|
| GET | Search assets | /v2/assets |
| GET | Get an asset | /v2/assets/:asset_id |
| GET | Get all assets | /v2/assets/all.json |
| GET | Get the current icon | /v2/assets/:asset_id/icon |
| GET | Get an icon by hash | /v2/assets/:asset_id/icon/:icon_hash.png |
| GET | Get an asset's audit log | /v2/assets/:asset_id/audit |
| GET | Search the global audit log | /v2/audit |
| GET | Health check | /health |
Assets
Search assets
GET /v2/assets
Paginated list of active assets with filters and sorting. Each item is a full asset object.
Fields: items, page, page_size, total_count, total_pages.
Parameters
| Name | In · type | Description |
|---|---|---|
If-Modified-Since | header · HTTP date | The Last-Modified value from your previous response. Returns 304 with no body if no asset has changed since. Caching. |
page | query · integer | 1-based page. Default 1. |
page_size | query · integer | 1–500. Default 50. |
sort | query · enum | <field>_asc or <field>_desc, where field is asset_id, name, ticker, domain, created_at or updated_at. Default asset_id_asc. |
asset_id | query · hex | Case-insensitive asset ID prefix. |
ticker | query · string | Case-insensitive ticker prefix. |
name | query · string | Case-insensitive name prefix. |
domain | query · string | Case-insensitive exact issuer domain. |
asset_type | query · enum | AMP_asset, stablecoin, security_token, other (set by registry admins) |
category_tag | query · enum, repeatable | stablecoin, bond, fixed-income, tokenized today. Several values match assets with any of them. Registry admins define this list and can add, change or remove values, so don't hard-code it. |
trading_venue | query · enum | sideswap or bitfinex |
created_after | query · RFC 3339 | Strictly after this time. |
updated_after | query · RFC 3339 | Strictly after this time. Use it for incremental sync. |
Unknown query parameters are rejected with 400 (
unknown query parameter), so don't add cache-busters or tracking parameters.
ticker,nameandasset_idare prefix matches:ticker=BMNreturns BMN1 and BMN2. For an exact ticker, filter the results yourself.
Keeping a local copy up to date? Follow Sync the registry:
updated_afteralone never reports removed assets.
Example request
curl "https://assets.blockstream.info/v2/assets?ticker=BMN&sort=ticker_asc&page=1&page_size=2"// Node.js 18+ (ES module: save as .mjs or set "type": "module")
const BASE_URL = "https://assets.blockstream.info";
const res = await fetch(`${BASE_URL}/v2/assets?` + new URLSearchParams([["ticker","BMN"],["sort","ticker_asc"],["page","1"],["page_size","2"]]));
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(JSON.stringify(await res.json(), null, 2)); // full output: plain console.log cuts long strings and deep objects# pip install requests
import requests
BASE_URL = "https://assets.blockstream.info"
res = requests.get(f"{BASE_URL}/v2/assets", params={"ticker": "BMN", "sort": "ticker_asc", "page": "1", "page_size": "2"}, timeout=30)
if not res.ok:
raise RuntimeError(f"{res.status_code} {res.text}")
print(res.json())package main
import (
"fmt"
"io"
"net/http"
"net/url"
)
func main() {
baseURL := "https://assets.blockstream.info"
q := url.Values{}
q.Add("ticker", "BMN")
q.Add("sort", "ticker_asc")
q.Add("page", "1")
q.Add("page_size", "2")
endpoint := baseURL + "/v2/assets" + "?" + q.Encode()
res, err := http.Get(endpoint)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, body))
}
fmt.Println(string(body))
}Response: 200 (production · application/json)
{
"items": [
{
"asset_id": "11f91cb5edd5d0822997ad81f068ed35002daec33986da173461a8427ac857e1",
"contract": {
"entity": {
"domain": "blockstream.com"
},
"name": "BMN Series 1",
"precision": 2,
"ticker": "BMN1",
"version": 0,
"issuer_pubkey": "02a1ff5fd5d6f17febe5cd2d6b9e45b6775a1e416b56fa8414e337e2589c2e9bd5"
},
"issuance_txin": {
"txid": "6fbe5f8aedadf1fbfe023e97bb64d27a600c2c71fbf852d11059606e4a8f43c0",
"vin": 0
},
"issuance_prevout": {
"txid": "ed790fac247b5dfff4c0529d9b562d0f76757f7c08256b41fc2469b9a85bb3f9",
"vout": 3
},
"initial_issuer_pubkey": "02a1ff5fd5d6f17febe5cd2d6b9e45b6775a1e416b56fa8414e337e2589c2e9bd5",
"initial_issuer_pubkey_source": "registry_registration",
"current_issuer_pubkey": "02a1ff5fd5d6f17febe5cd2d6b9e45b6775a1e416b56fa8414e337e2589c2e9bd5",
"issuer_pubkey_history": [
{
"pubkey": "02a1ff5fd5d6f17febe5cd2d6b9e45b6775a1e416b56fa8414e337e2589c2e9bd5",
"valid_from_audit_id": 587,
"valid_until_audit_id": null
}
],
"mutable": {
"trading_venues": [],
"category_tags": [],
"custom": {}
},
"admin": {
"asset_type": null,
"featured": false,
"malicious": false,
"delisted": false,
"admin_notes": null,
"last_admin_action": null
},
"icon": {
"href": "/v2/assets/11f91cb5edd5d0822997ad81f068ed35002daec33986da173461a8427ac857e1/icon/e0f4c1f8eb9ab39bf0f835cbbb976a0a9ca42edf505df93118c59150ee979be9.png"
},
"status": "active",
"created_at": "2026-07-16T15:08:46.919893Z",
"updated_at": "2026-08-17T15:35:14.442979Z"
},
{
"asset_id": "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034",
"contract": {
"entity": {
"domain": "blockstream-mining2.stokr.io"
},
"name": "BMN Series 2",
"precision": 4,
"ticker": "BMN2",
"version": 0,
"issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b"
},
"issuance_txin": {
"txid": "9bd4592c4238146e3b116f0b307100e38d58a26c62465e53c0ba7b8511ce7c88",
"vin": 0
},
"issuance_prevout": {
"txid": "b9317d00e44ab9f4ce435677b9b3f915e09d12b2be648da6bd12cd790d12658d",
"vout": 3
},
"initial_issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"initial_issuer_pubkey_source": "registry_registration",
"current_issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"issuer_pubkey_history": [
{
"pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"valid_from_audit_id": 13962,
"valid_until_audit_id": null
}
],
"mutable": {
"trading_venues": [],
"category_tags": [],
"custom": {}
},
"admin": {
"asset_type": null,
"featured": false,
"malicious": false,
"delisted": false,
"admin_notes": null,
"last_admin_action": {
"action": "set_icon",
"field": null,
"admin_id": "8ecf873d-0463-440e-b6d2-e2c147958888",
"timestamp": "2026-10-20T19:15:36.407298Z"
}
},
"icon": {
"href": "/v2/assets/aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034/icon/9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212.png"
},
"status": "active",
"created_at": "2026-07-16T15:14:51.930732Z",
"updated_at": "2026-10-20T19:15:36.438915Z"
}
],
"page": 1,
"page_size": 2,
"total_count": 2,
"total_pages": 1
}Error: unsupported sort: 422 (production · application/json)
{
"detail": [
{
"type": "literal_error",
"loc": [
"query",
"sort"
],
"msg": "Input should be 'asset_id_asc', 'asset_id_desc', 'domain_asc', 'domain_desc', 'name_asc', 'name_desc', 'ticker_asc', 'ticker_desc', 'created_at_asc', 'created_at_desc', 'updated_at_asc' or 'updated_at_desc'",
"input": "newest",
"ctx": {
"expected": "'asset_id_asc', 'asset_id_desc', 'domain_asc', 'domain_desc', 'name_asc', 'name_desc', 'ticker_asc', 'ticker_desc', 'created_at_asc', 'created_at_desc', 'updated_at_asc' or 'updated_at_desc'"
}
}
]
}Error: unknown query parameter: 400 (production · application/json)
{
"error": "validation_error",
"message": "unknown query parameter",
"details": {
"parameters": [
"limit"
]
}
}Get an asset
GET /v2/assets/:asset_id
One active asset, in the v2 shape: immutable contract, issuer keys, issuer-controlled metadata, admin annotations and icon. Works for assets registered through either API.
Returns an asset object.
Parameters
| Name | In · type | Description |
|---|---|---|
asset_id required | path · hex (64) | Liquid asset ID. Upper-case hex is accepted and normalized. |
If-Modified-Since | header · HTTP date | The Last-Modified value from your previous response. Returns 304 with no body if no asset has changed since. Caching. |
Moderation doesn't hide assets. A delisted or malicious asset is still returned with
status: "active". Checkadmin.delistedandadmin.maliciousbefore showing it to users.
Example request
curl "https://assets.blockstream.info/v2/assets/aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"// Node.js 18+ (ES module: save as .mjs or set "type": "module")
const BASE_URL = "https://assets.blockstream.info";
const ASSET_ID = "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034";
const res = await fetch(`${BASE_URL}/v2/assets/${ASSET_ID}`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(JSON.stringify(await res.json(), null, 2)); // full output: plain console.log cuts long strings and deep objects# pip install requests
import requests
BASE_URL = "https://assets.blockstream.info"
ASSET_ID = "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"
res = requests.get(f"{BASE_URL}/v2/assets/{ASSET_ID}", timeout=30)
if not res.ok:
raise RuntimeError(f"{res.status_code} {res.text}")
print(res.json())package main
import (
"fmt"
"io"
"net/http"
)
func main() {
baseURL := "https://assets.blockstream.info"
assetID := "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"
endpoint := baseURL + "/v2/assets/" + assetID
res, err := http.Get(endpoint)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, body))
}
fmt.Println(string(body))
}Response: 200 (production · application/json)
{
"asset_id": "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034",
"contract": {
"entity": {
"domain": "blockstream-mining2.stokr.io"
},
"name": "BMN Series 2",
"precision": 4,
"ticker": "BMN2",
"version": 0,
"issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b"
},
"issuance_txin": {
"txid": "9bd4592c4238146e3b116f0b307100e38d58a26c62465e53c0ba7b8511ce7c88",
"vin": 0
},
"issuance_prevout": {
"txid": "b9317d00e44ab9f4ce435677b9b3f915e09d12b2be648da6bd12cd790d12658d",
"vout": 3
},
"initial_issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"initial_issuer_pubkey_source": "registry_registration",
"current_issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"issuer_pubkey_history": [
{
"pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"valid_from_audit_id": 13962,
"valid_until_audit_id": null
}
],
"mutable": {
"trading_venues": [],
"category_tags": [],
"custom": {}
},
"admin": {
"asset_type": null,
"featured": false,
"malicious": false,
"delisted": false,
"admin_notes": null,
"last_admin_action": {
"action": "set_icon",
"field": null,
"admin_id": "8ecf873d-0463-440e-b6d2-e2c147958888",
"timestamp": "2026-10-20T19:15:36.407298Z"
}
},
"icon": {
"href": "/v2/assets/aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034/icon/9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212.png"
},
"status": "active",
"created_at": "2026-07-16T15:14:51.930732Z",
"updated_at": "2026-10-20T19:15:36.438915Z"
}Error: unknown or deregistered asset: 404 (production · application/json)
{
"error": "asset_not_found",
"message": "asset not found",
"details": {
"asset_id": "0000000000000000000000000000000000000000000000000000000000000000"
}
}Get all assets
GET /v2/assets/all.json
Every active asset in one JSON object keyed by asset ID (v2 shape). Over 20 MB on mainnet; Accept-Encoding: gzip cuts it about tenfold.
Parameters
| Name | In · type | Description |
|---|---|---|
If-Modified-Since | header · HTTP date | The Last-Modified value from your previous response. Returns 304 with no body if no asset has changed since. Caching. |
Use it once to bootstrap a local copy, then sync incrementally.
Sent with
Cache-Control: no-cacheand a registry-wideLast-Modified. Send that value back inIf-Modified-Sinceto get a bodyless 304 when nothing changed. See Caching.
Example request
curl "https://assets.blockstream.info/v2/assets/all.json"// Node.js 18+ (ES module: save as .mjs or set "type": "module")
const BASE_URL = "https://assets.blockstream.info";
const res = await fetch(`${BASE_URL}/v2/assets/all.json`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(JSON.stringify(await res.json(), null, 2)); // full output: plain console.log cuts long strings and deep objects# pip install requests
import requests
BASE_URL = "https://assets.blockstream.info"
res = requests.get(f"{BASE_URL}/v2/assets/all.json", timeout=30)
if not res.ok:
raise RuntimeError(f"{res.status_code} {res.text}")
print(res.json())package main
import (
"fmt"
"io"
"net/http"
)
func main() {
baseURL := "https://assets.blockstream.info"
endpoint := baseURL + "/v2/assets/all.json"
res, err := http.Get(endpoint)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, body))
}
fmt.Println(string(body))
}Response: 200 (production · application/json · 14,230 entries, first shown)
{
"aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034": {
"asset_id": "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034",
"contract": {
"entity": {
"domain": "blockstream-mining2.stokr.io"
},
"name": "BMN Series 2",
"precision": 4,
"ticker": "BMN2",
"version": 0,
"issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b"
},
"initial_issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"initial_issuer_pubkey_source": "registry_registration",
"current_issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"issuer_pubkey_history": [
{
"pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"valid_from_audit_id": 13962,
"valid_until_audit_id": null
}
],
"mutable": {
"trading_venues": [],
"category_tags": [],
"custom": {}
},
"admin": {
"asset_type": null,
"featured": false,
"malicious": false,
"delisted": false,
"admin_notes": null,
"last_admin_action": {
"action": "set_icon",
"field": null,
"admin_id": "8ecf873d-0463-440e-b6d2-e2c147958888",
"timestamp": "2026-10-20T19:15:36.407298Z"
}
},
"icon": {
"href": "/v2/assets/aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034/icon/9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212.png"
},
"status": "active",
"created_at": "2026-07-16T15:14:51.930732Z",
"updated_at": "2026-10-20T19:15:36.438915Z",
"issuance_txin": {
"txid": "9bd4592c4238146e3b116f0b307100e38d58a26c62465e53c0ba7b8511ce7c88",
"vin": 0
},
"issuance_prevout": {
"txid": "b9317d00e44ab9f4ce435677b9b3f915e09d12b2be648da6bd12cd790d12658d",
"vout": 3
}
}
}Icons
Get the current icon
GET /v2/assets/:asset_id/icon
Redirects (307, Cache-Control: no-cache) to the asset's current content-addressed icon. Use it when you want one stable URL per asset.
Parameters
| Name | In · type | Description |
|---|---|---|
asset_id required | path · hex (64) | Liquid asset ID. Upper-case hex is accepted and normalized. |
Example request
curl -i "https://assets.blockstream.info/v2/assets/aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034/icon" # 307 with a Location header; add -L to follow it// Node.js 18+ (ES module: save as .mjs or set "type": "module")
const BASE_URL = "https://assets.blockstream.info";
const ASSET_ID = "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034";
const res = await fetch(`${BASE_URL}/v2/assets/${ASSET_ID}/icon`, { redirect: "manual" });
console.log(res.status, res.headers.get("location"));# pip install requests
import requests
BASE_URL = "https://assets.blockstream.info"
ASSET_ID = "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"
res = requests.get(f"{BASE_URL}/v2/assets/{ASSET_ID}/icon", allow_redirects=False, timeout=30)
print(res.status_code, res.headers["Location"])package main
import (
"fmt"
"net/http"
)
func main() {
baseURL := "https://assets.blockstream.info"
assetID := "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"
endpoint := baseURL + "/v2/assets/" + assetID + "/icon"
client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
res, err := client.Get(endpoint)
if err != nil {
panic(err)
}
defer res.Body.Close()
fmt.Println(res.StatusCode, res.Header.Get("Location"))
}Response: 307 (production)
Location: /v2/assets/aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034/icon/9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212.pngError: no approved icon: 404 (production · application/json)
{
"error": "icon_not_found",
"message": "published icon not found",
"details": {
"asset_id": "a6027bba1721182eb6c422b028ec805ad113ffcd1288b680d7a5882d0736a002"
}
}Get an icon by hash
GET /v2/assets/:asset_id/icon/:icon_hash.png
The PNG bytes of a published icon. The URL contains the SHA-256 of the bytes, so the response never changes: cache it forever. This is the URL in an asset's icon.href.
Parameters
| Name | In · type | Description |
|---|---|---|
asset_id required | path · hex (64) | Liquid asset ID. Upper-case hex is accepted and normalized. |
icon_hash required | path · hex (64) | SHA-256 of the PNG bytes. |
Headers:
Cache-Control: public, max-age=31536000, immutable,ETag: "<icon_hash>"(quoted),X-Content-Type-Options: nosniff. Send the ETag back inIf-None-Matchto get a 304; see Caching.
New icons are exactly 500×500, but icons imported from the legacy registry can have other sizes (e.g. 537×537). Scale them to your layout.
Example request
curl -o icon.png "https://assets.blockstream.info/v2/assets/aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034/icon/9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212.png"// Node.js 18+ (ES module: save as .mjs or set "type": "module")
const BASE_URL = "https://assets.blockstream.info";
const ASSET_ID = "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034";
const ICON_HASH = "9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212";
const res = await fetch(`${BASE_URL}/v2/assets/${ASSET_ID}/icon/${ICON_HASH}.png`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
await (await import("node:fs/promises")).writeFile("icon.png", Buffer.from(await res.arrayBuffer()));
console.log("saved icon.png, ETag", res.headers.get("etag"));# pip install requests
import requests
BASE_URL = "https://assets.blockstream.info"
ASSET_ID = "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"
ICON_HASH = "9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212"
res = requests.get(f"{BASE_URL}/v2/assets/{ASSET_ID}/icon/{ICON_HASH}.png", timeout=30)
if not res.ok:
raise RuntimeError(f"{res.status_code} {res.text}")
with open("icon.png", "wb") as f:
f.write(res.content)
print("saved icon.png, ETag", res.headers["ETag"])package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
baseURL := "https://assets.blockstream.info"
assetID := "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"
iconHash := "9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212"
endpoint := baseURL + "/v2/assets/" + assetID + "/icon/" + iconHash + ".png"
res, err := http.Get(endpoint)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, body))
}
if err := os.WriteFile("icon.png", body, 0o644); err != nil {
panic(err)
}
fmt.Println("saved icon.png, ETag", res.Header.Get("ETag"))
}Response: 200 (production · image/png)
<10836 bytes of PNG>Conditional request (If-None-Match): 304 (production)
(no body)Audit
Get an asset's audit log
GET /v2/assets/:asset_id/audit
Append-only history of every accepted change to one asset, oldest first by default. Assets imported from the legacy registry start with a legacy_register entry. See audit entry and verifying the trail.
Fields: items, next_since_audit_id (see Audit entry).
Parameters
| Name | In · type | Description |
|---|---|---|
asset_id required | path · hex (64) | Liquid asset ID. Upper-case hex is accepted and normalized. |
since_audit_id | query · integer | Exclusive cursor. Default 0. |
limit | query · integer | 1–1000. Default 100. |
order | query · asc | desc | Default asc. |
To read everything, page with
order=ascand passnext_since_audit_idback assince_audit_id. Useorder=desc&limit=Nonly for "latest N": the cursor moves forward, not backward.
Example request
curl "https://assets.blockstream.info/v2/assets/aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034/audit?limit=10&order=asc"// Node.js 18+ (ES module: save as .mjs or set "type": "module")
const BASE_URL = "https://assets.blockstream.info";
const ASSET_ID = "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034";
const res = await fetch(`${BASE_URL}/v2/assets/${ASSET_ID}/audit?` + new URLSearchParams([["limit","10"],["order","asc"]]));
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(JSON.stringify(await res.json(), null, 2)); // full output: plain console.log cuts long strings and deep objects# pip install requests
import requests
BASE_URL = "https://assets.blockstream.info"
ASSET_ID = "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"
res = requests.get(f"{BASE_URL}/v2/assets/{ASSET_ID}/audit", params={"limit": "10", "order": "asc"}, timeout=30)
if not res.ok:
raise RuntimeError(f"{res.status_code} {res.text}")
print(res.json())package main
import (
"fmt"
"io"
"net/http"
"net/url"
)
func main() {
baseURL := "https://assets.blockstream.info"
assetID := "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034"
q := url.Values{}
q.Add("limit", "10")
q.Add("order", "asc")
endpoint := baseURL + "/v2/assets/" + assetID + "/audit" + "?" + q.Encode()
res, err := http.Get(endpoint)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, body))
}
fmt.Println(string(body))
}Response (legacy asset: one imported entry): 200 (production · application/json)
{
"items": [
{
"audit_id": 13962,
"server_received_at": "2026-07-16T15:14:51.930732Z",
"actor": "system",
"verified_pubkey": null,
"admin_id": null,
"action": {
"request": {
"name": "BMN Series 2",
"entity": {
"domain": "blockstream-mining2.stokr.io"
},
"ticker": "BMN2",
"version": 0,
"asset_id": "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034",
"contract": {
"name": "BMN Series 2",
"entity": {
"domain": "blockstream-mining2.stokr.io"
},
"ticker": "BMN2",
"version": 0,
"precision": 4,
"issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b"
},
"precision": 4,
"collection": null,
"issuance_txin": {
"vin": 0,
"txid": "9bd4592c4238146e3b116f0b307100e38d58a26c62465e53c0ba7b8511ce7c88"
},
"issuer_pubkey": "0206eb0dd95be6ace6d83e5e865815d73bed9e553a1a829fe8c2de74189ee2289b",
"issuance_prevout": {
"txid": "b9317d00e44ab9f4ce435677b9b3f915e09d12b2be648da6bd12cd790d12658d",
"vout": 3
}
},
"contract_hash": "998614e8941c5cc4fd674e1d9b29e53468ced64290ec5742273659ef103cc461"
},
"action_hash": null,
"signature": null
},
{
"audit_id": 16007,
"server_received_at": "2026-08-17T15:35:14.442979Z",
"actor": "system",
"verified_pubkey": null,
"admin_id": null,
"action": {
"source": "legacy_icons_json",
"asset_id": "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034",
"icon_hash": "a6fa6352bfd700a23aea18ab5494cf08de30a063e2deb18b02163e905b6537a0",
"operation": "import_legacy_icon"
},
"action_hash": null,
"signature": null
},
{
"audit_id": 17823,
"server_received_at": "2026-10-20T19:15:36.407298Z",
"actor": "admin",
"verified_pubkey": "02fa74607f51aba28596cceb95e7c6c6e4ff44a0b0cf76f3045df80e34f04c5eb8",
"admin_id": "8ecf873d-0463-440e-b6d2-e2c147958888",
"action": {
"nonce": "f212bbde3d80ae4a9b295b5b055b7d8a",
"asset_id": "aa909f1b77451e409fe95fe1d3638ad017ab3325c6d4f00301af6d582d0f2034",
"icon_hash": "9389e4e5458f76347a0f5bcd8f963f6aa3e14c1b6b923d5304c91f63614a5212",
"operation": "set_icon",
"timestamp": "2026-10-20T19:15:36Z",
"actor_pubkey": "02fa74607f51aba28596cceb95e7c6c6e4ff44a0b0cf76f3045df80e34f04c5eb8",
"signing_context": "liquid-asset-registry-admin-action-v1"
},
"action_hash": null,
"signature": "uQb6mz9Cg6s7STaloLHjd7h0DFZXOewpD7k3dEtj/E3Heul5DGJ1Jmt2hcnbEV9G1gtkd5HUdh6vqGxhtVJPCg=="
}
],
"next_since_audit_id": 17823
}A v2 asset with issuer and admin actions: 200 (local instance · application/json · showing 3 of 12 items)
{
"items": [
{
"audit_id": 1,
"server_received_at": "2026-11-03T22:47:34.213879Z",
"actor": "system",
"verified_pubkey": null,
"admin_id": null,
"action": {
"mutable": {
"custom": {
"website": "https://example-issuer.com"
},
"category_tags": [
"stablecoin"
],
"trading_venues": [
{
"url": "https://sideswap.io/",
"venue": "sideswap"
}
]
},
"asset_id": "5ac9a0f9d1b54e2c8e7f3a6b2d4c8e1f0a9b7c5d3e1f2a4b6c8d0e2f4a6b8c0d",
"contract": {
"name": "Example Dollar",
"entity": {
"domain": "example-issuer.com"
},
"ticker": "EXUSD",
"version": 2,
"precision": 8,
"initial_issuer_pubkey": "03566ecb682f1a49ccad62a6dbc5b4045d96d65912bbbb177b7121c27c01d91e6e"
},
"operation": "register",
"contract_hash": "d0d6126a9574aaf930fead73d0859da1c807cc0756ae6ca77392fd14418c6f9e",
"initial_issuer_pubkey": "03566ecb682f1a49ccad62a6dbc5b4045d96d65912bbbb177b7121c27c01d91e6e",
"domain_verification_method": "dns",
"initial_issuer_pubkey_source": "contract"
},
"action_hash": "3c19a3bef43921f4ce0dfa865da2c08f153e9285fcef31db4f54b97455f89547",
"signature": null
},
{
"audit_id": 3,
"server_received_at": "2026-11-03T22:47:34.326597Z",
"actor": "issuer",
"verified_pubkey": "03566ecb682f1a49ccad62a6dbc5b4045d96d65912bbbb177b7121c27c01d91e6e",
"admin_id": null,
"action": {
"nonce": "391bad7c-f582-436e-a026-29eea333bff9",
"asset_id": "5ac9a0f9d1b54e2c8e7f3a6b2d4c8e1f0a9b7c5d3e1f2a4b6c8d0e2f4a6b8c0d",
"operation": "replace_category_tags",
"timestamp": "2026-11-03T22:47:34.323000Z",
"category_tags": [
"stablecoin",
"tokenized"
],
"signing_context": "liquid-asset-registry-action-v1",
"prev_action_hash": "3c19a3bef43921f4ce0dfa865da2c08f153e9285fcef31db4f54b97455f89547",
"mutable_schema_version": 1
},
"action_hash": "84a664d89014aedb77fab1f145dcc60d98d2f1632d31e8c0bdbe46be9c1a4d7f",
"signature": "Fa1R9CBMVbDLBcltLo1IRkF87XT4pUDgE68SzhcJ6XJoJzv4zyt0yE2Ka+PYXFR83Ab6bX6Frv9aEMDeXDjksg=="
},
{
"audit_id": 4,
"server_received_at": "2026-11-03T22:47:34.360514Z",
"actor": "issuer",
"verified_pubkey": "03566ecb682f1a49ccad62a6dbc5b4045d96d65912bbbb177b7121c27c01d91e6e",
"admin_id": null,
"action": {
"nonce": "533ba854-99fa-445f-8db6-bd32a0850839",
"asset_id": "5ac9a0f9d1b54e2c8e7f3a6b2d4c8e1f0a9b7c5d3e1f2a4b6c8d0e2f4a6b8c0d",
"operation": "replace_trading_venues",
"timestamp": "2026-11-03T22:47:34.359000Z",
"trading_venues": [
{
"url": "https://sideswap.io/",
"venue": "sideswap"
},
{
"url": "https://trading.bitfinex.com/t/EXUSD:USD",
"venue": "bitfinex"
}
],
"signing_context": "liquid-asset-registry-action-v1",
"prev_action_hash": "84a664d89014aedb77fab1f145dcc60d98d2f1632d31e8c0bdbe46be9c1a4d7f",
"mutable_schema_version": 1
},
"action_hash": "e0f86da7419b1594bdfdae4bb264468e423d063ed59b47a39ec35536c3984c5b",
"signature": "O453jjZDsHqq0U+qYLYgpVRItgZzqYQ2b75pSWMAL0xGhKPQSfP+lnYzPVlMGaF1bQd/sib85TfZh6+dcEGY+Q=="
}
],
"next_since_audit_id": 22
}Search the global audit log
GET /v2/audit
All assets' actions and admin lifecycle actions in one stream, ordered by the global audit_id.
Fields: items, next_since_audit_id (see Audit entry).
Parameters
| Name | In · type | Description |
|---|---|---|
asset_id | query · hex (64) | Only this asset. |
operation | query · string | e.g. legacy_register, replace_custom, add_admin. |
actor | query · enum | issuer, admin or system. |
from_server_received_at | query · RFC 3339 | Inclusive lower bound. |
to_server_received_at | query · RFC 3339 | Upper bound. |
since_audit_id | query · integer | Exclusive cursor. Default 0. |
limit | query · integer | 1–1000. Default 100. |
order | query · asc | desc | Default asc. |
Example request
curl "https://assets.blockstream.info/v2/audit?limit=2&order=desc"// Node.js 18+ (ES module: save as .mjs or set "type": "module")
const BASE_URL = "https://assets.blockstream.info";
const res = await fetch(`${BASE_URL}/v2/audit?` + new URLSearchParams([["limit","2"],["order","desc"]]));
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(JSON.stringify(await res.json(), null, 2)); // full output: plain console.log cuts long strings and deep objects# pip install requests
import requests
BASE_URL = "https://assets.blockstream.info"
res = requests.get(f"{BASE_URL}/v2/audit", params={"limit": "2", "order": "desc"}, timeout=30)
if not res.ok:
raise RuntimeError(f"{res.status_code} {res.text}")
print(res.json())package main
import (
"fmt"
"io"
"net/http"
"net/url"
)
func main() {
baseURL := "https://assets.blockstream.info"
q := url.Values{}
q.Add("limit", "2")
q.Add("order", "desc")
endpoint := baseURL + "/v2/audit" + "?" + q.Encode()
res, err := http.Get(endpoint)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, body))
}
fmt.Println(string(body))
}Response (latest 2 entries): 200 (production · application/json)
{
"items": [
{
"audit_id": 18230,
"server_received_at": "2026-11-03T16:54:07.182798Z",
"actor": "admin",
"verified_pubkey": "02e3e9ed5dcf5e468e66dacee5722df414237c2905ad488b2a535fd6a0a8412c7a",
"admin_id": "23c12594-9fbe-4084-a631-85453e254d0f",
"action": {
"nonce": "a2ba13f9-a5fd-4045-97b6-9b4ca8a7b1b4",
"operation": "add_admin",
"timestamp": "2026-11-03T16:54:06.889Z",
"permissions": [
"migrate_assets",
"review_icons",
"manage_icons",
"annotate_assets",
"delist_assets"
],
"actor_pubkey": "02e3e9ed5dcf5e468e66dacee5722df414237c2905ad488b2a535fd6a0a8412c7a",
"admin_pubkey": "029be720939041f800d368d9a62c2a20c6016dc2c9cabb37d30c0835e3c0d2ea67",
"friendly_name": "raspitor",
"signing_context": "liquid-asset-registry-admin-action-v1"
},
"action_hash": null,
"signature": "7wVPGrMNPKSRDcXnrwZPIlDIAEOhavftjtD6A/KT8ZwZ7W19oBPQaSXYLgZ33Ujmlt1lyVLyUtJnuDYBBYsXhw=="
},
{
"audit_id": 18229,
"server_received_at": "2026-11-03T16:52:40.519469Z",
"actor": "admin",
"verified_pubkey": "02e3e9ed5dcf5e468e66dacee5722df414237c2905ad488b2a535fd6a0a8412c7a",
"admin_id": "23c12594-9fbe-4084-a631-85453e254d0f",
"action": {
"nonce": "725945f6-0fe0-434a-9e96-6aaa7d3efe00",
"operation": "remove_admin",
"timestamp": "2026-11-03T16:52:40.147Z",
"actor_pubkey": "02e3e9ed5dcf5e468e66dacee5722df414237c2905ad488b2a535fd6a0a8412c7a",
"admin_pubkey": "02fa74607f51aba28596cceb95e7c6c6e4ff44a0b0cf76f3045df80e34f04c5eb8",
"signing_context": "liquid-asset-registry-admin-action-v1"
},
"action_hash": null,
"signature": "gZNTzt+T7aBOdUVVhlbPeSW3//gFnyBjpTpbOs1JK5JIKfBVtDZBneTH8uhP5GHQMrvk8LR05A8MNdmtETcF+Q=="
}
],
"next_since_audit_id": 18230
}Service
Health check
GET /health
Process health. Returns 200 with the deployment environment.
Example request
curl "https://assets.blockstream.info/health"// Node.js 18+ (ES module: save as .mjs or set "type": "module")
const BASE_URL = "https://assets.blockstream.info";
const res = await fetch(`${BASE_URL}/health`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(JSON.stringify(await res.json(), null, 2)); // full output: plain console.log cuts long strings and deep objects# pip install requests
import requests
BASE_URL = "https://assets.blockstream.info"
res = requests.get(f"{BASE_URL}/health", timeout=30)
if not res.ok:
raise RuntimeError(f"{res.status_code} {res.text}")
print(res.json())package main
import (
"fmt"
"io"
"net/http"
)
func main() {
baseURL := "https://assets.blockstream.info"
endpoint := baseURL + "/health"
res, err := http.Get(endpoint)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, body))
}
fmt.Println(string(body))
}Response: 200 (production · application/json)
{
"status": "ok",
"environment": "production"
}Updated about 4 hours ago