Operator API

URnetwork API: Other

Version 2026.9.14 · 187 operations across 14 groups.Especificación OpenAPI ↗

URnetwork implementa por completo la especificación de operador del protocolo UR. Protocolo UR ↗

Other

62 operations
GET/privacy.txtPublicPrivacy Txt

Redirect (303) to the canonical privacy policy on bringyour.com. The api serves the redirect so a client that only knows the api host can still reach the policy.

1 status · show schema
303
No body
GET/terms.txtPublicTerms Txt

Redirect (303) to the canonical terms of service on bringyour.com.

1 status · show schema
303
No body
GET/vdp.txtPublicVdp Txt

Redirect (303) to the vulnerability disclosure policy on bringyour.com.

1 status · show schema
303
No body
GET/statusPublicWarp Status

The serving status of the api process that answered, used by the deploy poll and by fleet status sampling. status is a latched value rather than a per-request health check, so this route stays O(1) and never couples a deploy poll to database load: "ok" while the process serves, a string beginning error while it is not ready (which fails a deploy poll), and "draining" from SIGTERM onwards (deliberately not an error, so an operator-initiated drain is not counted as a service error). host, service and block name the process that answered, and client_address is the caller address it observed.

1 status · show schema
200
version
string

The process's build version; absent when it has none.

config_version
string

The deployed config version; absent when it has none.

status
string

The latched serving status: "ok", "draining" from SIGTERM onwards, or a string beginning "error" while the process is not ready (which is what fails a deploy poll).

client_address
string

The caller address this process observed.

host
string
service
string
block
string
GET/clockPublicClock

Get the cumulative destination-reported bytes for contracts finalized on or after the start of subnet block 9. The counter advances when contracts reach a final outcome and is intended for public clients polling every 1-5 seconds.

2 statuses · show schema
200
total_transfer_byte_count
string

Base-10 destination-reported bytes for contracts finalized since since_block. This is a string so clients do not lose integer precision as the lifetime total grows.

since_block
integer

First subnet block included in the counter.

since_time
string (date-time)

UTC start time of since_block.

503
No body
POST/log/{clientId}/uploadBearer authLog Upload

Upload a log file for the caller, associated with a feedback id. The request body is the raw log file content, streamed to storage. Note the {clientId} path segment is actually the feedback id the log is attached to.

clientId(path)
string

udid. The feedback id the log file is associated with.

required
show schema
string (binary)
1 status · show schema
200
error
object

Set, with a 200, when the upload was refused: over the per-network rate limit (one upload per network per period) or over the size limit.

message
string
GET/x402/skusPublicX402 Skus

The catalog an agent can buy from, so a skill can discover it without triggering a 402. Prices come from pro.yml, never from the x402 configuration, so an agent is quoted the same number a human pays by card; a sku with no price is not offered at all.

2 statuses · show schema
200
x402_version
integer
networks
array<string>
array
string
asset
string
skus
array<X402Sku>
array
sku_id
string
description
string
price_usd
number
pro
boolean

True for the Pro-month sku, which grants the Pro entitlement. The data skus never do.

byte_count
integer
404
No body
POST/x402/purchaseBearer authX402 Purchase

Buy a sku inline. Without an X-PAYMENT header the answer is 402 carrying the payment terms; the agent signs them and retries the SAME request with the signed payment in X-PAYMENT, which is settled and granted. The receipt is returned both as the body and in the X-PAYMENT-RESPONSE header. Requires a network session.

The same 402 round trip is also carried inline by POST /network/auth-client, where an agent meets the plan's concurrent-client limit.

X-PAYMENT(header)
string

The signed payment for terms previously quoted by a 402.

show schema
sku_id
string
network
string

The network the agent intends to pay on. Must be one that was quoted. Optional when only one is configured.

email
string

Where to send the receipt. Optional.

6 statuses · show schema
200
complete
boolean
sku_id
string
pro
boolean
byte_count
integer
transaction
string
network
string
400
No body
401
No body
402
x402Version
integer
error
string
accepts
array<X402Accept>
array
scheme
string
network
string
maxAmountRequired
string
resource
string
description
string
mimeType
string
payTo
string
asset
string
maxTimeoutSeconds
integer
404
No body
502
No body
POST/pay/stripePublicStripe Webhook

The Stripe webhook. Authenticated by the Stripe-Signature header over the raw body, verified before the event is parsed; there is no URnetwork JWT. The subscription is granted here, never by the client. Only the fields the server reads are listed; Stripe sends a much larger event.

show schema
id
string
type
string
data
object
object
object

The event's object, whose shape depends on type.

empty object
1 status · show schema
200
message
object

Set only when the event was not acted on.

message
string
POST/pay/coinbasePublicCoinbase Webhook

The Coinbase Commerce webhook, authenticated by the X-CC-Webhook-Signature HMAC over the raw body with the shared secret, checked before the event is parsed. Only the fields the server reads are listed.

show schema
event
object
id
string
type
string
data
object
id
string
name
string
description
string
payments
array<object>
array
empty object
checkout
object
empty object
metadata
object
empty object
1 status · show schema
200
empty object
POST/pay/playPublicPlay Webhook

The Play RTDN push, delivered through Google Pub/Sub and authenticated as a Pub/Sub push -- the Authorization bearer is verified as a Google-signed OIDC token before the body is parsed. The notification itself is base64 inside message.data; the server decodes it and credits the renewal through the same advisory-lock gate as /subscription/verify-play-purchase and the payment reconciler.

show schema
message
object
data
string

base64 of the Play developer notification JSON.

1 status · show schema
200
Message
objectnull

Present only when the notification was not acted on. The field is capitalized on the wire, because the Go field carries no json tag.

message
string
POST/pay/solanaPublicHelius Webhook

The Helius webhook for USDC transfers on Solana. Authenticated by the configured shared secret in the Authorization header, checked before the body is parsed. The server matches a transfer to a payment intent by the Solana Pay reference among the transaction's account keys, or by the memo text, and credits the plan or the data pack (see /solana/payment-intent and /pay/data/solana-intent).

The body is Helius's own enhanced-transaction ARRAY, not a wrapped object. One delivery carries a batch, and a customer's payment can sit behind any number of unrelated transfers, so a transaction that does not match is skipped rather than returned on. A per-transaction failure is reported only after the whole batch is examined, as a non-2xx, so Helius redelivers everything; the already-credited intents are no-ops on the retry.

show schema
array
empty object
1 status · show schema
200
message
string

Set only when the transfer was not credited.

POST/pay/circlePublicCircle Webhook

The Circle programmable-wallets notification, authenticated by the X-Circle-Signature ECDSA signature over the raw body under the key named by X-Circle-Key-Id. Deprecated; it is the same handler as POST /account/circle-wallet.

show schema
subscriptionId
string
notificationId
string
notificationType
string
notification
object
id
string
userId
string
status
string
correlationIds
array<string>

The first entry names the network the wallet belongs to.

array
string
errorCode
integer
type
string
timestamp
string (date-time)
version
integer
1 status · show schema
200
empty object
POST/apple/notificationPublicApple Notification

The App Store Server Notification V2 endpoint. The envelope carries one field, signedPayload, a compact JWS; unknown envelope fields are rejected and the body is capped at 1 MiB.

Every JWS -- the notification and the signed transaction and renewal info nested inside it -- is verified independently: ES256, a three-certificate x5c chain carrying Apple's leaf and intermediate policy extensions, terminating at a PINNED Apple root, with the chain judged at the payload's own signedDate. The outer notification must additionally be within 24 hours old and no more than 5 minutes in the future, and its bundle id, environment and app Apple id must match this app; the nested payloads must match the outer notification. A SUBSCRIBED, DID_RENEW or EXPIRED notification without signed transaction info is rejected.

Anything that fails is a 400 with no detail. A verified notification answers 200 with an empty object, whether or not it changed anything.

show schema
signedPayload
string

The compact JWS of the notification.

3 statuses · show schema
200
empty object
400
No body
413
No body
GET/key/{clientId}PublicGet Client Key

