Docs
Discovery and health
The MCP endpoint, the discovery descriptor, llms.txt, the contract itself, and the probes.
Generated from openapi.yaml (Btcintel native API 0.1.0). Every hash is canonical display order; every answer carries the index and node heights; errors use one envelope — see Semantics.
- GET
/health— Liveness probe - GET
/ready— Readiness probe - GET
/metrics— Prometheus metrics - GET
/v1/export/stix— STIX 2.1 bundle of one subject, one entity, one source or one campaign - GET
/v1/export/tagpack— TagPack (GraphSense YAML) of one source or one campaign - GET
/v1/campaigns/{claim_id}— One campaign, with its members counted by role and state - GET
/v1/watch— The caller's watchlist - GET
/v1/watch/changes— The change feed of the watched subjects - PUT
/v1/watch/{kind}/{key}— Watch a subject - DELETE
/v1/watch/{kind}/{key}— Stop watching a subject - POST
/v1/auth/nip98— Sign in with a NIP-98 event and open a T3 session - GET
/v1/auth/nonce— A nonce that binds the next NIP-98 login to this browser - GET
/v1/auth/me— The signed-in key or account, its tier, quota and live sessions - POST
/v1/auth/logout— Revoke the current session and clear the cookie - DELETE
/v1/auth/sessions/{id}— Revoke one of the caller's sessions - GET
/v1/auth/backup— Download the NIP-49 backup - PUT
/v1/auth/backup— Store the NIP-49 backup of the signed-in key - DELETE
/v1/auth/backup— Delete the NIP-49 backup (NIP-98 only) - GET
/v1/auth/oauth/{provider}/start— Start a sign-in with a provider - GET
/v1/auth/oauth/{provider}/callback— The provider's return - POST
/v1/auth/oauth/{provider}/callback— The provider's return as a form POST (Apple) - POST
/v1/auth/oauth/confirm— Link a provider to the account with the same verified address - POST
/v1/auth/npub/custodied— Create a custodied signing key for the account - POST
/v1/auth/npub/challenge— A challenge to prove an own key before linking it - POST
/v1/auth/npub/link— Link the person's own key to the account - DELETE
/v1/auth/npub/{pubkey}— Unlink the account's key - POST
/v1/auth/npub/sign— Sign an event with the account's custodied key - POST
/v1/auth/npub/export— Export the custodied key as an nsec - POST
/v1/auth/npub/bunker— A bunker link to use the custodied key in another app - GET
/v1/auth/npub/clients— The apps connected to the custodied key - DELETE
/v1/auth/npub/clients/{client_pubkey}— Disconnect an app from the custodied key - POST
/mcp— MCP Streamable HTTP endpoint (JSON-RPC 2.0) - GET
/.well-known/btcintel.json— Discovery descriptor for agents - GET
/.well-known/mcp/server.json— The MCP Registry entry (server.json) - GET
/llms.txt— llms.txt (mirror of the web's)
GET /health
Liveness probe
Response 200
Process is up.
schema
string = "ok"
Example
curl -s https://api.btcintel.io/health | jqGET /ready
Readiness probe
200 only when the database and node answer and the index is within 2 blocks of the node.
Response 200
Serving honest data.
schema
readyboolean requiredindex_heightintegernode_heightintegerlag_blocksintegerreasonstring
Errors
503— Database or node unreachable, or index lagging;reasonexplains.
Example
curl -s https://api.btcintel.io/ready | jq '{ready, index_height, node_height}'GET /metrics
Prometheus metrics
Text format 0.0.4. Gauges btcintel_index_height, btcintel_node_height, btcintel_index_lag_blocks, btcintel_index_lag_alert, btcintel_mempool_txs, btcintel_disk_free_bytes, btcintel_disk_total_bytes; histograms btcintel_http_request_duration_seconds (by route) and btcintel_rpc_latency_seconds (by method); counter btcintel_http_requests_total (route, code).
Response 200
Metrics exposition.
Example
curl -s https://api.btcintel.io/metrics | jqGET /v1/export/stix
STIX 2.1 bundle of one subject, one entity, one source or one campaign
Spec S6.1 FR-1…FR-4 and S1.11 FR-4, FR-11 (ADR-0004 §Export; P5, P6, P20, P38, P73). Exactly one of subject, entity, source or campaign. A subject bundle carries its observable, every annotation on it (expired with valid_until, retracted with revoked), the events that name it and, with community=true (P29, never by default), the Community notes and labels as note, opinion and identity objects. An entity bundle carries the entity, its annotations (paged), its relations and its events. A source bundle pages the source's annotations by id (since bounds updated_at; cursor from the previous page) and carries its relations and events on the first page. A campaign bundle pages the campaign's members the same way, delisted ones with valid_until, and carries the campaign's report on the first page and a campaign object on every page: a member with an abuse word is an indicator that indicates the campaign, and a victim is a note on its observable and the campaign, never an indicator. Only current claims export: a provisional member is in no bundle, and include=provisional is refused. confidence_class and source_kind narrow the claims (events follow the source kind only). Objects derived from non-redistributable sources never enter a bundle; a note lists the omitted sources and x_btcintel_omitted_sources repeats them. Ids are deterministic (type--uuid5(NS, type + ":" + key); SCO ids per the STIX rule), so bundles merge.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
subject | query | string | <kind>:<key>, e.g. address:bc1q… |
entity | query | string | — |
source | query | string | — |
campaign | query | string | An event id, evt_<n>: the campaign whose members export (S1.11). |
confidence_class | query | string · max length 100 | Only claims of this confidence class (e.g. authority_data; the confidence: prefix is optional). Levels are never combined (P6). |
source_kind | query | string · max length 100 | Only claims from sources of this kind (sanctions, crime-intel, osint-report, …). |
since | query | string | A date (2006-01-02) or an RFC 3339 timestamp; source, entity and campaign exports. |
cursor | query | string | — |
limit | query | integer · ≥ 1 · ≤ 500 · default 200 | — |
community | query | boolean · default false | Subject exports only (P29). |
Response 200
The bundle; X-Next-Cursor and x_btcintel_next_cursor when a further page exists.
Headers: X-Next-Cursor
schema
typestring required · one of "bundle"idstring requiredobjectsarray of object requiredtypestring requiredidstring required- any other property allowed
x_btcintel_bundle_sha256string required — SHA-256 of the RFC 8785 serialisation ofobjects.x_btcintel_omitted_sourcesarray of stringx_btcintel_next_cursorstring
Errors
400— Malformed input (invalid_input).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.
Example
curl -s https://api.btcintel.io/v1/export/stix | jq '{type, id, x_btcintel_bundle_sha256}'GET /v1/export/tagpack
TagPack (GraphSense YAML) of one source or one campaign
Spec S6.1 FR-5 and S1.11 FR-4, FR-11 (P73). Exactly one of source or campaign. A source's file: its current address annotations as tags, confidence the class id (in the header, and on a tag when it differs), category and abuse the DW-VA short ids, context a JSON string with the claim id, its citation, the entity, and for a campaign member its role and campaign; 403 when the source is not redistributable (P38). A campaign's file: its current address members as tags, each with its own confidence and source; victims are never tags; members of non-redistributable sources are left out and named in a comment and in X-Omitted-Sources. Only current claims export: include=provisional is refused. confidence_class and source_kind narrow the tags.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
source | query | string | — |
campaign | query | string | An event id, evt_<n>: the campaign whose members are tags (S1.11). |
confidence_class | query | string · max length 100 | Only claims of this confidence class (e.g. authority_data; the confidence: prefix is optional). Levels are never combined (P6). |
source_kind | query | string · max length 100 | Only claims from sources of this kind (sanctions, crime-intel, osint-report, …). |
Response 200
The TagPack file (YAML).
Headers: X-Omitted-Sources
schema
titlestring requiredcreatorstring requireddescriptionstringlastmodstring required — YYYY-MM-DDcurrencystring required · one of "BTC"confidencestring — The source's default confidence class id.sourcestringtagsarray of object requiredaddressstring requiredlabelstring requiredcategorystring — The DW-VA entity short id.abusestring — The DW-VA abuse short id.confidencestring — Present when it differs from the header.lastmodstringsourcestring — The fetch URL.contextstring — JSON: claim_id, citation, entity_id, role.actorstring
Errors
400— Malformed input (invalid_input).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.
Example
curl -s https://api.btcintel.io/v1/export/tagpack | jq '{title, creator, description}'GET /v1/campaigns/{claim_id}
One campaign, with its members counted by role and state
Spec S1.11 FR-3 and FR-5 (P73). The event read of /v1/claims/evt_<n> under the campaign's name: the incident with its kind, dates, source and citation, members counted by role and state (never the members themselves: list_campaign_members pages them), and its first 100 subjects with subjects_total. Any other claim id is a 400.
- MCP tool
get_campaign— see For agents
Parameters
| Name | In | Type | Description |
|---|---|---|---|
claim_id required | path | string | An event id, evt_<n>. |
version | query | integer · ≥ 1 | — |
Response 200
The campaign, an event claim (CC-4) with citation and content hash; ETag = content hash.
Headers: ETag
schema
claim_idstringkindstring required · one of "event"versionintegercontent_sha256stringcitationstringevent_kindstringoccurred_atstring (date)occurred_precisionstring · one of "day", "month", "year"titlestringsummarystringsourceobjectfetchobjectcitesarray of objectrefsarray of objectsubjectsarray of object — The first 100 subjects the event links (the matcher's links), in kind and key order.subjects_totalinteger — How many subjects the event links in all.entitiesarray of objectmembersCampaignMemberCounts — The event's members (S1.11 FR-3):countis the current ones,by_rolehow many hold each role in each state, delisted and retracted members left out. Counts, never the list: the members page through /v1/claims/{claim_id}/members. An event with no members answers count 0 and an empty by_role.countinteger requiredby_roleobject required
retracted_atstring | null (date-time)created_atstring (date-time)
Errors
400— Malformed input (invalid_input).404— Unknown to the index and, for transactions, to the node mempool.
Example
curl -s https://api.btcintel.io/v1/campaigns/ann_33932 | jq '{claim_id, kind, version}'GET /v1/watch
The caller's watchlist
Spec S5.4 FR-2 (P33). The subjects a key (t2, t4) or a signed-in npub follows, with since_seq — the change-feed cursor at the time each was added — and last_seq, the feed's current head. Pull only: no webhooks (P31).
- MCP tool
list_watched— see For agents- Auth
- An operator credential as the bearer; every call is audited.
Response 200
The list.
schema
itemsarray of object requiredsubjectSubject requiredkindstring · one of "address", "script", "tx", "block", "outpoint", "pool", "descriptor", "pubkey"keystring — Canonical key (CC-3).
added_atstring (date-time) requiredsince_seqinteger required — The feed cursor when the subject was added.
countinteger requiredmaxinteger requiredlast_seqinteger required
Errors
401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s https://api.btcintel.io/v1/watch \
-H 'Authorization: Bearer $BTCI_KEY' | jq '{count, max, last_seq}'An operator credential: the ops bearer or a btci_ key with the ops:agent scope.
GET /v1/watch/changes
The change feed of the watched subjects
Spec S5.4 FR-3 (P33). The S1.6 change feed after since, restricted to the caller's watched subjects, plus community: per watched subject, how many comments and labels arrived after the cursor's time (counts only, the lens applied; every event when since is 0). Poll with next_cursor; honour Retry-After on 429.
- MCP tool
get_watch_changes— see For agents- Auth
- An operator credential as the bearer; every call is audited.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
since | query | integer · ≥ 0 | A cursor from a previous page; 0 or absent for the start. |
limit | query | integer · ≤ 1000 · default 100 | — |
Response 200
The page.
schema
itemsarray of Change requiredseqintegeratstring (date-time)claim_idstringkindstring · one of "annotation", "attribution", "relation", "event"actionstring · one of "created", "updated", "expired", "retracted"subjectSubjectkindstring · one of "address", "script", "tx", "block", "outpoint", "pool", "descriptor", "pubkey"keystring — Canonical key (CC-3).
communityarray of object required — Per watched subject with activity after the cursor's time: counts only, never the notes.subjectSubject requiredkindstring · one of "address", "script", "tx", "block", "outpoint", "pool", "descriptor", "pubkey"keystring — Canonical key (CC-3).
commentsinteger requiredlabelsinteger required
next_cursorinteger requiredwatchedinteger required — Subjects on the list._metaCommunityMeta requirednoticestring required — Always: Community content is indexed from Nostr and not endorsed by btcintel.endorsementboolean required — Always false.
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s https://api.btcintel.io/v1/watch/changes \
-H 'Authorization: Bearer $BTCI_KEY' | jq '{next_cursor, watched}'An operator credential: the ops bearer or a btci_ key with the ops:agent scope.
PUT /v1/watch/{kind}/{key}
Watch a subject
Spec S5.4 FR-1, FR-2. Adds the subject (canonicalised, CC-3) to the caller's list; idempotent (200 when it was there, 201 when added); 429 when the list is full (watchlist_max: t2 10, t3 25, t4 100). since_seq is the feed's head now: poll /v1/watch/changes?since= from it.
- MCP tool
watch_subject— see For agents- Auth
- An operator credential as the bearer; every call is audited.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
kind required | path | string · one of "address", "tx", "block" | — |
key required | path | string | The address, txid or block hash. |
Response 200
Already watched.
schema
subjectSubject requiredkindstring · one of "address", "script", "tx", "block", "outpoint", "pool", "descriptor", "pubkey"keystring — Canonical key (CC-3).
addedboolean requiredsince_seqinteger required — The change feed's head now.watchedinteger requiredmaxinteger required
Response 201
Added.
schema
subjectSubject requiredkindstring · one of "address", "script", "tx", "block", "outpoint", "pool", "descriptor", "pubkey"keystring — Canonical key (CC-3).
addedboolean requiredsince_seqinteger required — The change feed's head now.watchedinteger requiredmaxinteger required
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.429—quota_exceeded(spec S3.1 FR-4): the caller's tier ran out of requests for the minute or the day.Retry-Aftersays when to try again; every rate-limited answer carriesX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetandX-RateLimit-Tier. While the deployment runs in log-only mode the decision is logged instead andX-RateLimit-Policy: log-onlyis set.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s -X PUT https://api.btcintel.io/v1/watch/address/bc1qz654h5qcqzalv6ltpxsk4ksdcc3e20wkedqntgnarv3a0t2e2p7scr48pl \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
DELETE /v1/watch/{kind}/{key}
Stop watching a subject
Spec S5.4 FR-2. Removes the subject from the caller's list; 404 when it was not there.
- MCP tool
unwatch_subject— see For agents- Auth
- An operator credential as the bearer; every call is audited.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
kind required | path | string · one of "address", "tx", "block" | — |
key required | path | string | — |
Response 200
Removed.
schema
removedboolean requiredsubjectSubject requiredkindstring · one of "address", "script", "tx", "block", "outpoint", "pool", "descriptor", "pubkey"keystring — Canonical key (CC-3).
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s -X DELETE https://api.btcintel.io/v1/watch/address/bc1qz654h5qcqzalv6ltpxsk4ksdcc3e20wkedqntgnarv3a0t2e2p7scr48pl \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
POST /v1/auth/nip98
Sign in with a NIP-98 event and open a T3 session
Spec S5.1 FR-1 (P11, P16). The request carries Authorization: Nostr <base64(kind 27235 event)> per NIP-98: tag u = this exact URL, method = POST, created_at within ±60 s, a payload tag (SHA-256 of the body) whenever a body is sent; the signature verifies; the event id is single use. A nonce tag (spec S5.8) must equal the btci_login cookie that GET /v1/auth/nonce set in this browser and name an unspent nonce; the login spends it. While LOGIN_NONCE_REQUIRED is false a login without the tag is accepted and counted; with it true, refused (401). On success a session of 30 days is created and the cookie btci_session is set (HttpOnly; Secure; SameSite=Lax; Path=/; Domain=.btcintel.io). No account, password or email exists: the identity is the public key, and no private key ever reaches btcintel. At most 10 logins per key and 30 per address each hour (FR-4).
- Auth
- An operator credential as the bearer; every call is audited.
Response 201
The session is open; the cookie is on the response.
Headers: Set-Cookie
schema
pubkeystring required — 64 hex characters.npubstringtierstring required · one of "t3"expires_atstring (date-time) requiredsession_idstring required — The row id (SHA-256 of the cookie token) as/v1/auth/melists it.quotaQuotarequests_per_minintegerburst_per_minintegerrequests_per_dayintegerbulk_max_subjectsintegerpublish_per_dayintegerfeedback_per_dayinteger — Feedback entries per caller per UTC day (CC-8, spec S3.4).proposals_per_dayinteger — Curated proposals per caller per UTC day (CC-8, spec S5.1 FR-10).disputes_per_dayinteger — Disputes per caller per UTC day (CC-8, spec S5.3 FR-1).scopesarray of string
Errors
401— Missing or invalid bearer (unauthorized).422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.429—quota_exceeded(spec S3.1 FR-4): the caller's tier ran out of requests for the minute or the day.Retry-Aftersays when to try again; every rate-limited answer carriesX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetandX-RateLimit-Tier. While the deployment runs in log-only mode the decision is logged instead andX-RateLimit-Policy: log-onlyis set.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s -X POST https://api.btcintel.io/v1/auth/nip98 \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
GET /v1/auth/nonce
A nonce that binds the next NIP-98 login to this browser
Spec S5.8 FR-1 (CP3 finding 17). 32 random bytes as URL-safe base64, ten minutes, single use, kept in memory; the same value is set as the cookie btci_login (HttpOnly; Secure; SameSite=Lax; Path=/v1/auth/; Max-Age=600). The login event carries it in a nonce tag, and POST /v1/auth/nip98 accepts the login only when the tag and the cookie agree and the nonce is unspent: an event another page obtained opens nothing. Readable from the explorer's origins only (CORS). Sixty per client address per hour.
Response 200
The nonce; the cookie is on the response.
Headers: Set-Cookie
schema
noncestring requiredexpires_atstring (date-time) required
Errors
429—quota_exceeded(spec S3.1 FR-4): the caller's tier ran out of requests for the minute or the day.Retry-Aftersays when to try again; every rate-limited answer carriesX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetandX-RateLimit-Tier. While the deployment runs in log-only mode the decision is logged instead andX-RateLimit-Policy: log-onlyis set.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s https://api.btcintel.io/v1/auth/nonce | jq '{nonce, expires_at}'GET /v1/auth/me
The signed-in key or account, its tier, quota and live sessions
Spec S5.1 FR-2 and S5.5 FR-9. A session cookie (a NIP-98 login or an account sign-in) or a NIP-98 header on this request; auth says which. Sessions list the caller's own live sessions (current marks this one); user_agent_hash is the SHA-256 of the User-Agent seen at login, never the string.
- Auth
- An operator credential as the bearer; every call is audited.
Response 200
The caller.
schema
pubkeystring — The t3 public key, 64 hex characters; absent for an account without a linked key.npubstringtierstring required · one of "t3", "t4"authstring required — How this request was authenticated. · one of "session", "nip98"accountobject — The account of a t4 session (spec S5.5 FR-9); absent for a t3 key.idinteger (int64) requiredemailstring — Lower case; absent once the account is anonymised.display_namestringprovidersarray of string — The sign-in providers bound to the account (spec S5.5 FR-13); empty when none.created_atstring (date-time) required
aalinteger — The assurance level in force on this session (spec S5.5 FR-7): 2 after a passkey sign-in or within a step-up window. · one of 1, 2amrarray of string — The methods that produced this session: nip98, password, link, passkey, totp, recovery, google, github, apple, oidc.password_reset_onlyboolean — This session came from a reset link and may only set a new password (spec S5.5 FR-3).second_factorarray of string — The account's second factors (spec S5.5 FR-9); empty when none.recovery_codes_leftintegersigning_identitySigningIdentity — The account's live Nostr key (spec S5.6 FR-6, FR-7): custodied by btcintel's signer or connected by the person's own signer. Absent from/v1/auth/mewhen the account has none.pubkeystring required — 64 hex characters.npubstring requiredsourcestring required · one of "custodied", "nip07", "nip46", "nip55"linked_atstring (date-time) requiredexportableboolean required — True for a custodied key:POST /v1/auth/npub/exporthands it over.exported_atstring (date-time) — When a custodied key was last exported; absent until then, and while the signer does not answer.clientsarray of SignerClient — The NIP-46 apps connected to a custodied key (spec S5.6 FR-4); empty when none, absent while the signer does not answer.client_pubkeystring required — The app's pubkey, 64 hex characters.namestring required — What the account called the app when it made the link; may be empty.permsarray of string required — What the app may ask for:sign_event:<kind>,sign_event,nip44_encrypt,nip44_decrypt,nip04_encrypt,nip04_decrypt.connected_atstring (date-time) requiredlast_used_atstring (date-time) — Absent until the app's first request after connecting.
retired_keyobject — A custodied key the account's own key replaced, which the signer keeps and exports for 30 days (spec S5.9 FR-2, FR-4); absent when there is none.pubkeystring requirednpubstring requiredretired_atstring (date-time) requiredkept_untilstring (date-time) required
quotaQuotarequests_per_minintegerburst_per_minintegerrequests_per_dayintegerbulk_max_subjectsintegerpublish_per_dayintegerfeedback_per_dayinteger — Feedback entries per caller per UTC day (CC-8, spec S3.4).proposals_per_dayinteger — Curated proposals per caller per UTC day (CC-8, spec S5.1 FR-10).disputes_per_dayinteger — Disputes per caller per UTC day (CC-8, spec S5.3 FR-1).scopesarray of string
sessionsarray of object requiredidstringcreated_atstring (date-time)expires_atstring (date-time)amrarray of stringlast_seen_atstring | null (date-time)currentbooleanuser_agent_hashstring
Errors
401— Missing or invalid bearer (unauthorized).422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.
Example
curl -s https://api.btcintel.io/v1/auth/me \
-H 'Authorization: Bearer $BTCI_KEY' | jq '{pubkey, npub, tier}'An operator credential: the ops bearer or a btci_ key with the ops:agent scope.
POST /v1/auth/logout
Revoke the current session and clear the cookie
Spec S5.1 FR-2, FR-3. With a session cookie the request must also carry X-Requested-With: btcintel and an allowed Origin (CSRF), else 403.
- Auth
- An operator credential as the bearer; every call is audited.
Response 200
Logged out; the cookie is cleared.
schema
logged_outboolean
Errors
401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).
Example
curl -s -X POST https://api.btcintel.io/v1/auth/logout \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
DELETE /v1/auth/sessions/{id}
Revoke one of the caller's sessions
Spec S5.1 FR-2, FR-3. Any live session of the same key, the current one included; the CSRF proof applies to cookie callers.
- Auth
- An operator credential as the bearer; every call is audited.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | The session id as GET /v1/auth/me lists it (64 hex characters). |
Response 200
Revoked.
schema
revokedstring
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.
Example
curl -s -X DELETE https://api.btcintel.io/v1/auth/sessions/1 \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
GET /v1/auth/backup
Download the NIP-49 backup
Spec S5.1 FR-6 and P16. Two doors, no cookie: a NIP-98 signature by the same key, or ?pubkey= with the download key in X-Btcintel-Backup-Key (the second key derived from the password on the client). Downloads by key are counted before the key is compared: at most five attempts a day per npub. The blob is still encrypted; decryption happens in the browser with the password.
- Auth
- An operator credential as the bearer; every call is audited.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
pubkey | query | string | 64 hex characters or npub1…; with the header, when no NIP-98 signature is sent. |
X-Btcintel-Backup-Key | header | string | The download key as 64 hex characters; the header is allowed cross-origin for the explorer. |
Response 200
The backup, still encrypted.
schema
KeyBackup
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.429—quota_exceeded(spec S3.1 FR-4): the caller's tier ran out of requests for the minute or the day.Retry-Aftersays when to try again; every rate-limited answer carriesX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetandX-RateLimit-Tier. While the deployment runs in log-only mode the decision is logged instead andX-RateLimit-Policy: log-onlyis set.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s https://api.btcintel.io/v1/auth/backup \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
PUT /v1/auth/backup
Store the NIP-49 backup of the signed-in key
Spec S5.1 FR-5 (P16). The browser generated the key and encrypted it with the person's password (NIP-49, scrypt + XChaCha20-Poly1305); btcintel stores the ncryptsec1… verbatim after a shape check (bech32, 91-byte payload, version 2), never decrypts it, never logs it and cannot recover a forgotten password. auth_key is a second key derived from the password on the client (not the password): its SHA-256 lets the blob be downloaded later without a signer (P16). A first backup may be stored from a session; replacing one needs a NIP-98 signed request.
- Auth
- An operator credential as the bearer; every call is audited.
Request body
schema
ncryptsecstring required —ncryptsec1…(NIP-49).auth_keystring required — 32 bytes as 64 hex characters, derived from the password on the client; never the password.hintstring — A reminder the person wrote for themselves; never the password. · max length 80
Response 200
Replaced (NIP-98).
schema
pubkeystring requirednpubstringhintstring | nullcreated_atstring (date-time) requiredupdated_atstring (date-time) required
Response 201
Stored.
schema
pubkeystring requirednpubstringhintstring | nullcreated_atstring (date-time) requiredupdated_atstring (date-time) required
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s -X PUT https://api.btcintel.io/v1/auth/backup \
-H 'Authorization: Bearer $BTCI_KEY' \
-H 'Content-Type: application/json' \
-d '{"ncryptsec":"…","auth_key":"…"}' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
DELETE /v1/auth/backup
Delete the NIP-49 backup (NIP-98 only)
Spec S5.1 FR-5. A session cookie is refused (403); the request must be signed with NIP-98.
- Auth
- An operator credential as the bearer; every call is audited.
Response 200
Deleted.
schema
deletedbooleanpubkeystring
Errors
401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s -X DELETE https://api.btcintel.io/v1/auth/backup \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
GET /v1/auth/oauth/{provider}/start
Start a sign-in with a provider
Spec S5.5 FR-13 (phase D). Sends the browser to the provider's authorization endpoint: Authorization Code with PKCE (S256), a state and a nonce per flow, kept in memory for ten minutes; the state also travels in the btci_oauth cookie of the browser that started the flow. The provider returns to /v1/auth/oauth/{provider}/callback on the public API host and nowhere else. next is a path of the explorer to land on afterwards; anything that is not a path lands on /. With an account session on the request the flow links the provider to that account instead (the addresses must match). A provider not configured answers 404 and is absent from features.sign_in; thirty starts per client address per hour, shared with the other sign-ins.
- Auth
- An operator credential as the bearer; every call is audited.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
provider required | path | string · one of "google", "github", "apple", "oidc" | — |
next | query | string · max length 512 | Where the explorer lands afterwards: a path such as /watchlists. |
Errors
302— The browser goes to the provider.404— Unknown to the index and, for transactions, to the node mempool.429—quota_exceeded(spec S3.1 FR-4): the caller's tier ran out of requests for the minute or the day.Retry-Aftersays when to try again; every rate-limited answer carriesX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetandX-RateLimit-Tier. While the deployment runs in log-only mode the decision is logged instead andX-RateLimit-Policy: log-onlyis set.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—unavailable(spec S5.5 FR-13): the sign-in provider did not answer its discovery document; try again in a moment.
Example
curl -s https://api.btcintel.io/v1/auth/oauth/<provider>/start \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
GET /v1/auth/oauth/{provider}/callback
The provider's return
Spec S5.5 FR-13. Checks the state against the cookie (single use), exchanges the code with the PKCE verifier and verifies the ID token (issuer, audience, signature against the issuer's keys, nonce); GitHub, which issues none, is asked for the user and the verified addresses instead. Then: a provider identity bound to an account signs it in (a session at aal 1, amr [provider]; the cookie is set) and the browser lands on next; an unbound identity whose verified address has no account creates one and signs in; one whose verified address belongs to an account lands on /auth/provider?link=<token>&provider=<name> on the explorer, where the person confirms once (POST /v1/auth/oauth/confirm). Failures land on /auth/provider?error=<reason>&provider=<name>: state (no flow, expired, another browser), denied (the person stopped at the provider), provider (the exchange or the token failed), email_unverified (the provider does not vouch for an address), email_mismatch (a signed-in account asked for a link and the provider vouched for another address), bound (the identity is bound to another account). No token from the provider is stored.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
provider required | path | string · one of "google", "github", "apple", "oidc" | — |
code | query | string | — |
state | query | string | — |
error | query | string | The provider's reason when the person stopped there. |
Errors
302— The browser lands on the explorer.404— Unknown to the index and, for transactions, to the node mempool.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s https://api.btcintel.io/v1/auth/oauth/<provider>/callback | jqPOST /v1/auth/oauth/{provider}/callback
The provider's return as a form POST (Apple)
Spec S5.5 FR-13. Sign in with Apple returns the code with response_mode=form_post; the fields and the outcomes are those of the GET.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
provider required | path | string · one of "google", "github", "apple", "oidc" | — |
Errors
302— The browser lands on the explorer.404— Unknown to the index and, for transactions, to the node mempool.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s -X POST https://api.btcintel.io/v1/auth/oauth/<provider>/callback | jqPOST /v1/auth/oauth/confirm
Link a provider to the account with the same verified address
Spec S5.5 FR-13. token is the one the callback put in the explorer's URL, single use, ten minutes. Binds the provider identity to the account whose address the provider vouched for and opens a session (aal 1, amr [provider]); next in the answer is where the flow started. 404 when the token is unknown, used or expired; 409 when the account's address changed meanwhile or the identity is bound already.
Request body
schema
tokenstring required — The token from the e-mail link (URL-safe base64).
Response 201
The provider is bound and the session open; the cookie is set.
schema
accountobject requiredidinteger (int64) requiredemailstringdisplay_namestringcreated_atstring (date-time) required
tierstring required · one of "t4"expires_atstring (date-time) requiredsession_idstring required — The row id (SHA-256 of the cookie token) as/v1/auth/melists it.aalinteger required · one of 1, 2amrarray of string requiredpassword_reset_onlybooleannextstring — Where the explorer lands after a provider link (POST /v1/auth/oauth/confirm): the path the flow started with.signing_identitySigningIdentity — The account's Nostr key when it has one; while custodied signing is on, a sign-in gives an account without one its custodied key first (spec S5.9 FR-1).pubkeystring required — 64 hex characters.npubstring requiredsourcestring required · one of "custodied", "nip07", "nip46", "nip55"linked_atstring (date-time) requiredexportableboolean required — True for a custodied key:POST /v1/auth/npub/exporthands it over.exported_atstring (date-time) — When a custodied key was last exported; absent until then, and while the signer does not answer.clientsarray of SignerClient — The NIP-46 apps connected to a custodied key (spec S5.6 FR-4); empty when none, absent while the signer does not answer.client_pubkeystring required — The app's pubkey, 64 hex characters.namestring required — What the account called the app when it made the link; may be empty.permsarray of string required — What the app may ask for:sign_event:<kind>,sign_event,nip44_encrypt,nip44_decrypt,nip04_encrypt,nip04_decrypt.connected_atstring (date-time) requiredlast_used_atstring (date-time) — Absent until the app's first request after connecting.
quotaQuotarequests_per_minintegerburst_per_minintegerrequests_per_dayintegerbulk_max_subjectsintegerpublish_per_dayintegerfeedback_per_dayinteger — Feedback entries per caller per UTC day (CC-8, spec S3.4).proposals_per_dayinteger — Curated proposals per caller per UTC day (CC-8, spec S5.1 FR-10).disputes_per_dayinteger — Disputes per caller per UTC day (CC-8, spec S5.3 FR-1).scopesarray of string
Errors
400— Malformed input (invalid_input).404— Unknown to the index and, for transactions, to the node mempool.409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s -X POST https://api.btcintel.io/v1/auth/oauth/confirm \
-H 'Content-Type: application/json' \
-d '{"token":"…"}' | jqPOST /v1/auth/npub/custodied
Create a custodied signing key for the account
Spec S5.6 FR-6 (P56), aal 1. btcintel's signer service generates a Nostr key for an account that has none and keeps it encrypted; the API stores the public key alone (access.account_npubs, source custodied). One live key per account: 409 while one is linked, custodied or own. 501 on a deployment without the signer service (SIGNER_URL). The key signs and exports only while features.signing is true; it stays encrypted meanwhile.
- Auth
- An operator credential as the bearer; every call is audited.
Response 201
The account's new key.
schema
pubkeystring required — 64 hex characters.npubstring requiredsourcestring required · one of "custodied", "nip07", "nip46", "nip55"linked_atstring (date-time) requiredexportableboolean required — True for a custodied key:POST /v1/auth/npub/exporthands it over.exported_atstring (date-time) — When a custodied key was last exported; absent until then, and while the signer does not answer.clientsarray of SignerClient — The NIP-46 apps connected to a custodied key (spec S5.6 FR-4); empty when none, absent while the signer does not answer.client_pubkeystring required — The app's pubkey, 64 hex characters.namestring required — What the account called the app when it made the link; may be empty.permsarray of string required — What the app may ask for:sign_event:<kind>,sign_event,nip44_encrypt,nip44_decrypt,nip04_encrypt,nip04_decrypt.connected_atstring (date-time) requiredlast_used_atstring (date-time) — Absent until the app's first request after connecting.
Errors
401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—signing_off(spec S5.6 FR-5): the custodied signer is switched off (SIGNING_ENABLED), keys stay encrypted and exportable later; orunavailable: the signer service is not answering.
Example
curl -s -X POST https://api.btcintel.io/v1/auth/npub/custodied \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
POST /v1/auth/npub/challenge
A challenge to prove an own key before linking it
Spec S5.6 FR-7, aal 1. The challenge lives five minutes and is spent by one attempt of this account. The proof for POST /v1/auth/npub/link is a kind 27235 event (NIP-98) signed by the key to link, with the tags u = url, method = POST and challenge = challenge, created within a minute of sending; the person's extension (NIP-07), remote signer (NIP-46) or Android signer (NIP-55) signs it, so no private key ever reaches the API.
- Auth
- An operator credential as the bearer; every call is audited.
Response 201
The challenge.
schema
challengestring required — 64 hex characters.expires_atstring (date-time) requiredurlstring required — The value of the proof'sutag.methodstring required · one of "POST"
Errors
401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).
Example
curl -s -X POST https://api.btcintel.io/v1/auth/npub/challenge \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
POST /v1/auth/npub/link
Link the person's own key to the account
Spec S5.6 FR-7, aal 1. proof is the kind 27235 event described at POST /v1/auth/npub/challenge, signed by pubkey; source records how the person signs: nip07 (extension), nip46 (remote signer), nip55 (Android signer). One live key per account and one account per key (409). With replace: true (spec S5.9 FR-2) the key takes the place of the account's live key in one step: a custodied key is retired, kept in the signer and exportable for 30 days (retired_key on /v1/auth/me), and the apps connected to it lose their access; replacing a custodied key needs aal 1, or aal 2 when apps use it or the account has a second factor (CP4-4), and replacing an own key needs aal 2 (403 otherwise). Every replacement is told to the account by e-mail, naming the old and the new npub. The link is shown to the account (/v1/auth/me) and to human operators in the ops audit (P42); no public route or record carries it.
- Auth
- An operator credential as the bearer; every call is audited.
Request body
schema
pubkeystring required — 64 hex characters.sourcestring required · one of "nip07", "nip46", "nip55"proofNostrEvent required — A Nostr event as NIP-01 serialises it. Signed on the way out ofPOST /v1/auth/npub/sign; unsigned on the way in (idandsigabsent,pubkeyoptional).idstringpubkeystringcreated_atinteger required — Unix seconds.kindinteger requiredtagsarray of array of string requiredcontentstring requiredsigstring
replaceboolean — In place of the account's live key (spec S5.9 FR-2); without it, a live key answers 409.
Response 201
The linked key.
schema
pubkeystring required — 64 hex characters.npubstring requiredsourcestring required · one of "custodied", "nip07", "nip46", "nip55"linked_atstring (date-time) requiredexportableboolean required — True for a custodied key:POST /v1/auth/npub/exporthands it over.exported_atstring (date-time) — When a custodied key was last exported; absent until then, and while the signer does not answer.clientsarray of SignerClient — The NIP-46 apps connected to a custodied key (spec S5.6 FR-4); empty when none, absent while the signer does not answer.client_pubkeystring required — The app's pubkey, 64 hex characters.namestring required — What the account called the app when it made the link; may be empty.permsarray of string required — What the app may ask for:sign_event:<kind>,sign_event,nip44_encrypt,nip44_decrypt,nip04_encrypt,nip04_decrypt.connected_atstring (date-time) requiredlast_used_atstring (date-time) — Absent until the app's first request after connecting.
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.422— The NIP-98 event was refused (nip98_invalid): wrong URL or method, stale, bad signature, wrong payload hash, or used before; the message names the reason.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—signing_off(spec S5.6 FR-5): the custodied signer is switched off (SIGNING_ENABLED), keys stay encrypted and exportable later; orunavailable: the signer service is not answering.
Example
curl -s -X POST https://api.btcintel.io/v1/auth/npub/link \
-H 'Authorization: Bearer $BTCI_KEY' \
-H 'Content-Type: application/json' \
-d '{"pubkey":"…","source":"nip07","proof":{"created_at":10,"kind":10,"tags":[],"content":"…"}}' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
DELETE /v1/auth/npub/{pubkey}
Unlink the account's key
Spec S5.6 FR-7 and FR-8, aal 2. An own key is unlinked at once. A custodied key must have been exported first (409 until then): unlinking destroys btcintel's encrypted copy, and the public key stays as a tombstone in the signer's audit. The signer caps erasures at thirty an hour across all accounts (CP4 round 1), then 429 quota_exceeded with Retry-After.
- Auth
- An operator credential as the bearer; every call is audited.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
pubkey required | path | string | 64 hex characters. |
Response 200
The link ended.
schema
unlinkedboolean required · one of truepubkeystring requireddestroyedboolean required — True when the signer's copy of a custodied key was erased.
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.429— The signer's hourly cap of erasures (quota_exceeded, Retry-After).501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—signing_off(spec S5.6 FR-5): the custodied signer is switched off (SIGNING_ENABLED), keys stay encrypted and exportable later; orunavailable: the signer service is not answering.
Example
curl -s -X DELETE https://api.btcintel.io/v1/auth/npub/<pubkey> \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
POST /v1/auth/npub/sign
Sign an event with the account's custodied key
Spec S5.6 FR-8 and FR-9, aal 1. The event comes unsigned (pubkey may be left empty; id and sig are set on the way back). It passes the same door as POST /v1/community/publish first (kinds 1111, 1985 and 5; the window; the size; NIP-73 subjects; the proof-of-work floor, mined by the client against the key's pubkey; bans), then the signer signs it within its own limits (30 a minute and 300 an hour per key). The signed event goes back to the caller, who publishes it through POST /v1/community/publish. 404 for an account without a custodied key; 409 when its key is its own; 503 signing_off while features.signing is false.
- Auth
- An operator credential as the bearer; every call is audited.
Request body
schema
eventNostrEvent required — A Nostr event as NIP-01 serialises it. Signed on the way out ofPOST /v1/auth/npub/sign; unsigned on the way in (idandsigabsent,pubkeyoptional).idstringpubkeystringcreated_atinteger required — Unix seconds.kindinteger requiredtagsarray of array of string requiredcontentstring requiredsigstring
Response 200
The signed event.
schema
eventNostrEvent required — A Nostr event as NIP-01 serialises it. Signed on the way out ofPOST /v1/auth/npub/sign; unsigned on the way in (idandsigabsent,pubkeyoptional).idstringpubkeystringcreated_atinteger required — Unix seconds.kindinteger requiredtagsarray of array of string requiredcontentstring requiredsigstring
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.422— The event was refused at the door (event_rejected): the message carries the relay's NIP-01 prefix —unbound:(not this caller's key),invalid:(shape, subject, window, size, signature),pow:(below the floor),blocked:(kind, banned, read-only).429—quota_exceeded(spec S3.1 FR-4): the caller's tier ran out of requests for the minute or the day.Retry-Aftersays when to try again; every rate-limited answer carriesX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetandX-RateLimit-Tier. While the deployment runs in log-only mode the decision is logged instead andX-RateLimit-Policy: log-onlyis set.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—signing_off(spec S5.6 FR-5): the custodied signer is switched off (SIGNING_ENABLED), keys stay encrypted and exportable later; orunavailable: the signer service is not answering.
Example
curl -s -X POST https://api.btcintel.io/v1/auth/npub/sign \
-H 'Authorization: Bearer $BTCI_KEY' \
-H 'Content-Type: application/json' \
-d '{"event":{"created_at":10,"kind":10,"tags":[],"content":"…"}}' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
POST /v1/auth/npub/export
Export the custodied key as an nsec
Spec S5.6 FR-9 (P56), aal 2. The private key, bech32 nsec, shown once per request and never asked back; the signer records and alerts every export. Exports are capped by the signer itself (CP4-1): two a day for an account and six an hour on the server, then 429 quota_exceeded with Retry-After. The key stays custodied and keeps signing until it is unlinked. When the account's key is its own, the custodied key it replaced is exported instead while the signer keeps it (spec S5.9 FR-3, 30 days); 409 when there is none; 503 signing_off while features.signing is false.
- Auth
- An operator credential as the bearer; every call is audited.
Response 200
The key. Never cached, never logged; the account is told of the export by e-mail (CP4-4).
schema
nsecstring requirednpubstring requiredpubkeystring requiredexported_atstring (date-time) required
Errors
401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.429—quota_exceeded(spec S3.1 FR-4): the caller's tier ran out of requests for the minute or the day.Retry-Aftersays when to try again; every rate-limited answer carriesX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetandX-RateLimit-Tier. While the deployment runs in log-only mode the decision is logged instead andX-RateLimit-Policy: log-onlyis set.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—signing_off(spec S5.6 FR-5): the custodied signer is switched off (SIGNING_ENABLED), keys stay encrypted and exportable later; orunavailable: the signer service is not answering.
Example
curl -s -X POST https://api.btcintel.io/v1/auth/npub/export \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
POST /v1/auth/npub/bunker
A bunker link to use the custodied key in another app
Spec S5.6 FR-4 (NIP-46), aal 2. A one-time link, bunker://<pubkey>?relay=wss://relay.btcintel.io&secret=…, valid ten minutes, that Primal, Amethyst, Amber, nsec.app or any NIP-46 client connects with; the explorer shows it as a QR. name is what the account calls the app; perms are what the app may ask the signer for: sign_event:<kind> per kind, sign_event for every kind, nip44_encrypt, nip44_decrypt, nip04_encrypt, nip04_decrypt; the default is signing the Community kinds (0, 1, 1111, 1985, 5). One open link per key: a new one replaces the last. 409 when the account's key is its own; 501 when the signer runs without a relay; 503 signing_off while features.signing is false.
- Auth
- An operator credential as the bearer; every call is audited.
Request body (optional)
schema
namestring · max length 80permsarray of string
Response 201
The link; the QR payload is bunker_url.
schema
bunker_urlstring requiredpubkeystring requirednpubstring requiredrelaystring requiredpermsarray of string requiredexpires_atstring (date-time) required
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—signing_off(spec S5.6 FR-5): the custodied signer is switched off (SIGNING_ENABLED), keys stay encrypted and exportable later; orunavailable: the signer service is not answering.
Example
curl -s -X POST https://api.btcintel.io/v1/auth/npub/bunker \
-H 'Authorization: Bearer $BTCI_KEY' \
-H 'Content-Type: application/json' \
-d '{}' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
GET /v1/auth/npub/clients
The apps connected to the custodied key
Spec S5.6 FR-4. Every NIP-46 app connected through a bunker link, with its name, permissions and last use. 404 without a custodied key; 409 when the account's key is its own.
- Auth
- An operator credential as the bearer; every call is audited.
Response 200
The apps; empty when none.
schema
clientsarray of SignerClient requiredclient_pubkeystring required — The app's pubkey, 64 hex characters.namestring required — What the account called the app when it made the link; may be empty.permsarray of string required — What the app may ask for:sign_event:<kind>,sign_event,nip44_encrypt,nip44_decrypt,nip04_encrypt,nip04_decrypt.connected_atstring (date-time) requiredlast_used_atstring (date-time) — Absent until the app's first request after connecting.
Errors
401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—signing_off(spec S5.6 FR-5): the custodied signer is switched off (SIGNING_ENABLED), keys stay encrypted and exportable later; orunavailable: the signer service is not answering.
Example
curl -s https://api.btcintel.io/v1/auth/npub/clients \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
DELETE /v1/auth/npub/clients/{client_pubkey}
Disconnect an app from the custodied key
Spec S5.6 FR-4, aal 2. The app's next request is refused; it can only come back through a new bunker link.
- Auth
- An operator credential as the bearer; every call is audited.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
client_pubkey required | path | string | The app's pubkey, 64 hex characters. |
Response 200
Disconnected.
schema
revokedboolean required · one of trueclient_pubkeystring required
Errors
400— Malformed input (invalid_input).401— Missing or invalid bearer (unauthorized).403— The operator lacks the role or kind the action needs, or is switched off (forbidden).404— Unknown to the index and, for transactions, to the node mempool.409— The state refuses the change (conflict): an open dispute by this caller exists already, or the dispute or claim is not open any more.501— The mempool engine is not enabled on this deployment (BITCOIN_ZMQ_SEQUENCEunset).503—signing_off(spec S5.6 FR-5): the custodied signer is switched off (SIGNING_ENABLED), keys stay encrypted and exportable later; orunavailable: the signer service is not answering.
Example
curl -s -X DELETE https://api.btcintel.io/v1/auth/npub/clients/<client_pubkey> \
-H 'Authorization: Bearer $BTCI_KEY' | jqAn operator credential: the ops bearer or a btci_ key with the ops:agent scope.
POST /mcp
MCP Streamable HTTP endpoint (JSON-RPC 2.0)
The Model Context Protocol endpoint (spec S3.2): JSON-RPC 2.0 over Streamable HTTP as the MCP specification defines it (initialize, tools/list, tools/call, ping; GET /mcp opens the server-to-client SSE stream; DELETE /mcp ends a session). The tools are the operations of this file marked x-mcp-tool, generated by cmd/mcpgen into internal/mcp/tools.json; their inputs are the operation's parameters or body, their outputs the 200 schema rooted at type: object as MCP requires (a 200 schema that is not an object, list_sources' array, gives no output schema and a result that is the text alone), each sibling admitting the absent marker {"present": false}; the structured content is the answer when it is a JSON object, and every result carries the lag heights in _meta.btcintel. A bearer btci_ key is honoured; without one, initialize mints a t1 token and returns it in _meta.btcintel.token and in the instructions. Calls are counted in the mcp route class. Quota exhaustion is a JSON-RPC error with data {code: "quota_exceeded", retry_after}; every other API error is a tool result with isError whose text is the error envelope (with http_status), without structured content, since hosts check structured content against the tool's output schema.
Request body
schema
object — A JSON-RPC 2.0 request or notification.
Response 200
A JSON-RPC 2.0 response (application/json) or an event stream.
schema
object
Errors
429— Quota exceeded at the HTTP level (quota_exceeded, Retry-After).
Example
curl -s -X POST https://api.btcintel.io/mcp \
-H 'Content-Type: application/json' \
-d '{}' | jqGET /.well-known/btcintel.json
Discovery descriptor for agents
Where everything is: the MCP endpoint and transport, the OpenAPI contract, the docs, llms.txt and the public tier ladder (spec S3.2 FR-6).
Response 200
The descriptor; cacheable for an hour.
schema
namestring = "btcintel" requiredversionstringmcpobject requiredendpointstring (uri) requiredtransportstring = "streamable-http" requiredprotocol_versionsarray of stringtoolsarray of string — The enabled tool names.
openapistring (uri) requireddocsstring (uri) requiredagentsstring (uri) — The "For agents" page.llmsstring (uri) requiredtiersobjectenforcementstring · one of "enforce", "log_only"ladderarray of object
Errors
405— Method not allowed (GET and HEAD only).
Example
curl -s https://api.btcintel.io/.well-known/btcintel.json | jq '{name, version, openapi}'GET /.well-known/mcp/server.json
The MCP Registry entry (server.json)
This server in the MCP Registry's server.json format (schema 2025-12-11): the name io.btcintel/btcintel, the streamable-http remote, the docs page and the tools count (spec S9.4 FR-1). Served for hosts and directories that read it; the registry submission itself is the owner's step with the publisher CLI. Not an MCP tool.
Response 200
The registry entry; cacheable for an hour.
schema
$schemastring (uri) requirednamestring = "io.btcintel/btcintel" requiredtitlestringdescriptionstring required — Carries the enabled tools count. · max length 100versionstring requiredwebsiteUrlstring (uri) — The "For agents" page.remotesarray of object requiredtypestring = "streamable-http" requiredurlstring (uri) required
Errors
405— Method not allowed (GET and HEAD only).
Example
curl -s https://api.btcintel.io/.well-known/mcp/server.json | jq '{$schema, name, title}'GET /llms.txt
llms.txt (mirror of the web's)
The llms.txt convention: title, summary, links to the docs, the OpenAPI contract, the MCP endpoint, the citation guide and the tiers.
Response 200
Plain text; cacheable for an hour.
schema
string
Errors
405— Method not allowed (GET and HEAD only).
Example
curl -s https://api.btcintel.io/llms.txt | jq