Overview
Cosmos SDK provides a REST API server that exposes blockchain queries and transaction broadcasting over HTTP. It uses gRPC-Gateway to automatically generate REST endpoints from gRPC service definitions.API Server Setup
Server Configuration
import "github.com/cosmos/cosmos-sdk/server/api"
type Config struct {
Enable bool `mapstructure:"enable"`
Swagger bool `mapstructure:"swagger"`
Address string `mapstructure:"address"`
MaxOpenConnections int `mapstructure:"max-open-connections"`
RPCReadTimeout uint `mapstructure:"rpc-read-timeout"`
RPCWriteTimeout uint `mapstructure:"rpc-write-timeout"`
RPCMaxBodyBytes uint `mapstructure:"rpc-max-body-bytes"`
EnableUnsafeCORS bool `mapstructure:"enabled-unsafe-cors"`
}
func DefaultConfig() *Config {
return &Config{
Enable: true,
Swagger: false,
Address: "tcp://0.0.0.0:1317",
MaxOpenConnections: 1000,
RPCReadTimeout: 10,
RPCWriteTimeout: 10,
RPCMaxBodyBytes: 1000000,
EnableUnsafeCORS: false,
}
}
server/config/config.go
Starting the API Server
import (
"github.com/cosmos/cosmos-sdk/server/api"
"github.com/cosmos/cosmos-sdk/server"
)
func StartAPIServer(
ctx *server.Context,
clientCtx client.Context,
cfg config.APIConfig,
) (*api.Server, error) {
apiSrv := api.New(clientCtx, ctx.Logger.With("module", "api-server"))
// Register gRPC-Gateway routes
app.RegisterGRPCGatewayRoutes(clientCtx, apiSrv.Router)
// Register Tendermint/CometBFT routes
cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSrv.Router)
// Register tx routes
authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSrv.Router)
// Enable swagger documentation
if cfg.Swagger {
apiSrv.Router.Handle("/swagger", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
// Serve swagger UI
},
))
}
// Start server
return apiSrv, apiSrv.Start(cfg)
}
server/api/server.go
gRPC-Gateway Integration
Registering Routes
import (
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/cosmos/cosmos-sdk/client"
)
func RegisterGRPCGatewayRoutes(
clientCtx client.Context,
mux *runtime.ServeMux,
) {
// Register bank module routes
banktypes.RegisterQueryHandlerClient(
context.Background(),
mux,
banktypes.NewQueryClient(clientCtx),
)
// Register staking module routes
stakingtypes.RegisterQueryHandlerClient(
context.Background(),
mux,
stakingtypes.NewQueryClient(clientCtx),
)
// Register other modules...
}
Auto-Generated REST Endpoints
REST endpoints are automatically generated from gRPC service definitions:service Query {
// Maps to: GET /cosmos/bank/v1beta1/balances/{address}/by_denom
rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/balances/{address}/by_denom";
}
// Maps to: GET /cosmos/bank/v1beta1/balances/{address}
rpc AllBalances(QueryAllBalancesRequest) returns (QueryAllBalancesResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/balances/{address}";
}
// Maps to: GET /cosmos/bank/v1beta1/supply
rpc TotalSupply(QueryTotalSupplyRequest) returns (QueryTotalSupplyResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/supply";
}
}
REST API Usage
Querying Balances
# Get single balance
curl http://localhost:1317/cosmos/bank/v1beta1/balances/cosmos1.../by_denom?denom=stake
# Response
{
"balance": {
"denom": "stake",
"amount": "1000000"
}
}
# Get all balances
curl http://localhost:1317/cosmos/bank/v1beta1/balances/cosmos1...
# Response
{
"balances": [
{
"denom": "stake",
"amount": "1000000"
},
{
"denom": "token",
"amount": "500000"
}
],
"pagination": {
"next_key": null,
"total": "2"
}
}
Pagination in REST
# Request with pagination
curl "http://localhost:1317/cosmos/bank/v1beta1/balances/cosmos1...?pagination.limit=10&pagination.offset=0"
# Using page key
curl "http://localhost:1317/cosmos/bank/v1beta1/balances/cosmos1...?pagination.key=base64_encoded_key"
# Response includes pagination info
{
"balances": [...],
"pagination": {
"next_key": "base64_encoded_next_key",
"total": "100"
}
}
Broadcasting Transactions
# Broadcast a signed transaction
curl -X POST http://localhost:1317/cosmos/tx/v1beta1/txs \
-H "Content-Type: application/json" \
-d '{
"tx_bytes": "base64_encoded_tx",
"mode": "BROADCAST_MODE_SYNC"
}'
# Response
{
"tx_response": {
"height": "0",
"txhash": "ABC123...",
"codespace": "",
"code": 0,
"data": "",
"raw_log": "[]",
"logs": [],
"info": "",
"gas_wanted": "200000",
"gas_used": "0",
"tx": null,
"timestamp": ""
}
}
Simulating Transactions
# Simulate transaction before broadcasting
curl -X POST http://localhost:1317/cosmos/tx/v1beta1/simulate \
-H "Content-Type: application/json" \
-d '{
"tx_bytes": "base64_encoded_tx"
}'
# Response
{
"gas_info": {
"gas_wanted": "0",
"gas_used": "123456"
},
"result": {
"data": "",
"log": "",
"events": []
}
}
Query Transaction by Hash
# Get transaction by hash
curl http://localhost:1317/cosmos/tx/v1beta1/txs/ABC123...
# Response
{
"tx": {
"body": {
"messages": [...],
"memo": "",
"timeout_height": "0",
"extension_options": [],
"non_critical_extension_options": []
},
"auth_info": {...},
"signatures": [...]
},
"tx_response": {
"height": "12345",
"txhash": "ABC123...",
"code": 0,
"logs": [...],
"gas_wanted": "200000",
"gas_used": "123456"
}
}
Custom REST Endpoints
Legacy REST Registration
For custom endpoints not covered by gRPC-Gateway:import (
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client"
)
// Register custom REST routes
func RegisterRoutes(
clientCtx client.Context,
r *mux.Router,
) {
r.HandleFunc(
"/custom/endpoint",
customQueryHandlerFn(clientCtx),
).Methods("GET")
}
func customQueryHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Parse query parameters
vars := mux.Vars(r)
param := vars["param"]
// Query state
queryClient := types.NewQueryClient(clientCtx)
res, err := queryClient.CustomQuery(
r.Context(),
&types.QueryRequest{Param: param},
)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
// Write response
rest.PostProcessResponse(w, clientCtx, res)
}
}
OpenAPI/Swagger Documentation
Enabling Swagger UI
import (
"github.com/rakyll/statik/fs"
_ "github.com/cosmos/cosmos-sdk/client/docs/statik" // statik files
)
func RegisterSwagger(apiSrv *api.Server) {
statikFS, err := fs.New()
if err != nil {
panic(err)
}
staticServer := http.FileServer(statikFS)
apiSrv.Router.PathPrefix("/swagger/").Handler(staticServer)
}
http://localhost:1317/swagger/
Generating OpenAPI Specs
# Generate OpenAPI specifications from proto files
make proto-swagger-gen
# Combine all specs into single file
cd client/docs
statik -src=./swagger-ui -dest=. -f
CORS Configuration
import "github.com/rs/cors"
func setupCORS(handler http.Handler, enableUnsafeCORS bool) http.Handler {
if enableUnsafeCORS {
// Allow all origins (development only!)
return cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"*"},
}).Handler(handler)
}
// Production CORS settings
return cors.New(cors.Options{
AllowedOrigins: []string{
"https://yourdomain.com",
},
AllowedMethods: []string{"GET", "POST"},
AllowedHeaders: []string{"Content-Type"},
}).Handler(handler)
}
Client Usage Examples
JavaScript/TypeScript
import axios from 'axios';
const API_URL = 'http://localhost:1317';
// Query balance
async function getBalance(address: string, denom: string) {
const response = await axios.get(
`${API_URL}/cosmos/bank/v1beta1/balances/${address}/by_denom`,
{ params: { denom } }
);
return response.data.balance;
}
// Query all balances with pagination
async function getAllBalances(address: string, limit: number = 100) {
let allBalances = [];
let nextKey = null;
do {
const params = {
'pagination.limit': limit.toString(),
};
if (nextKey) {
params['pagination.key'] = nextKey;
}
const response = await axios.get(
`${API_URL}/cosmos/bank/v1beta1/balances/${address}`,
{ params }
);
allBalances = allBalances.concat(response.data.balances);
nextKey = response.data.pagination?.next_key;
} while (nextKey);
return allBalances;
}
// Broadcast transaction
async function broadcastTx(txBytes: string) {
const response = await axios.post(
`${API_URL}/cosmos/tx/v1beta1/txs`,
{
tx_bytes: txBytes,
mode: 'BROADCAST_MODE_SYNC'
}
);
return response.data.tx_response;
}
Python
import requests
import base64
API_URL = "http://localhost:1317"
def get_balance(address: str, denom: str):
response = requests.get(
f"{API_URL}/cosmos/bank/v1beta1/balances/{address}/by_denom",
params={"denom": denom}
)
response.raise_for_status()
return response.json()["balance"]
def broadcast_tx(tx_bytes: bytes):
tx_bytes_b64 = base64.b64encode(tx_bytes).decode('utf-8')
response = requests.post(
f"{API_URL}/cosmos/tx/v1beta1/txs",
json={
"tx_bytes": tx_bytes_b64,
"mode": "BROADCAST_MODE_SYNC"
}
)
response.raise_for_status()
return response.json()["tx_response"]