Fetch the published Ed25519 public key for a client id. Unauthenticated by design - the value is a public key meant to be fetchable by any peer. A client that has never published a key returns {"public_key": null} with HTTP 200.

clientId(path)
string

udid. The client id to look up.

required
1 status · show schema
200
public_key
stringnull

base64 encoded Ed25519 public key for the client id. null when the client has never published a key.

GET/key/{clientId}/historyPublicGet Client Key History

Fetch the operator-signed registration history of a client's public key: the ordered signed registrations, generation 1 first, each an opaque serialized registration exactly as the operator signed it. A peer verifies the chain itself -- every entry must be signed by a trusted signer under the deployment domain, generations must be contiguous and each entry must commit to the hash of the one before -- and compares the key it ends on with the one the platform attached to a contract, which is what turns the platform's key distribution from something a peer must trust into something it can check (connect/DESIGNNOTES3.md).

Unauthenticated by design, matching GET /key/{clientId}: every byte returned is already-published signed evidence. A client with no signed history -- a legacy client, or an operator that does not run the signed path -- answers {"history": []} with HTTP 200, never an error: an error is an availability signal, and a consumer must not confuse the two. At most 64 registrations, and at most 1 MiB, are returned.

clientId(path)
string

udid. The client id to look up.

required
1 status · show schema
200
history
array<string>

The ordered signed registrations, generation 1 first, each the base64 of one opaque serialized registration. Empty when the client has no signed history.

array
string
GET/helloPublicHello

Simple hello to the network that returns some useful information. This can be used for discovery.

It is also the extender network's trust root: extender_root_public_keys are the keys whose record signatures this network space accepts, and gossip_peer_id names the operator's gossip node. Both arrive over the platform's pinned tls, so a client can refresh them even when the request itself travelled through an untrusted extender -- and the extender activation probe forwards a real GET /hello through the candidate's tcp carrier to prove the forward works.

1 status · show schema
200
client_address
string
extender_root_public_keys
array<string>

The extender root keys whose record signatures this network space accepts, hex encoded. It arrives over the platform's pinned tls, so it is trustworthy even when the request itself travelled through an untrusted extender, and it REPLACES whatever list the client had stored. Absent when the operator has configured no extender network.

array
string
gossip_peer_id
string

The libp2p peer id of the operator's gossip node, derived from its gossip identity key. A member dials the operator only when it knows this id, since without it there is nothing to authenticate the far end of the dial against -- so an operator running no gossip service serves none rather than an address a member could not verify.

GET/my-ip-infoPublicMy IP Info

Information about the caller's IP as seen by the platform: its geolocation, and whether the IP is currently connected to the network.

The location comes from MaxMind GeoLite2 City. This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com. The database has no privacy/VPN classification, so none is returned.

1 status · show schema
200
info
object
ip
string
location
object

city and region are left out when the IP is not located that precisely, e.g. only to its country.

coordinates
object
lat
number
lon
number
city
string
region
string

The largest subdivision, e.g. a state.

country
object
code
string

ISO 3166-1 alpha-2, lowercase.

name
string
continent
object
code
string

Two-letter continent code, lowercase.

name
string
timezone
string

IANA time zone, e.g. Europe/London.

connected_to_network
boolean
POST/stripe/payment-intentBearer authStripe Payment Intent

When paying for a subscription with Stripe, create the payment intents, ephemeral key, and customer id needed to complete the purchase.

show schema
empty object
1 status · show schema
200
payment_intents
array<StripePaymentIntent>
array
subscription_type
string
client_secret
string
ephemeral_key
string
customer_id
string
publishable_key
string
error
object
message
string
POST/stripe/customer-portalBearer authStripe Customer Portal

Create a Stripe customer portal URL for the caller to manage their subscription.

show schema
empty object
1 status · show schema
200
url
string
error
object
message
string
POST/pay/data/checkoutPublicPay Data Checkout

Buy a data pack with a card without signing in (the ur.io buy-data page). Starts a hosted Stripe checkout and returns the url to send the customer to. To pay with USDC on Solana use /pay/data/solana-intent.

With network_name, the data is applied to that network by the payment webhook as soon as the payment confirms, and the customer is emailed a note that the data is on the network (the code is included, already applied). The name is matched case-insensitively and exactly; an unknown name returns error.message "No network named ".

Without network_name, the customer receives a data code by email, as before. Stripe collects the email on its checkout page when email is not given.

The success url is the configured checkout success url plus item= and, when applying to a network, network=. Unauthenticated, per-ip rate limited. Errors are returned as error.message with a 200.

show schema
item_id
string
enum: data_1tib, data_10tib
provider
string

the hosted checkout; omitted or empty means stripe

enum: , stripe
network_name
string

the network that receives the data; omit to receive a code by email

email
string

where the code (or the applied note) is sent; Stripe collects it at checkout when omitted

1 status · show schema
200
url
string

the hosted checkout url to send the customer to

provider
string
enum: stripe
network_id
string (uuid)

the resolved network when network_name was given

error
object
message
string
POST/pay/data/network-lookupPublicPay Data Network Lookup

Whether a network with exactly this name exists, for the buy-data page. Matched the same way /pay/data/checkout resolves network_name (case-insensitive, exact). Unlike /auth/network-check, which is a sign-up similarity check, a name within a few characters of an existing one is NOT reported as existing. Returns only the canonical name, no id. Unauthenticated, per-ip rate limited.

show schema
network_name
string
1 status · show schema
200
exists
boolean
network_name
string

the name as stored, when it exists

POST/pay/data/solana-intentPublicPay Data Solana Intent

Buy a data pack with USDC on Solana, without signing in or giving an email: the data goes straight to the network named by network_name (matched like /pay/data/network-lookup). Records the payment intent and returns the exact amount_usd to send in USDC. The client generates reference (a Solana Pay reference: a fresh base58 public key) and either opens a Solana Pay url carrying it, or shows the buyer the receiving address, the amount and memo (which is the reference) to send by hand from any wallet or exchange. The Helius webhook (/pay/solana) matches the transfer by the reference among the transaction's account keys OR by the memo text, then lands the data on the network (data only, valid for one year, no Pro) and emails the network's admin when their login is an email.

The intent expires after 24 hours (expires_at); a payment that arrives late still credits as long as the intent has not been swept. A reused reference, an unknown item or an unknown network return error.message with a 200. Unauthenticated, per-ip rate limited.

show schema
item_id
string
enum: data_1tib, data_10tib
network_name
string

the network that receives the data

reference
string

the Solana Pay reference the client generated (a fresh base58 public key); it doubles as the transfer memo for a payment sent by hand

1 status · show schema
200
amount_usd
number

the exact amount to send, in USDC

reference
string
memo
string

the transfer memo for a payment sent by hand (the reference)

expires_at
string (date-time)
network_name
string

the resolved network name, as stored

network_id
string (uuid)
error
object
message
string
POST/pay/data/solana-statusPublicPay Data Solana Status

The state of a data pack payment started with /pay/data/solana-intent, for the buy-data page to poll while it waits: pending until the USDC transfer is credited, paid once the data is on the network, expired when the intent passed expires_at unpaid, unknown for a reference that is not a data pack intent (plan intents made by a signed-in wallet are not reported here). Unauthenticated, per-ip rate limited.

show schema
reference
string
1 status · show schema
200
status
string
enum: pending, paid, expired, unknown
item_id
string
enum: data_1tib, data_10tib
network_name
string
amount_usd
number
expires_at
string (date-time)
POST/stripe/create-checkout-sessionBearer authStripe Create Checkout Session

Create a Stripe Checkout Session for a subscription or data pack.

ui_mode selects which shape the result carries -- a single Stripe session cannot produce both:

  • "hosted" (the default, so existing callers are unchanged): returns checkout_url, which the client opens in a browser. - "embedded": returns client_secret and publishable_key, which the client uses to mount Stripe Embedded Checkout inline (the desktop apps load ur.io/checkout in a webview with these).

