Deploy a Service
This guide takes you from a registered service ID to a supplier serving real relays. Budget a day: a few hours to build and test the backend against the rules below, an hour for staking and the RelayMiner, and a session or two of waiting for the network to pick your supplier up.
It assumes the service is already registered. If it is not, start with Register a Service. It also assumes you are the first supplier. A later supplier follows the same steps from Stake a Supplier onward, reading the service’s metadata card instead of writing it.
Every relay a supplier serves is signed with the operator key. A lost operator key cannot be replaced on an existing supplier stake; the operator address is immutable. Back it up before staking and never run two RelayMiners with the same key against the same service unless you intend them as replicas.
Prerequisites
- A registered service ID with a metadata card. See Register a Service.
pocketdinstalled and two funded accounts: an owner that holds the stake and an operator that signs relays. Non-custodial staking, where these are different accounts, is recommended for production. See Supplier Staking for the owner-operator model and Accounts & Keys.- Enough POKT to stake. The supplier minimum is a governance parameter, currently 59,500 POKT on MainNet. Query it on the network you are staking on:
On Beta, request funds from the faucet listed on the Networks page. The operator account also needs a small working balance for claim and proof transaction fees.bash
pocketd query supplier params --network=beta - A public endpoint. Suppliers publish a URL that clients connect to. The protocol accepts
http://orhttps://, but gateways can be configured to refuse plain-HTTP or raw-IP URLs, so for gateway traffic usehttps://with a publicly trusted certificate, terminated by a reverse proxy you control, because the RelayMiner itself speaks plain HTTP. - Access to a Pocket full node, either your own or a public RPC and gRPC endpoint from the Networks page. A RelayMiner needs both.
Set Up Environment
export SERVICE_ID=<your-service-id>
export OWNER=<owner-key-name>
export OPERATOR=<operator-key-name>
export NETWORK=beta # or main
export TX_FLAGS="--network=$NETWORK --gas auto --gas-prices 1upokt --gas-adjustment 1.5"How Your Service Will Be Consumed
The protocol puts no constraints on the shape of a request or a response. A RelayMiner forwards whatever HTTP request arrives to your backend, signs whatever bytes the backend returns, status code and body included, and passes them back unaltered. A client that signs its own relays, such as an application using the SDK directly, could exchange any format with your backend.
In practice, almost all traffic reaches services through a gateway such as SAGE or PATH. Gateways hold application keys for many users, select suppliers, retry failures, and score every supplier on the responses it returns. That scoring is what the rules in the next section are about. They are gateway rules rather than protocol rules, and a gateway operator can relax them per service, but a service designed for them works everywhere, and a service designed without them will not be routed to by the gateways that carry the network’s traffic.
Build the Backend
The backend is an ordinary HTTP server. It does not talk to the chain, verify signatures, or know what a session is; the RelayMiner does all of that. What the backend receives is a plain HTTP request with the method, path, query string, headers, and body the client sent, and what it returns is passed back to the client.
There is one catch. Gateways grade every response to decide which suppliers to trust, and the grade is applied to exactly the bytes your backend returned. A backend that ignores the rules below will work perfectly when you test it directly and will get its suppliers penalized in production, and a service that gets its own suppliers penalized is one nobody will keep supplying. Design to these rules from the first line of code.
Every Response Is a JSON Object
Gateways classify a response body by its first byte. A body that starts with { or [ is graded as a normal response. A body that starts with <html or <!DOCTYPE is treated as a proxy error page: the request is retried on another supplier and the supplier that answered is penalized and circuit-broken. Plain text, an empty body on a 200, or CSV get the same treatment.
So any output that is not itself JSON travels as a string inside a JSON object. The field names are your contract, not a network rule; what matters is that the body starts with {.
{
"content_type": "text/html",
"body": "<!DOCTYPE html><html>...</html>"
}Plain JSON string escaping is sufficient. Base64 is not needed and doubles the size. Put short metadata fields first and the large string last. A gateway operator can switch body grading off for a service, but you cannot count on every gateway doing so.
All Inputs Arrive in the Body
The relay format can carry headers, and the RelayMiner copies whatever arrives onto the backend request, but you cannot rely on them arriving. SAGE forwards the HTTP method, the path, and the query string only; it drops every caller header and sets Content-Type: application/json on the request your backend sees. Self-signing clients forward most headers but strip credentials, and older gateways drop the query string as well. Design every endpoint to take its inputs from a JSON request body, including bulk data such as CSV, and use POST for anything with inputs.
No Caller Authentication
Relays are paid for by the application’s stake, not by a credential. Gateways and application-side relay clients strip Authorization, Api-Key, X-Api-Key, and Cookie headers before forwarding. If your backend needs a credential to reach something behind it, the RelayMiner can inject static headers or HTTP basic auth toward the backend; the caller never supplies one.
Status Codes
| Situation | Return | Why |
|---|---|---|
| Success | 200 with a JSON object | Graded as success. |
| Nothing to return | 204 with no body | The only status where an empty body is graded as correct. |
| Bad input | 400 or 422 with a JSON error object | Gateways treat a 4xx with a JSON body as the client’s mistake: delivered as is, not retried, no penalty. |
| Backend failure | 5xx | Retried elsewhere, the supplier is penalized, and the relay is not paid. Reserve for real failures. |
Never answer an expected condition with a 5xx, and never answer any error with HTML or plain text.
Avoid Error-Looking Words Early in a Success Body
Gateways also scan the first 2 KB of a body for substrings that indicate an upstream failure, among them timeout, bad gateway, service unavailable, gateway timeout, connection refused, and connection reset, and they retry on a match even inside a valid JSON object. Do not name fields timeout and do not echo those phrases in status messages.
Respond Within the Session
Gateways bound each relay attempt with a per-service timeout; the gateway configuration later in this guide uses 30 seconds. A relay whose backend call finishes after the session ends, plus the grace period of 10 blocks, is not paid. Long-running work should return quickly with a handle rather than blocking, unless you deliberately target streaming-capable clients through the HA RelayMiner.
Serve Identity Encoding
Some relay clients force Accept-Encoding: identity. Do not gzip responses.
Provide the Three Probe Endpoints
Your metadata card’s serving.healthcheck names an identity endpoint, a readiness endpoint, and a cheap functional request. Implement them exactly as the card describes; suppliers and gateways will run them.
GET /v1/version → {"service": "<service-id>", "version": "1.2.0"}
GET /v1/health → {"status": "ok"}
POST /v1/<resource> with a minimal body → a deterministic answerTest the Backend Directly
Before involving the network, run the card’s health checks against the backend with curl, and confirm that every response, including error responses, starts with {.
Choose a RelayMiner
Two implementations exist. Both speak the same protocol and both serve REST backends.
RelayMiner (pocketd relayminer) | HA RelayMiner | |
|---|---|---|
| Shape | One process | Stateless relayers plus a miner, coordinated through Redis |
| Fits | A first deployment, a single machine, moderate traffic | Production, multiple replicas, failover, high volume |
| Streaming responses | No | Yes |
| Backend health checks | Reachability only | Active per-backend checks and circuit breaking |
| Documentation | RelayMiner Setup | HA RelayMiner |
Start with the single-process RelayMiner unless you already know you need HA. Moving to HA later does not touch the supplier stake.
Run the RelayMiner
The examples use the single-process RelayMiner. The HA equivalent is noted where it differs.
Configuration
# relayminer_config.yaml
default_signing_key_names: [operator] # the operator key's name in the keyring
smt_store_path: /var/lib/pocket/smt # must persist across restarts
default_request_timeout_seconds: 30
default_max_body_size: 20MB
pocket_node:
query_node_rpc_url: https://sauron-rpc.beta.infra.pocket.network
query_node_grpc_url: https://sauron-grpc.beta.infra.pocket.network:443
tx_node_rpc_url: https://sauron-rpc.beta.infra.pocket.network
metrics:
enabled: true
addr: :9090
ping:
enabled: true
addr: :8081
suppliers:
- service_id: <your-service-id>
listen_url: http://0.0.0.0:8545 # the reverse proxy forwards here
service_config:
backend_url: http://127.0.0.1:8080 # your backend
forward_pocket_headers: true
rpc_type_service_configs:
rest:
backend_url: http://127.0.0.1:8080Points that matter for a REST backend:
- The key under
rpc_type_service_configsis the card’srpc_types[].type, lowercased.RESTin the card becomesresthere. - The path the client requested is appended to
backend_url. Withbackend_url: http://127.0.0.1:8080/api, a request for/v1/chartreaches the backend at/api/v1/chart. Trailing slashes are normalized away. forward_pocket_headers: trueaddsPocket-Supplier,Pocket-Service,Pocket-Session-Id,Pocket-Application,Pocket-Session-Start-Height, andPocket-Session-End-Heightto each backend request. They are the only per-request identity your backend will ever see, and they are useful for logging and per-application rate limiting.smt_store_pathholds the relays you have served but not yet claimed. If it is lost, those relays are unpaid.
For the HA RelayMiner the same service is declared as:
services:
<your-service-id>:
timeout_profile: fast # or streaming, for long or SSE responses
max_body_size_bytes: 20971520
default_backend: rest
backends:
rest:
url: http://127.0.0.1:8080
health_check:
endpoint: /v1/healthStart It
pocketd relayminer start \
--config ./relayminer_config.yaml \
--chain-id pocket-lego-testnet \
--keyring-backend file \
--grpc-insecure=falseUse --chain-id pocket on MainNet. Confirm it is up and can reach the backend:
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8081/ping # 204 when every backend_url is reachablePut a Reverse Proxy in Front
The RelayMiner listens on plain HTTP. Terminate TLS on a reverse proxy such as Caddy, nginx, or your cloud load balancer, and forward to listen_url. The proxy’s public https:// address is what you will stake as the supplier’s endpoint. Do not add authentication at the proxy; every request is already signed.
Stake a Supplier
The stake declares which service you serve and where. The full field reference and the owner-operator model are in Supplier Staking. The stake amount must be at least the live min_stake, which on MainNet is currently 59500000000upokt.
# supplier_stake_config.yaml
owner_address: <owner pokt1... address>
operator_address: <operator pokt1... address>
stake_amount: <amount>upokt # at least the live min_stake
default_rev_share_percent:
<owner pokt1... address>: 100
services:
- service_id: <your-service-id>
endpoints:
- publicly_exposed_url: https://relay.example.org
rpc_type: RESTrpc_type must match the card. Gateways select supplier endpoints by transport. A card that declares REST and a stake that declares JSON_RPC means REST clients never see your endpoint. Use exactly the type the card’s rpc_types lists.
Before staking, send any transaction from the operator account, for example a tiny self-transfer, so its public key is recorded on-chain. Gateways reject responses from an operator whose key they cannot look up.
pocketd tx supplier stake-supplier \
--config ./supplier_stake_config.yaml \
--from $OPERATOR \
$TX_FLAGSVerify:
pocketd query supplier show-supplier <operator address> --network=$NETWORKThe service configuration becomes active at the start of the next session. A session is 20 blocks, which is roughly 10 minutes on Beta TestNet and 20 minutes on MainNet given their block times on the Networks page.
Test End to End
Testing needs an application, because relays are paid for by an application stake. Stake a small one for your own service on Beta. See Application Staking for the details. The application minimum is a governance parameter, currently 1,000 POKT on MainNet; query it on Beta with pocketd query application params --network=beta. The short form is:
# app_stake_config.yaml
stake_amount: <amount>upokt # at least the live application min_stake
service_ids:
- <your-service-id> # exactly onepocketd tx application stake-application --config ./app_stake_config.yaml --from <app-key> $TX_FLAGSWait for the next session, then send relays with pocket-ap, a command-line relay client from the pocket-ap repository. It signs with the application key directly; no gateway and no delegation are needed, because an application is always a member of its own signing ring.
go install github.com/pokt-network/pocket-ap/cmd/pocket-ap@latest # or: brew install pokt-network/tap/pocket-ap# pocket-ap.yaml
network: beta
listeners:
- addr: 127.0.0.1:8550
service_id: <your-service-id>
rpc_type: rest
apps: [] # key comes from the environmentPOCKET_APP_PRIVATE_KEY=<app key hex> pocket-ap call \
--config ./pocket-ap.yaml \
--service <your-service-id> --rpc-type rest \
-X POST --path /v1/<resource> \
-d '{"...": "..."}' -vIts --compare <url> flag sends the same request straight to your backend and diffs the two answers, which is the fastest way to catch a path-prefix or content-type mistake.
pocketd relayminer relay sends JSON-RPC relays only. It posts to the endpoint root with no path, so it cannot exercise a REST service. Use pocket-ap or a gateway.
Then confirm the relay was accounted for. Within a session or two of serving relays you should see a claim from your operator:
pocketd query proof list-claims --supplier-operator-address <operator address> --network=$NETWORKTo test the path most users will take, run a gateway locally. SAGE is the current gateway implementation; the configuration it needs for your service is in the next section.
Give Gateway Operators What They Need
Today each gateway is configured for your service by hand, and if it is not, a REST request to it is refused before a session is even looked up. Card-driven configuration is on the near-term roadmap, so keep the card and this snippet in sync, and publish the snippet in your service documentation so an operator can copy it.
The SAGE service entry:
gateway_config:
services:
- id: <your-service-id>
type: passthrough
rpc_types: ["rest"]
timeout_config:
relay_timeout: 30s
active_health_checks:
local:
- service_id: <your-service-id>
enabled: true
checks:
- name: version
type: rest
method: GET
path: /v1/version
expected_status_code: 200
reputation_signal: critical_error
timeout: 5s
- name: health
type: rest
method: GET
path: /v1/health
expected_status_code: 200
reputation_signal: major_error
timeout: 5sThe health checks mirror your card’s serving.healthcheck. Keep the two in sync. Do not add sync_check or sync_allowance to a non-blockchain service; those assume a block height and will mark every healthy supplier as failing.
Minimal Server Specifications
Three components run on the supplier side. They can share one machine at first.
| Component | Minimum | Comfortable | Notes |
|---|---|---|---|
| Service backend | Whatever your service needs | Size it for concurrent requests, not average load. Gateways hedge and retry, so bursts are normal. | |
| RelayMiner | 1 vCPU, 1 GB RAM, 5 GB SSD | 4 vCPU, 16 GB RAM, 5 GB SSD | Scales linearly with relay volume and with the number of services one RelayMiner fronts. smt_store_path must be on persistent disk. |
| Full node | 4 vCPU, 16 GB RAM, 200 GB SSD | 6 vCPU, 32 GB RAM, 420 GB SSD | Optional. A public RPC and gRPC endpoint from the Networks page works for a first deployment, but a production RelayMiner should not depend on infrastructure it does not control. |
The full tables are on Hardware & Infrastructure Requirements. Linux on x86_64 or ARM64 is the supported environment for the RelayMiner and full node.
Beyond hardware:
- Persistent disk for the RelayMiner’s SMT store and for the keyring.
- A public endpoint, on HTTPS with a publicly trusted certificate if you want gateway traffic. The protocol accepts plain HTTP, but gateways verify TLS and can be configured to refuse plain-HTTP or raw-IP supplier URLs outright.
- Operator balance kept above a few POKT at all times. Claims and proofs are transactions; an operator that cannot pay the fee forfeits the session’s earnings.
- Metrics scraped. The RelayMiner exposes Prometheus metrics on the address set under
metrics.addr(:9090in the configuration above). See Monitoring.
Going to MainNet
- Register the service on MainNet if you have not yet. See Register a Service.
- Re-query
min_stake,add_service_fee, and the shared pricing parameters with--network=main. - Repoint
pocket_nodeURLs and--chain-id pocket. Every other config line is unchanged. - Stake, wait a session, run the card’s health checks through
pocket-ap, and watch for the first claim. - Publish the gateway configuration and the card’s
docsURL, then tell gateway operators the service exists. A supplier no gateway routes to earns nothing.
Related Pages
- Register a Service — the on-chain record and metadata card this guide builds on
- Supplier Staking — full stake configuration and the owner-operator model
- RelayMiner Setup — every RelayMiner configuration field
- HA RelayMiner — the production, multi-replica deployment
- Application Staking — staking the test application
- Hardware & Infrastructure Requirements — sizing tables
- Monitoring — metrics and alerting
- Rewards & Economics — how supplier income is calculated