The subscription is granted by the invoice.paid webhook, never by the client; the client only polls /subscription/balance afterwards.

show schema
item_id
string
storefront_country
string

ISO 3166-1 storefront country, when the caller knows it. The Pro items are priced at the caller's regional tier and, when the welcome offer is redeemable (yearly only), the 25% coupon is applied server-side; the charged tier is finalized from the card's billing country once attached.

ui_mode
string

"hosted" (default) or "embedded".

enum: hosted, embedded
redirect_on_completion
string

"never" keeps an EMBEDDED checkout fully inline: Stripe fires the client's onComplete callback instead of redirecting, so the page the customer is on never navigates. Only valid with ui_mode "embedded". Empty means the embedded flow redirects to the configured return url, and hosted behaves as always.

1 status · show schema
200
ui_mode
string

which of the two shapes below is populated

checkout_url
string

hosted mode only

client_secret
string

embedded mode only

publishable_key
string

embedded mode only

session_id
string

both modes; the caller can reconcile the purchase with this

error
object
message
string
POST/verifyPublicVerify

One step of the validator's routing-verification protocol. The route accepts two body shapes:

  • SEED starts a new trail through the validator-chosen entry provider.
  • EXTEND claims the pending assigned hop of an active trail.

The request must egress from the provider being claimed - the server resolves the hop from the request source IP, never from the body. Authentication is the protocol's own Ed25519 signatures (over the canonical binary messages, not the JSON), not a JWT. A non-final step returns the assign result (the server's signed commitment to the next hop); the final step returns status "complete" and the published proof.

show schema
one of
client_id
string

uuid. The validator's own client_id; the server checks vpk equals this client's registered Ed25519 key.

vpk
string

base64 encoded 32-byte Ed25519 validator path key.

client_nonce
string

base64 encoded 32-byte nonce.

seed_sig
string

base64 encoded 64-byte Ed25519 SEED signature.

M
integer

Requested trail depth. The server clamps to its allowed range.

client_id
string

uuid. The validator's own client_id.

trail_id
string

uuid

trail
array<string>

The ordered confirmed hop client_ids plus the single pending hop being claimed.

array
string

uuid

extend_sig
string

base64 encoded 64-byte Ed25519 EXTEND signature.

2 statuses · show schema
200
one of
trail_id
string

uuid

server_nonce
string

base64 encoded 32-byte per-trail server nonce.

trail
array<string>

The ordered confirmed hop client_ids (ids only; times are published only in the final proof).

array
string

uuid

next_hop
string

uuid. The newly assigned pending hop.

M
integer

The server-clamped effective trail depth.

server_key_id
integer

1-byte id of the server key that signed assign_sig. See /verify/keys.

assign_sig
string

base64 encoded 64-byte Ed25519 ASSIGN signature by the server (msg_type 0x03).

status
string

"complete"

proof
VerifyProof
header
VerifyProofHeader
trail_id
string

uuid

server_nonce
string

base64 encoded 32-byte per-trail server nonce.

vpk
string

base64 encoded 32-byte Ed25519 validator path key.

M
integer

Trail depth.

hops
array<VerifyProofHop>
array
client_id
string

uuid. The canonical provider at this hop.

time_ms
integer

Server-stamped confirmation time, unix milliseconds UTC.

egress_ip_hash
array<integer>

The 32-byte hash of the provider's egress IP at the subnet's configured prefix granularity. Marshals on the wire as a JSON array of 32 integers (a Go [32]byte), not a base64 string.

array
integer
server_key_id
integer

1-byte id of the server key that signed final_sig.

coverage
integer

The server-attested coverage of the trail (v1 = M-1, the server-assigned confirmed hops with the seed excluded).

final_sig
string

base64 encoded 64-byte Ed25519 FINAL signature by the server.

verifier_sig
string

base64 encoded 64-byte Ed25519 depth-M EXTEND signature by the validator.

503
No body
GET/verify/keysPublicVerify Keys

The published server Ed25519 verify keys, by serverkeyid. All historical keys are listed so old proofs remain verifiable across key rotations.

2 statuses · show schema
200
keys
array<VerifyServerKey>
array
server_key_id
integer

1-byte key rotation id.

public_key
string

base64 encoded 32-byte Ed25519 public key.

503
No body
GET/verify/statsPublicVerify Stats

The public reproducibility index of the routing-verification protocol: per provider and period, how many hops were assigned and how many were confirmed, the resulting reliability in parts per million, and the latency percentiles when they were measured. The answer names the profile and policy hash the rows were produced under, plus the egress hash key id (the id only -- the key itself never leaves the server), so a third party can check that two indexes are comparable.

Unauthenticated, and it does not participate in validator scoring. 503 while the subnet subsystem is disabled.

from(query)
string (date-time)

RFC 3339 start of the window.

to(query)
string (date-time)

RFC 3339 end of the window.

limit(query)
integer
3 statuses · show schema
200
schema
string
profile
string
policy_hash
string
egress_hash_key_id
string

The id only; the key itself never leaves the server.

rows
arraynull
400
No body
503
No body
GET/verify/proofsPublicVerify Proofs

The published completed trails, so anyone can re-verify them against the keys from /verify/keys. Same window parameters, profile and policy hash as /verify/stats. Unauthenticated; 503 while the subnet subsystem is disabled.

from(query)
string (date-time)

RFC 3339 start of the window.

to(query)
string (date-time)

RFC 3339 end of the window.

limit(query)
integer
3 statuses · show schema
200
schema
string
profile
string
policy_hash
string
rows
arraynull
400
No body
503
No body
GET/sn/walletBearer authSn Get Wallet

The coldkeys attached inside the caller's network: the network-level wallet (no client_id) and every provider client's wallet. wallet is the session's effective wallet (the client-scoped wallet for a client session when set, else the network wallet); absent when none is attached. An empty wallets list is not an error.

1 status · show schema
200
wallet
SnWallet
coldkey_ss58
string
client_id
string

uuid of the provider client; absent for the network-level wallet.

set_at_millis
integer

Unix time in milliseconds the wallet was set.

wallets
array<SnWallet>
array
coldkey_ss58
string
client_id
string

uuid of the provider client; absent for the network-level wallet.

set_at_millis
integer

Unix time in milliseconds the wallet was set.

error
object
message
string
POST/sn/walletBearer authSn Set Wallet

Attach the caller's subnet coldkey - the ss58 address that pool payouts are claimable by. Proof of key possession is required: message is the single-use challenge issued by POST /auth/wallet-challenge for blockchain TAO and signature is the coldkey's sr25519 signature over it. client_id scopes the wallet to one provider client (a client-scoped JWT selects its own client). Banned addresses are rejected. Not retroactive: the wallet applies from the next committed epoch's leaf set. This wallet is deliberately separate from the account payout wallet.

show schema
coldkey_ss58
string

The subnet coldkey as an ss58 address (Bittensor prefix 42).

client_id
string

uuid of the provider client the wallet is attached to. Defaults to the session client for a client-scoped JWT; must belong to the caller's network.

signature
string

The coldkey's sr25519 signature (hex) over message. Required for app callers.

message
string

The exact single-use challenge text issued by POST /auth/wallet-challenge for blockchain TAO and this address.

1 status · show schema
200
error
object
message
string
POST/sn/wallet/validatePublicSn Validate Wallet

Checks an address before it is attached: ss58 syntax with the Bittensor prefix, the operator ban list, and whether the account exists on the subtensor chain. When the chain cannot be reached the result reports exists_on_chain: true with a message, so an outage never blocks a user. No authentication; chain lookups are rate limited per source address.

show schema
address
string

The ss58 address to check.

1 status · show schema
200
valid_syntax
boolean

The address decodes as ss58 with the Bittensor prefix.

exists_on_chain
boolean

The account exists on the subtensor chain (true when the chain could not be reached; see message).

banned
boolean

The address is on the operator ban list and must not be attached.

message
string
GET/sn/headBearer authSn Head

The caller network's standing for a head mining spot (Top 200, WHITEPAPER 8.4). score is the server's estimate of split-adjusted distinct routable egress-IP breadth from the live trail egress index, floor the score of the last network inside cutoff (0 while fewer networks score), rank_estimate 1-based (0 without a score). bound/hotkey/uid reflect an active head binding. source is "server" until validators publish consensus scores on chain; a chain outage degrades to the estimate, never an error.

1 status · show schema
200
eligible
boolean

The estimate places the network inside the cutoff.

score
number (double)

Split-adjusted distinct routable egress-IP breadth (server estimate).

floor
number (double)

Score of the last network inside the cutoff; 0 while fewer networks score.

rank_estimate
integer

1-based estimated rank; 0 without a score.

cutoff
integer

Head-tier size (200).

bound
boolean

An active head binding exists for one of the network's clients.

hotkey
string

The bound head hotkey as ss58; absent when not bound.

uid
integer

The bound uid; 0 when not bound.

rank
integer

The bound network's estimated rank; 0 when not bound.

epoch
integer

The current contract epoch.

netuid
integer
source
string

"server" (estimate) or "chain" (validator consensus).

error
object
message
string
POST/sn/head/bindingBearer authSn Head Binding

Store a device's consent for a fleet binding (WHITEPAPER 11.4): the canonical binding payload and the client's Ed25519 signature over its keccak256 digest, optionally with the head hotkey's sr25519 signature. When both signatures verify the response carries the bindFleetMember calldata for the operator to submit from their own key. The server never submits on chain.

show schema
binding
SnFleetBinding
chain_id
integer
netuid
integer
coordinator
string

0x-hex coordinator contract address.

fleet_id
string
hotkey
string
client_id
string

uuid

client_key
string

0x-hex 32-byte client Ed25519 public key.

generation
integer
valid_from_epoch
integer
valid_to_epoch
integer
commitment_hash
string
client_signature
string

The device's Ed25519 signature (hex) over the binding digest; signature is accepted as an alias.

signature
string
hotkey_signature
string

The head hotkey's sr25519 signature (hex) over the same digest; optional, required for calldata.

hotkey
string

Optional override of the binding's hotkey (ss58 or 0x-hex).

1 status · show schema
200
digest
string

0x-hex keccak256 binding digest.

client_signature_valid
boolean
hotkey_signature_valid
boolean
ready
boolean

Both signatures verified and calldata is present.

calldata
string

0x-hex bindFleetMember calldata for the operator to submit.

to
string

The coordinator contract address to send the calldata to.

data
string

Same as calldata.

contract_address
string
chain_id
integer
error
object
message
string
GET/sn/pool/claimBearer authSn Pool Claim

The caller network's merkle pool-payout claim for an epoch: everything needed to call claim on the subnet contract. Claims are served once the epoch is finalized on chain.

epoch(query)
integer

Epoch index. Absent means the latest finalized epoch -- and only absence defaults, since epoch 0 is a real epoch.

1 status · show schema
200
epoch
integer

Epoch index.

no_id
stringnull

base64 encoded 32-byte network operator id, as committed on chain.

coldkey
stringnull

base64 encoded 32-byte coldkey public key (the ss58-decoded key set via /sn/wallet).

share_bps
integer

The caller's payout share in basis points.

proof
arraynull

Merkle proof from the (noid, coldkey, sharebps) leaf to payout_root.

payout_root
stringnull

base64 encoded 32-byte committed payout merkle root.

contract_address
string

0x-hex EVM address of the subnet contract.

chain_id
integer

EVM chain id of the subnet contract chain.

claim_open_block
integer

Block number claims open at.

artifact_hash
string

The content hash of the payout artifact this claim was computed from, when one is recorded. Fetch it from /sn/artifact.

artifact_uri
string

Where that artifact is published, when recorded.

settlement_vault_address
string

0x-hex EVM address of the settlement vault the claim is sent to; absent when the operator has not configured one.

error
object
message
string
GET/sn/epochPublicSn Epoch

The current subnet epoch index, boundaries, and deadlines mirrored from chain, so clients do not need their own RPC.

1 status · show schema
200
epoch
integer

Epoch index.

start_block
integer
commit_deadline_block
integer
trails_deadline_block
integer
finalize_block
integer
t_epoch_blocks
integer

Epoch length in blocks.

chain_id
integer

EVM chain id of the subnet contract chain.

contract_address
string

0x-hex EVM address of the subnet contract.

settlement_vault_address
string

0x-hex EVM address of the settlement vault claims are sent to; absent when unset.

no_id
string

The operator id (decimal string); absent when unset.

netuid
integer
rpc_url
string

Public subtensor EVM JSON-RPC url for clients; absent when unset.

GET/sn/artifactPublicSn Artifact

Serve a canonical payout artifact addressed by its sha256 content hash. The hash is rechecked against the bytes before anything is returned, so a corrupt object store cannot silently substitute a different payout tree. Public and immutable: the answer carries the content hash as its ETag and a one-year immutable cache directive.

hash(query)
string

The artifact's sha256 content hash.

required
4 statuses · show schema
200
schema
string

Always urnetwork-payout-artifact-v1.

deployment_id
string
chain_id
integer
genesis_hash
string

0x-prefixed hex digest.

netuid
integer
coordinator
string

0x-prefixed 20-byte EVM address.

settlement_vault
string

0x-prefixed 20-byte EVM address.

epoch
integer
no_id
integer
policy_hash
string

0x-prefixed hex digest.

start
SnPayoutArtifactBoundary
number
integer
hash
string

0x-prefixed block hash.

end
SnPayoutArtifactBoundary
number
integer
hash
string

0x-prefixed block hash.

operator_snapshot_hash
string

sha256:-prefixed hex digest.

fleet_snapshot_hash
string

sha256:-prefixed hex digest.

provider_snapshot_hash
string

sha256:-prefixed hex digest.

reliability_a_min
integer
providers
arraynull

The provider inputs the tree was built from; null when the epoch had no providers (a nil slice in the builder).

leaves
array<SnPayoutArtifactLeaf>
array
index
integer
allocation_client_id
array<integer>

The 16-byte client id, as a JSON array of byte values.

array
integer
coldkey
array<integer>

The 32-byte coldkey public key, as a JSON array of byte values.

array
integer
share_bps
integer
proof
arraynull

Sibling hashes from the leaf to the root, each a 32-byte value as a JSON array of byte values; null for a tree that needs none.

payout_root
array<integer>

The 32-byte merkle root, as a JSON array of byte values.

array
integer
total_usage_bytes
integer
eligible_usage_bytes
integer
excluded_usage_bytes
integer
shares_total_bps
integer
created_at
string

RFC 3339 UTC.

signer
string

0x-prefixed 20-byte EVM address of the signing key.

content_hash
string
signature
string

0x-prefixed hex of the 65-byte secp256k1 signature over content_hash.

400
No body
502
No body
503
No body
GET/sn/artifactsPublicSn Artifact History

List immutable payout artifact keys under a deployment and netuid prefix, optionally narrowed to one epoch and one operator id. Only public object identifiers are returned -- never signer or vault material. The page is validated as an integrity boundary before it is served: keys must be strictly increasing, inside the requested prefix, and named by a canonical lowercase 32-byte hex hash with a .json suffix.

deployment_id(query)
string

One safe path segment (no /, \ or .).

required
netuid(query)
integer

A nonzero uint16.

required
epoch(query)
integer

A uint64. Required if no_id is given.

no_id(query)
integer

A nonzero uint64 operator id; only valid with epoch.

limit(query)
integer

Objects per page, default 256.

after(query)
string

The previous page's next_after; must be inside the same prefix.

4 statuses · show schema
200
schema
string

Always urnetwork-payout-artifact-history-v1.

objects
array<SnArtifactHistoryObject>
array
key
string
size
integer
content_hash
string

sha256: followed by the lowercase 64-character hex hash.

more
boolean
next_after
string

The cursor for the next page; empty on the last page.

400
No body
502
No body
503
No body
GET/sn/attempt-artifactPublicSn Attempt Artifact

Stream the exact immutable bytes of one attempt object. kind selects the shape: metadata is a JSON document, records and proofs are newline-delimited JSON. hash is the canonical lowercase, nonzero, 0x-prefixed sha256 of those bytes and is what the answer's ETag carries. Exactly these two query parameters are accepted, and a Range header is refused, so a partial read can never be mistaken for the whole object.

Concurrency is bounded (16 readers, each with one fixed 32 KiB copy buffer) and a request that finds no slot is answered 429 with Retry-After. A stream that fails after any bytes have been written is aborted without a clean end-of-body rather than ending in a truncated object that looks complete.

kind(query)
string
required
hash(query)
string

Canonical 0x-prefixed lowercase sha256 of the object.

required
6 statuses · show schema
200
one of
schema
string
kind
string
enum: records, proofs
item_count
integer
data_bytes
integer
chunk_count
integer
page_count
integer
first_page_hash
string

0x-prefixed sha256 of the first descriptor page; zero for an empty stream.

first_page_bytes
integer
schema
string
kind
string
enum: records, proofs
index
integer
chunks
array<SnAttemptStreamChunk>
array
index
integer
first_sequence
integer
last_sequence
integer
item_count
integer
data_bytes
integer
content_hash
string

0x-prefixed sha256 of the chunk bytes.

next_page_hash
string

0x-prefixed sha256 of the next page; zero on the last page.

next_page_bytes
integer
400
No body
408
No body
429
No body
502
No body
503
No body
POST/sn/attempt-artifactBearer authSn Upload Attempt Artifact

Upload the exact bytes of one attempt object. Requires a CLIENT jwt. The identity is the same kind and hash pair the reader uses, and the server recomputes the sha256 of the body and refuses anything that differs, so an upload can only ever publish the object it names.

The framing is strict: exactly one Content-Type matching the kind (application/json for metadata, application/x-ndjson for records and proofs), a positive Content-Length within the object bound (32 MiB for records and proofs, 2 MiB for metadata), and no Content-Encoding, Content-Range, Range, transfer encoding or trailers -- a transformed or partial upload is forbidden. Size is charged against the account's byte budget BEFORE any storage work, and the active upload slots are bounded.

A validator may instead present a signed reserved-upload intent in X-Urnetwork-Validator-Upload, which admits the upload against the api's own reserved lifecycle. The reserved header never bypasses client authentication.

The answer is 204 with the content hash in ETag.

kind(query)
string
required
hash(query)
string

Canonical 0x-prefixed lowercase sha256 of the body.

required
X-Urnetwork-Validator-Upload(header)
string

A signed validator reserved-upload intent.

show schema
empty object
9 statuses · show schema
204
No body
400
No body
401
No body
403
No body
408
No body
413
No body
429
No body
502
No body
503
No body
GET/sn/evidencePublicSn Evidence

Serve a signed release-evidence envelope by its content hash. The envelope's signature is re-verified and its content hash re-checked against the requested one before the bytes are returned. Public and immutable, with the content hash as ETag.

hash(query)
string
required
5 statuses · show schema
200
schema
string
deployment_id
string
chain_id
integer
genesis_hash
string

0x-hex.

netuid
integer
kind
string
run_id
string
created_at
string
payload
object

The evidence payload; its shape depends on kind.

empty object
signer
string

0x-hex EVM address of the signing operator artifact key.

content_hash
string

sha256: followed by the lowercase hex hash of the canonical bytes.

signature
string
400
No body
404
No body
502
No body
503
No body
POST/sn/evidencePublicSn Publish Evidence

Publish a release-evidence envelope. There is no header credential: the authorization IS the envelope's own signature, which must verify and must be the operator's configured artifact key, and whose chain id, netuid, deployment id and genesis hash must all match this operator. The body is capped at 64 MiB. Every rejection is the same 400, so a caller cannot probe which check failed.

show schema
schema
string
deployment_id
string
chain_id
integer
genesis_hash
string

0x-hex.

netuid
integer
kind
string
run_id
string
created_at
string
payload
object

The evidence payload; its shape depends on kind.

empty object
signer
string

0x-hex EVM address of the signing operator artifact key.

content_hash
string

sha256: followed by the lowercase hex hash of the canonical bytes.

signature
string
2 statuses · show schema
200
content_hash
string
content_key
string

The immutable object key the envelope was published at.

history_key
string

The key under the run's history prefix.

bucket
string
400
No body
GET/sn/evidence/historyPublicSn Evidence History

List the evidence objects of ONE signed run. Both kind and run_id are mandatory, so a public caller cannot enumerate other campaigns or turn a small verifier query into an unbounded deployment scan.

With hash the answer is that one object, fully re-verified (signature, deployment, netuid, kind, run and content hash) rather than merely listed, and after may not be combined with it. The legacy empty-run namespace is readable only by exact hash.

deployment_id(query)
string
required
netuid(query)
integer

A nonzero uint16.

required
kind(query)
string
required
run_id(query)
string
required
hash(query)
string

Read exactly this object instead of paging.

limit(query)
integer

Objects per page, default 256.

after(query)
string

The previous page's next_after. Not valid with hash.

5 statuses · show schema
200
schema
string

Always urnetwork-release-evidence-history-v1.

objects
arraynull
more
boolean
next_after
string

The cursor for the next page; empty on the last page.

400
No body
404
No body
502
No body
503
No body
POST/sn/client-key/observationBearer authSn Client Key Observation

Publish one signed client-key observation and read back that client's key history. A key observation creates a durable signed public statement, so it is an explicit POST owned by an authenticated CLIENT session rather than a side effect of the public GET /key/{clientId} lookup, and the request cannot supply a key to sign.

The framing is strict: Content-Type: application/json, a positive Content-Length of at most 16 KiB, no content encoding, transfer encoding or trailers. The body must be exactly the canonical encoding of the request -- unknown fields, trailing JSON and any non-canonical re-encoding are refused -- and the full maximum response size is charged against the account's byte budget before any operator signature or public write, so a failed call still pays its admission.

show schema
client_id
string

uuid. The client whose key history is being observed.

request
string

base64 of the encoded signed observation request, at most 8 KiB. The request cannot supply a key to sign.

4 statuses · show schema
200
history
array<string>

The client's signed key history statements, base64 encoded, oldest first.

array
string
observation
string

base64 of the signed observation this call published.

400
No body
401
No body
429
No body
POST/sn/client-key/observationsBearer authSn Client Key Observations

The plural form of /sn/client-key/observation, with the same authenticated, bounded and charged publication contract: one HTTP body never becomes one logical quota item, so the maximum response size is charged once PER request in the batch before any work is done. The body is capped at 1 MiB and the responses come back in request order, each an encoded client-key history response.

A batch whose chain work cannot be admitted is refused with 413 and an X-Ur-Client-Key-Batch-Admission: work header.

show schema
requests
array<ClientKeyObservationRequest>
array
client_id
array<integer>

A 16-byte client id, on the wire as a JSON array of 16 integers.

array
integer
validator_hotkey
array<integer>

A 32-byte hotkey, on the wire as a JSON array of 32 integers.

array
integer
native_block
integer
native_hash
array<integer>

A 32-byte hash, on the wire as a JSON array of 32 integers.

array
integer
native_epoch
integer
decision_boundary
ClientKeyEffectiveBoundary
epoch
integer
block
integer
hash
array<integer>

A 32-byte hash, on the wire as a JSON array of 32 integers (a Go [32]byte).

array
integer
nonce
array<integer>

A 32-byte nonce, on the wire as a JSON array of 32 integers.

array
integer
maximum_response_bytes
integer
7 statuses · show schema
200
responses
array<ClientKeyHistoryResponse>

One encoded ClientKeyHistoryResponse per request, in request order.

array
history
array<string>

The client's signed key history statements, base64 encoded, oldest first.

array
string
observation
string

base64 of the signed observation this call published.

400
No body
401
No body
405
No body
408
No body
413
No body
429
No body
POST/onboarding/offer/issueBearer authonboardingOfferIssue

Issue the caller's welcome offer: 25% off the first year of Pro (3 months free), valid validity_days (5) from issue, stacked on the 14-day trial, redeemable on every store. Issued ONCE per network: the call is idempotent and returns the existing offer in whatever state it is in (created: false); an expired offer is never re-issued. Call it the moment the offer surface renders (the intro plan step), so the deadline is real.

Refused with error when the onboarding config is absent, or when the caller is in the holdout variant of the in-app offer experiment (experiments["offer.in_app"]): those networks show the regular plan picker and reach the offer by email instead.

Budgeted per caller address (30 per 10 minutes): over budget the response is 429 with a Retry-After header.

show schema
surface
string

Where the offer is being shown. Only these three are accepted here; empty means intro_step, and anything else (including email_link, which only the campaign engine issues) is refused with error.message "Unknown surface.".

enum: , intro_step, final_screen, account
storefront_country
string

ISO 3166-1 storefront country, when the app knows it

3 statuses · show schema
200
offer
OnboardingOffer

The offer. The key is ABSENT (not null) when refused.

issued_at
string (date-time)
expires_at
string (date-time)
percent_off
integer
months_free
integer
first_year_usd
number
regular_year_usd
number
tier
string
currency
string
state
string
enum: active, redeemed, expired
apple_offer_code
string
play_offer_tag
string
stripe_coupon_id
string
redeemed_at
one of

When the offer was first redeemed; null until then.

one of
string (date-time)
null
store
one of

The store of the first redemption; null until then.

one of
string
enum: apple, play, stripe, solana
null
created
boolean

true for the call that issued the offer

error
ErrorMessage

The key is ABSENT (not null) on success.

message
string
401
No body
429
string
POST/client/eventsBearer authclientEventsSend

Store a batch of product events (at most 200 per call) against the CLOSED event schema (see ClientEvent). The network id comes from the bearer token only; tier, path, experiment and variant are stamped by the server. platform is required and must be one of the known platforms: an event without one is rejected. Unknown event names, unknown prop keys, missing required props and ill-typed values are refused PER EVENT in rejected (the rest of the batch is stored); a rejection is final, the client must not resend that event. No free text is stored except feedback.submitted.text, which is redacted (emails, phone numbers, IP addresses) before storage.

Clients batch every 30 s or on background, drop an event after three failed calls, and never send more than 200 per call (a larger batch is a 400). Budgeted per caller address (120 calls per 10 minutes): over budget the response is 429 with a Retry-After header.

show schema
events
array<ClientEvent>
array
one of
ClientEventBase (recursive)
name
string
props
object
step
EventToken
EventToken (recursive)
index
integer
elapsed_ms
integer
ClientEventBase (recursive)
name
string
props
object
step
EventToken
EventToken (recursive)
index
integer
elapsed_ms
integer
ClientEventBase (recursive)
name
string
props
object
step
EventToken
EventToken (recursive)
index
integer
elapsed_ms
integer
ClientEventBase (recursive)
name
string
props
object
surface
OfferSurface
OfferSurface (recursive)
experiment
EventToken
EventToken (recursive)
variant
EventToken
EventToken (recursive)
tier
PriceTierName
PriceTierName (recursive)
price_shown
number
currency
CurrencyCode
CurrencyCode (recursive)
expires_in_s
integer
ClientEventBase (recursive)
name
string
props
object
plan
OfferPlan
OfferPlan (recursive)
ClientEventBase (recursive)
name
string
props
object
plan
OfferPlan
OfferPlan (recursive)
store
EventStore
EventStore (recursive)
ClientEventBase (recursive)
name
string
props
object
control
OfferDeclineControl
OfferDeclineControl (recursive)
elapsed_ms
integer
ClientEventBase (recursive)
name
string
props
object
store
EventStore
EventStore (recursive)
product
EventToken
EventToken (recursive)
plan
OfferPlan
OfferPlan (recursive)
trial
boolean
price
number
currency
CurrencyCode
CurrencyCode (recursive)
error_class
EventToken
EventToken (recursive)
ClientEventBase (recursive)
name
string
props
object
store
EventStore
EventStore (recursive)
product
EventToken
EventToken (recursive)
plan
OfferPlan
OfferPlan (recursive)
trial
boolean
price
number
currency
CurrencyCode
CurrencyCode (recursive)
error_class
EventToken
EventToken (recursive)
ClientEventBase (recursive)
name
string
props
object
store
EventStore
EventStore (recursive)
product
EventToken
EventToken (recursive)
plan
OfferPlan
OfferPlan (recursive)
trial
boolean
price
number
currency
CurrencyCode
CurrencyCode (recursive)
error_class
EventToken
EventToken (recursive)
ClientEventBase (recursive)
name
string
props
object
store
EventStore
EventStore (recursive)
product
EventToken
EventToken (recursive)
plan
OfferPlan
OfferPlan (recursive)
trial
boolean
price
number
currency
CurrencyCode
CurrencyCode (recursive)
error_class
EventToken
EventToken (recursive)
ClientEventBase (recursive)
name
string
props
object
empty object
ClientEventBase (recursive)
name
string
props
object
kind
EventToken
EventToken (recursive)
ClientEventBase (recursive)
name
string
props
object
rating
integer
reason
EventToken
EventToken (recursive)
has_text
boolean
text
string

free text, redacted (emails, phone numbers, IP addresses) before storage

ClientEventBase (recursive)
name
string
props
object
product_updates
boolean
4 statuses · show schema
200
accepted
integer

events stored (or deduplicated)

rejected
array<object>

schema refusals by batch index; final, do not resend

array
index
integer
message
string
400
No body
401
No body
429
string
POST/onboarding/clickPubliconboardingClick

Record a campaign landing-page click. Every onboarding email link is https://ur.io/o/?t=; the landing page posts the token here, which writes the server-side landing.clicked attribution event and answers with the in-app destination to route to (onboarding/connect, onboarding/widgets, onboarding/offer, onboarding/feedback). An app open (/network/auth-client) within 48 h of the click is attributed to it as app.opened.

No authentication: the signed token is the credential, and the response carries nothing about the network. An expired token still returns step and destination (with ok: false, error: expired) so the page can route without attributing; an invalid token returns ok: false, error: invalid. Budgeted per caller address (60 per 10 minutes) -> 429 with Retry-After.

show schema
token
string
2 statuses · show schema
200
ok
boolean
step
string

the campaign step the link belongs to (e.g. e1_connect)

destination
string

the in-app destination to route to

enum: onboarding/connect, onboarding/widgets, onboarding/offer, onboarding/feedback
error
string
enum: invalid, expired
429
string
GET/onboarding/feedback/{token}PubliconboardingFeedbackToken

Resolve a feedback link token (https://ur.io/f/?r=n or ?why=n) to the rating or reason the one-tap email button stood for, so the in-app feedback screen opens pre-filled. The token carries the step and may carry the rating/reason; r (1-5) and why (a reason token) override when present. The app then submits the actual feedback through /feedback/send-feedback and reports feedback.submitted through /client/events.

No authentication: the signed token is the credential. Budgeted per caller address (60 per 10 minutes) -> 429 with Retry-After.

token(path)
string

The signed feedback token from the email link.

required
r(query)
integer

Rating 1-5 from the tapped button.

why(query)
string

Reason token from the tapped button.

2 statuses · show schema
200
ok
boolean
step
string
rating
integer

1-5. The key is ABSENT when the link carried no rating (the server omits the zero value rather than sending 0).

reason
string
error
string
enum: invalid, expired
429
string
GET/admin/onboarding/resultsBearer authadminOnboardingResults

The nightly aggregate the analysis script (mmm/onboarding/analyze.mjs) reads: onboarding_results_daily, recomputed at 02:00 UTC by the OnboardingResultsRollup task for the last onboarding.results.rollup_days cohort days (config/main/onboarding.yml). One row per cohort day (the network's sign-up day, UTC), experiment, variant, surface, platform, price tier and path. Counts are networks, never events.

Exposure is the registry assignment (hash of network id and experiment id), so holdout rows exist without any stamped event; for the email sequence it is the variant the campaign row was served, and only networks with an email address are exposed to an email experiment. The pseudo-experiment _all (variant all, surface all) counts every network of the cohort so the funnel per platform, tier and path is readable without an experiment. The cohort is the campaign's real population: a network enters (its cohort time is the enrollment time) when its login is an email address, otherwise on its first device client within 30 days of creation; a network with neither an email login nor a device is never a cohort member.

Column definitions, measured from each network's cohort time: sent, delivered, opened, clicked, unsubscribe, complaint = at least one email.* event of that name, any step; landing_clicked = a landing.clicked; app_open_48h = an app.opened (the auth-client attribution within 48 h of a landing click); connect_7d = a connect.day (server-written on each UTC day the network had a connection) or a connect.first within 7 days; widget_7d = widget.added within 7 days; feedback_7d = feedback.submitted or an account feedback within 7 days; pro_start_14d = a purchase.completed or the first subscription renewal (trial included) within 14 days; trial_to_paid_35d = trial.converted within 35 days; refund_60d = a refund within 60 days; retention_d7 = a connect.day on day 6 or 7; retention_d30 = a connect.day on day 28, 29 or 30. Connection days are the analytics events, never the connection table (which is pruned hours after disconnect), so they exist from the day the server started writing connect.day. A window that has not matured reports 0: read matured_days (the cohort's age at computed_at) to tell "not yet" from "did not".

Volume floor: rows with fewer than onboarding.results.min_exposures exposures (default 10) are suppressed, and no identifier of any kind is returned. Ordered by the dimension tuple; keyset paged by cursor.

Auth: Authorization: Bearer where the token is listed in vault onboarding.yml under onboarding.admin_bearers (compared in constant time). A missing or unknown token is 401; a network JWT is

  1. Without the vault entry the endpoint is closed.
experiment(query)
string

The experiment id from the registry.

required
from(query)
string (date)

First cohort day, inclusive (YYYY-MM-DD).

required
to(query)
string (date)

Last cohort day, inclusive (YYYY-MM-DD).

required
surface(query)
string
platform(query)
EventPlatform
tier(query)
PriceTierName
path(query)
string

The campaign path, A (saw the in-app offer) or B.

cursor(query)
string

The next_cursor of the previous page.

limit(query)
integer

Rows per page, 1-1000 (default 500).

4 statuses · show schema
200
rows
array<OnboardingResultsRow>
array
cohort_day
string (date)
experiment
string
variant
string
surface
string
platform
string

An EventPlatform, or unknown when the sign-up did not say.

tier
string

A PriceTierName, or unknown when the sign-up country is unknown.

path
string

The campaign path A or B, or unknown.

exposures
integer
sent
integer
delivered
integer
opened
integer
clicked
integer
landing_clicked
integer
app_open_48h
integer
connect_7d
integer
widget_7d
integer
feedback_7d
integer
pro_start_14d
integer
trial_to_paid_35d
integer
refund_60d
integer
retention_d7
integer
retention_d30
integer
unsubscribe
integer
complaint
integer
matured_days
integer

The cohort's age in whole days at computed_at; a window longer than this has not been measured yet.

computed_at
string (date-time)
next_cursor
stringnull

The cursor for the next page; null on the last page.

min_exposures
integer

The volume floor applied to this page.

400
No body
401
No body
403
No body
GET/admin/onboarding/email-trackerBearer authadminOnboardingEmailTracker

The durable per-flow-step send and engagement aggregate of the onboarding email campaign, one row per send day and dimension tuple (step, template, variant, experiment, experiment variant, platform, path). Counts are DISTINCT NETWORKS, never events, and no network, address, message or session identifier is ever returned; rows below the configured minimum-exposure floor (min_networks) are suppressed.

engaged is the union of email.clicked, landing.clicked, app.opened, connected, widget.added, feedback.submitted and pro.started, deliberately excluding email.opened (open tracking is too unreliable to count as engagement). attribution_ambiguous counts events that could only be attributed to a template shared by more than one flow step. The definitions block returns these rules with the data, so a reader never has to assume them.

Same auth as /admin/onboarding/results: an admin bearer from vault onboarding.yml, compared in constant time. Keyset paged by cursor.

from(query)
string (date)

First send day, inclusive (YYYY-MM-DD).

required
to(query)
string (date)

Last send day, inclusive (YYYY-MM-DD). Must not precede from, and the range is bounded.

required
step(query)
string

A flow step; e1, e2, e3, e4 or e5.

experiment(query)
string
platform(query)
string
path(query)
string

The campaign path, A or B.

cursor(query)
string

The next_cursor of the previous page.

limit(query)
integer
4 statuses · show schema
200
rows
array<OnboardingEmailTrackerRow>
array
send_day
string (date)
step
string
template
string
variant
string
experiment
string
experiment_variant
string
platform
string
path
string
sent
integer
delivered
integer
opened
integer
clicked
integer
landing_clicked
integer
app_opened
integer
connected
integer
widget_added
integer
feedback_submitted
integer
pro_started
integer
engaged
integer

The union of the engagement events named in definitions.engaged_includes.

bounced
integer
unsubscribed
integer
complained
integer
attribution_ambiguous
integer

Events attributable only to a template shared by more than one flow step.

computed_at
string (date-time)
next_cursor
stringnull

The cursor for the next page; null on the last page.

min_networks
integer

The volume floor applied to this page.

definitions
OnboardingEmailTrackerDefinitions
counts
string
dashboard_window_days
integer
engagement_window_days
integer
delivery_correction_days
integer
engaged_includes
array<string>
array
string
engaged_excludes
array<string>
array
string
legacy_attribution
string
400
No body
401
No body
403
No body
GET/admin/onboarding/experimentsBearer authadminOnboardingExperiments

The onboarding experiment registry (config/main/onboarding.yml onboarding.experiments) as the server loaded it, so the analysis script judges results against the same allocation, primary metric, guardrails and minimum exposures the server applied, plus each experiment's live variant states from the pause overlay (network_onboarding_experiment_state: a variant the nightly guardrail check or bringyourctl onboarding experiments --pause paused is served as control until resumed) and the results configuration (min_exposures, rollup_days, guardrail_days). Same auth as /admin/onboarding/results.

3 statuses · show schema
200
experiments
array<OnboardingExperiment>
array
id
string
surface
string

email. | offer.in_app | offer.email | cadence

status
string
enum: draft, running, paused, done
start
string (date)
stop
string (date)
allocation
objectnull

percent per variant name, by hash(network_id, id)

variants
objectnull

per variant, the Brevo template name or the store copy key set and layout id

primary_metric
string
secondary
array<string>
array
string
guardrails
map
map
number
min_exposures
integer
active
boolean

Whether the experiment assigns right now (status running, within start/stop).

variant_states
array<OnboardingExperimentVariantState>

The pause overlay rows of this experiment (empty when nothing was ever paused).

array
experiment_id
string
variant
string
status
string

paused = served as control; running = resumed.

enum: paused, running
reason
string
updated_at
string (date-time)
results
object

The results configuration the rollup and the results endpoint apply.

min_exposures
integer
rollup_days
integer
guardrail_days
integer
401
No body
403
No body
POST/updates/brevoPublicbrevoWebhook

The Brevo transactional-email webhook. Brevo is registered (idempotently, by the server when onboarding.enabled is on) to post one event per delivery step.

Authentication is HTTP BASIC, not bearer and not a network token: the request must carry Authorization: Basic base64(:) where is one of the account's webhook_bearers (vault brevo.yml) and the password half is empty.

unsubscribe turns the account's product-updates preference off (the existing behavior). For the onboarding campaign the remaining events are recorded as server-written email.delivered|opened|clicked|bounced|complained|unsubscribed events against the network, with the campaign step taken from the stored send that message-id names. An event whose message id is not a known campaign send records nothing.

The event name is matched lower-cased against this exact set: delivered; opened, unique_opened, proxy_open; click, clicked; hard_bounce, blocked, invalid_email, error; spam, complaint; unsubscribed, unsubscribe. Anything else records no campaign event. A hard bounce or a complaint exits the network from the sequence.

show schema
event
string
enum: request, delivered, opened, uniqueOpened, click, hardBounce, softBounce, blocked, spam, complaint, unsubscribed, unsubscribe, invalid, deferred, error
email
string
message-id
string
tags
array<string>

the send's tags; the campaign sends carry [onboarding, , ]

array
string
tag
string

Brevo's legacy form of the tags, a JSON-encoded array in a string

template_id
integer
X-Mailin-custom
string

The campaign sets this header to the step name on every send. The webhook path does not read it back; the step comes from the stored send that message-id names.

link
string

the clicked URL (click events)

ts_event
integer

unix seconds of the event

2 statuses · show schema
200
empty object
401
No body
GET/.well-known/oauth-authorization-serverPublicOauth Server Metadata

The rfc 8414 authorization server metadata. Served with Cache-Control: no-store, and identical to the OpenID Connect discovery document below. The issuer it publishes is the hostname clients must use, which is not necessarily the api host this request reached.

1 status · show schema
200
issuer
string
authorization_endpoint
string
token_endpoint
string
registration_endpoint
string
revocation_endpoint
string
userinfo_endpoint
string
jwks_uri
string
scopes_supported
array<string>
array
string
response_types_supported
array<string>
array
string
response_modes_supported
array<string>
array
string
grant_types_supported
array<string>
array
string
code_challenge_methods_supported
array<string>
array
string
token_endpoint_auth_methods_supported
array<string>
array
string
subject_types_supported
array<string>
array
string
id_token_signing_alg_values_supported
array<string>
array
string
claims_supported
array<string>
array
string
client_id_metadata_document_supported
boolean
authorization_response_iss_parameter_supported
boolean
resource_indicators_supported
boolean

rfc 8707.

GET/.well-known/openid-configurationPublicOauth Openid Configuration

The OpenID Connect discovery document. The same document as /.well-known/oauth-authorization-server.

1 status · show schema
200
issuer
string
authorization_endpoint
string
token_endpoint
string
registration_endpoint
string
revocation_endpoint
string
userinfo_endpoint
string
jwks_uri
string
scopes_supported
array<string>
array
string
response_types_supported
array<string>
array
string
response_modes_supported
array<string>
array
string
grant_types_supported
array<string>
array
string
code_challenge_methods_supported
array<string>
array
string
token_endpoint_auth_methods_supported
array<string>
array
string
subject_types_supported
array<string>
array
string
id_token_signing_alg_values_supported
array<string>
array
string
claims_supported
array<string>
array
string
client_id_metadata_document_supported
boolean
authorization_response_iss_parameter_supported
boolean
resource_indicators_supported
boolean

rfc 8707.

GET/.well-known/jwks.jsonPublicOauth Jwks

The authorization server's public signing keys as a JWK set. All current keys are listed so tokens stay verifiable across a rotation.

1 status · show schema
200
keys
array<Jwk>
array
kty
string
crv
string
kid
string
alg
string
use
string
x
string
y
string
POST/oauth/tokenPublicOauth Token

The rfc 6749 token endpoint, as a form post. Two grants are supported: authorization_code (with the PKCE code_verifier, and optionally the rfc 8707 resource indicator) and refresh_token. Clients are public -- the token endpoint auth method is none -- so client_id is always required and there is no client secret.

Errors are the rfc 6749 section 5.2 envelope. The descriptions are deliberately terse and the detail is logged rather than returned, so an error cannot be used to probe which check failed. The response is never cached by a shared cache.

show schema
grant_type
string
enum: authorization_code, refresh_token
client_id
string
code
string

authorization_code grant.

redirect_uri
string

authorization_code grant; must be registered for this client.

code_verifier
string

authorization_code grant; the PKCE verifier.

resource
string

authorization_code grant; the rfc 8707 resource indicator.

refresh_token
string

refresh_token grant.

scope
string

refresh_token grant; a space-separated subset of the granted scopes.

4 statuses · show schema
200
access_token
string
token_type
string
expires_in
integer
refresh_token
string
id_token
string
scope
string
400
error
string
error_description
string
403
error
string
error_description
string
500
error
string
error_description
string
POST/oauth/registerPublicOauth Register

Dynamic client registration (rfc 7591). Deprecated by the MCP specification in favor of client id metadata documents, and kept for clients that predate it. The endpoint is openly writable, so it is metered per caller address before the body is parsed or anything is stored; over budget it answers 429 with Retry-After. Registered clients are public, so no secret is issued.

show schema
client_id
string
client_name
string
client_uri
string
logo_uri
string
application_type
string
redirect_uris
array<string>
array
string
scope
string
grant_types
array<string>
array
string
3 statuses · show schema
201
client_id
string
client_name
string
application_type
string
redirect_uris
array<string>
array
string
scope
string
token_endpoint_auth_method
string

Always none -- registered clients are public and have no secret.

400
error
string
error_description
string
429
error
string
error_description
string
POST/oauth/revokePublicOauth Revoke

Token revocation (rfc 7009), as a form post. Always answers 200 with an empty body: per the rfc a client must not be able to tell an unknown token from a revoked one.

show schema
token
string
2 statuses · show schema
200
No body
400
error
string
error_description
string
GET/oauth/userinfoBearer authOauth Userinfo

The OpenID Connect userinfo endpoint. Authenticated by an ACCESS TOKEN this authorization server issued, carrying the openid scope -- not by a URnetwork network JWT. The token's audience is the resource it was minted for rather than this issuer, so the audience is deliberately not constrained here.

3 statuses · show schema
200
sub
string
network_id
string
network_name
string
principal
string
401
error
string
error_description
string
403
error
string
error_description
string
POST/oauth/consentBearer authOauth Consent

What the consent page on the ur.io origin needs to render: who is asking, for what scopes, and whether consent is actually required. The caller is the consent page passing through the signed-in user's URnetwork JWT.

The authorization request is validated here, BEFORE anything is rendered, so a hostile request never reaches a screen that could be used to phish approval; an invalid request is reported to the user rather than redirected, because a redirect uri is only trustworthy once it has matched the client's registered set. prompt=consent forces the screen even when the scopes are already approved. issuer is returned so the page can echo iss when the user declines, as rfc 9207 requires on error responses too.

show schema
client_id
string
redirect_uri
string
response_type
string

Defaults to code.

scope
string
state
string
code_challenge
string
code_challenge_method
string

Defaults to S256.

resource
string

rfc 8707 resource indicator.

nonce
string
prompt
string

consent forces the screen even when the scopes are already approved.

3 statuses · show schema
200
client_name
string
client_uri
string
logo_uri
string
scopes
array<string>

The requested scopes, filtered to the supported set.

array
string
network_name
string
issuer
string

Echoed as iss when the user declines, per rfc 9207.

consent_required
boolean
error
string
400
error
string
error_description
string
401
error
string
error_description
string
POST/oauth/authorizeBearer authOauth Authorize

Mint the authorization code for a request the user approved, and return the redirect the consent page should follow. The caller is the ur.io consent page passing through the signed-in user's URnetwork JWT. Consent is recorded HERE rather than when the screen is shown, so an abandoned screen grants nothing. The redirect carries code, the request's state when it had one, and iss per rfc 9207 so the client can detect a mix-up attack.

show schema
client_id
string
redirect_uri
string
response_type
string

Defaults to code.

scope
string
state
string
code_challenge
string
code_challenge_method
string

Defaults to S256.

resource
string

rfc 8707 resource indicator.

nonce
string
prompt
string

consent forces the screen even when the scopes are already approved.

3 statuses · show schema
200
redirect_uri
string

Where the consent page should send the user agent; carries code, iss and any state.

400
error
string
error_description
string
401
error
string
error_description
string