Skip to main content


counterparty-lib provides a JSON RPC 2.0-based API based off of that of Bitcoin Core. It is the primary means by which other applications should interact with the Counterparty network.

The API server is started either through the CLI interface or with the counterparty-lib Python library. It listens on port 4000 by default (14000 for testnet) and requires HTTP Basic Authentication to connect.

The API includes numerous information retrieval methods, most of which begin with get_, as well as several create_ methods which create new Counterparty transactions. While the get_ type methods simply return the requested information, the create_ methods return unsigned raw transactions which must then be signed and broadcast on the Bitcoin network. This means that while counterparty-server requires Bitcoin Core and uses it for retieval and parsing of blockchain data, it and this API do not require Bitcoin Core's wallet functionality for private key storage and transaction signing. Transaction signing and broadcast can thus be accomplished using whatever means the developer sees fit (including using Bitcoin core's APIs if desired, or a library like Bitcore, or a service like, and so on).

In addition to the JSON RPC API, counterparty-lib provides a complementary RESTful API also based off of that of Bitcoin Core's design. This REST API is still under development and will include more functionality in the future, and listens on the same port as JSON RPC one.

Getting Started

By default, the server will listen on port 4000 (if on mainnet) or port 14000 (on testnet) for API requests.

Note that the main API is built on JSON-RPC 2.0, not 1.1. JSON-RPC itself is pretty lightweight, and API requests are made via a HTTP POST request to /api/ (note the trailing slash), with JSON-encoded data passed as the POST body.

The requests to the secondary REST API are made via HTTP GET to /rest/, with request action and parameters encoded in the URL.

General Format


All requests must have POST data that is JSON encoded. Here's an example of the POST data for a valid API request:

"method": "get_sends",
"params": {"order_by": "tx_hash",
"order_dir": "asc",
"start_block": 280537,
"end_block": 280539},
"jsonrpc": "2.0",
"id": 0

The jsonrpc and id properties are requirements under the JSON-RPC 2.0 spec.

You should note that the data in params is a JSON object (e.g. mapping), not an array. In other words, the API only supports named arguments, not positional arguments (e.g. use {"argument1": "value1", "argument2": "value2"} instead of ["value1", "value2"]). This is the case for safety and bug-minimization reasons.

For more information on JSON RPC, please see the JSON RPC 2.0 specification.


For REST API all requests are made via GET where query-specific arguments are encoded as URL parameters. Moreover, the same requests can be passed via HTTP POST in order to encrypt the transaction parameters. There are only two methods supported: get and compose. The URL formats are as follows, respectively: /rest/<table_name>/get?<filters>&op=<operator> /rest/<message_type>/compose?<transaction arguments>


The API support HTTP basic authentication to use, which is enabled if and only if a password is set. The default user is 'rpc'.

Example Implementations for JSON RPC API

The following examples have authentication enabled and the user set to its default value of 'rpc'. The password is not set (default: 'rpc'). Ensure these values correspond to values in your counterparty-server's configuration file 'server.conf'.

Submissions of examples in additional languages are welcome!


import json
import requests
from requests.auth import HTTPBasicAuth

url = "http://localhost:4000/api/"
headers = {'content-type': 'application/json'}
auth = HTTPBasicAuth('rpc', PASSWORD)

payload = {
"method": "get_running_info",
"params": {},
"jsonrpc": "2.0",
"id": 0
response =, data=json.dumps(payload), headers=headers, auth=auth)
print("Response: ", response.text)


With PHP, you use the JsonRPC library.

require 'JsonRPC/src/JsonRPC/Client.php';
use JsonRPC\Client;
$client = new Client('http://localhost:4000/api/');
$client->authentication('rpc', PASSWORD);

$result = $client->execute('get_balances', array('filters' => array('field' => 'address', 'op' => '==', 'value' => '1NFeBp9s5aQ1iZ26uWyiK2AYUXHxs7bFmB')));
print("get_balances result:\n");

$result2 = $client->execute('get_running_info');
print("get_running_info result:\n");


Remember to surround non-numeric parameter values with the double quotes, as per JSON-RPC 2.0 examples. For example, "order_by": "tx_hash" is correct and will work, "order_by": 'tx_hash' won't.


curl -X POST --user rpc:$PASSWORD -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{ "jsonrpc": "2.0", "id": 0, "method": "get_running_info" }'


On Windows, depending on implementation the above curl command may need to be formatted differently due to problems that Windows has with escapes. For example this particular format was found to work with curl 7.50.1 (x86_64-w64-mingw32) on Windows 10 (x64).

curl -X POST --user rpc:$PASSWORD -H "Content-Type: application/json; charset=UTF-8" -H "Accept: application/json, text/javascript" --data-binary "{ \"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"get_running_info\" }"

c# (RestSharp)

Authorization string in the example below is based on the default username/password.

var client = new RestClient("");
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("authorization", "Basic cnBjOjEyMzQ=");
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\r\n \"method\": \"get_running_info\",\r\n \"params\": {},\r\n \"jsonrpc\": \"2.0\",\r\n \"id\": 1\r\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);


Authorization string in the example below is based on the default username/password.

package main

import (

func main() {

url := ""

payload := strings.NewReader("{\r\n \"method\": \"get_running_info\",\r\n \"params\": {},\r\n \"jsonrpc\": \"2.0\",\r\n \"id\": 1\r\n}")

req, _ := http.NewRequest("POST", url, payload)

req.Header.Add("content-type", "application/json")
req.Header.Add("authorization", "Basic cnBjOjEyMzQ=")
req.Header.Add("cache-control", "no-cache")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)



Ruby (Net::HTTP)

Authorization string in the example below is based on the default username/password.

require 'uri'
require 'net/http'

url = URI("")

http =, url.port)

request =
request["content-type"] = 'application/json'
request["authorization"] = 'Basic cnBjOjEyMzQ='
request["cache-control"] = 'no-cache'
request.body = "{\r\n \"method\": \"get_running_info\",\r\n \"params\": {},\r\n \"jsonrpc\": \"2.0\",\r\n \"id\": 1\r\n}"

response = http.request(request)
puts response.read_body

Example Implementations for REST API

The following examples don't use authentication as with default settings.


import requests

url = "http://localhost:4000/rest/"
headers = {'content-type': 'application/json'}

query = 'sends/get?source=mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc&destination=mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns&op=AND'

response = requests.get(url + query, headers=headers)
print("Response: ", response.text)


These examples use the default username/password combination in URL.


curl "http://rpc:rpc@" -H "Content-Type: application/json; charset=UTF-8" -H "Accept: application/json"


This example was created with curl 7.50.1 (x86_64-w64-mingw32) on Windows 10. For POST encryption add '-X POST'.

curl "http://rpc:rpc@" -H "Content-Type: application/json; charset=UTF-8" -H "Accept: application/json"

Example Parameters

  • Fetch all balances for all assets for both of two addresses, using keyword-based arguments

      payload = {
    "method": "get_balances",
    "params": {
    "filters": [{"field": "address", "op": "==", "value": "14qqz8xpzzEtj6zLs3M1iASP7T4mj687yq"},
    {"field": "address", "op": "==", "value": "1bLockjTFXuSENM8fGdfNUaWqiM4GPe7V"}],
    "filterop": "or"
    "jsonrpc": "2.0",
    "id": 0
  • Get all burns between blocks 280537 and 280539 where greater than .2 BTC was burned, sorting by tx_hash (ascending order)

      payload = {
    "method": "get_burns",
    "params": {
    "filters": {"field": "burned", "op": ">", "value": 20000000},
    "filterop": "AND",
    "order_by": "tx_hash",
    "order_dir": "asc",
    "start_block": 280537,
    "end_block": 280539
    "jsonrpc": "2.0",
    "id": 0
  • Fetch all debits for > 2 XCP between blocks 280537 and 280539, sorting the results by quantity (descending order)

      payload = {
    "method": "get_debits",
    "params": {
    "filters": [{"field": "asset", "op": "==", "value": "XCP"},
    {"field": "quantity", "op": ">", "value": 200000000}],
    "filterop": "AND",
    "order_by": "quantity",
    "order_dir": "desc"
    "jsonrpc": "2.0",
    "id": 0
  • Send 1 XCP (specified in satoshis) from one address to another.

      payload = {
    "method": "create_send",
    "params": {
    "source": "1CUdFmgK9trTNZHALfqGvd8d6nUZqH2AAf",
    "destination": "17rRm52PYGkntcJxD2yQF9jQqRS4S2nZ7E",
    "asset": "XCP",
    "quantity": 100000000
    "jsonrpc": "2.0",
    "id": 0
  • Issuance (indivisible)

      payload = {
    "method": "create_issuance",
    "params": {
    "source": "1CUdFmgK9trTNZHALfqGvd8d6nUZqH2AAf",
    "asset": "MYASSET",
    "quantity": 1000,
    "description": "my asset is cool",
    "divisible": false
    "jsonrpc": "2.0",
    "id": 0
  • Transfer asset ownership

      payload = {
    "method": "create_issuance",
    "params": {
    "source": "1CUdFmgK9trTNZHALfqGvd8d6nUZqH2AAf",
    "transfer_destination": "17rRm52PYGkntcJxD2yQF9jQqRS4S2nZ7E",
    "asset": "MYASSET",
    "quantity": 0
    "jsonrpc": "2.0",
    "id": 0
  • Lock asset

      payload = {
    "method": "create_issuance",
    "params": {
    "source": "1CUdFmgK9trTNZHALfqGvd8d6nUZqH2AAf",
    "asset": "MYASSET",
    "quantity": 0,
    "description": "LOCK"
    "jsonrpc": "2.0",
    "id": 0

Signing Transactions Before Broadcasting

Note: Before v9.49.4, the counterparty server API provided an interface to Bitcoin Core's signing functionality through the do_*, sign_tx and broadcast_tx methods, which have all since been removed.

All create_ API calls return an unsigned raw transaction serialization as a hex-encoded string (i.e. the same format that bitcoind returns with its raw transaction API calls). This raw transaction's inputs may be validated and then must be signed (i.e. via Bitcoin Core, a 3rd party Bitcoin library like Bitcore, etc) and broadcast on the Bitcoin network.

The process of signing and broadcasting a transaction, from start to finish, depends somewhat on the wallet software used. Below are examples of how one might use a wallet to sign and broadcast an unsigned Counterparty transaction created with this API.

Bitcoin Core with Python

#! /usr/bin/env python3

from counterpartylib.lib import util
from counterpartylib.lib import config
from counterpartylib.lib.backend import addrindex

config.TESTNET =
config.RPC =
config.BACKEND_URL =

def counterparty_api(method, params):
return util.api(method, params)

def bitcoin_api(method, params):
return addrindex.rpc(method, params)

def do_send(source, destination, asset, quantity, fee, encoding):
validateaddress = bitcoin_api('validateaddress', [source])
assert validateaddress['ismine']
pubkey = validateaddress['pubkey']
unsigned_tx = counterparty_api('create_send', {'source': source, 'destination': destination, 'asset': asset, 'quantity': quantity, 'pubkey': pubkey, 'allow_unconfirmed_inputs': True})
signed_tx = bitcoin_api('signrawtransaction', [unsigned_tx])['hex']
tx_hash = bitcoin_api('sendrawtransaction', [signed_tx])
return tx_hash

Bitcoin Core with Javascript (Utilizing the Counterwallet Bitcore wrapper code for brevity.)

<script src=""></script>
<script src=""></script>
<script src=""></script>
counterparty_api = function(method, params) {
// call Counterparty API method via your prefered method

bitcoin_api = function(method, params) {
// call Bitcoin Core API method via your prefered method

// generate a passphrase
var m = new Mnemonic(128); //128 bits of entropy (12 word passphrase)
var words = m.toWords();
var passphrase = words.join(' ')

// generate private key, public key and address from the passphrase
wallet = new CWHierarchicalKey(passphrase);
var cwk = wallet.getAddressKey(i); // i the number of the address
var source = key.getAddress();
var pubkey = cwk.getPub()

// generate unsigned transaction
unsigned_hex = counterparty_api('create_send', {'source': source, 'destination': destination, 'asset': asset, 'quantity': quantity, 'pubkey': pubkey})

CWBitcore.signRawTransaction2(self.unsignedTx(), cwk, function(signedHex) {
bitcoin_api('sendrawtransaction', signedHex)

Bitcoinjs-lib on javascript, signing a P2SH redeeming transaction

// Assumes NodeJS runtime. Several libraries exist to replace the Buffer class on web browsers
const bitcoin = require('bitcoinjs-lib')

async function signP2SHDataTX(wif, txHex) {
const network = bitcoin.networks.testnet // Change appropiately to your used network
const keyPair = bitcoin.ECPair.fromWIF(wif, network)
const dataTx = bitcoin.Transaction.fromHex(txHex) // The unsigned second part of the 2 part P2SH transactions

const sigType = bitcoin.Transaction.SIGHASH_ALL // This shouldn't be changed unless you REALLY know what you're doing

for (let i=0; i < dataTx.ins.length; i++) {
const sigHash = dataTx.hashForSignature(i, bitcoin.script.decompile(dataTx.ins[i].script)[0], sigType)
const sig = keyPair.sign(sigHash)
const encodedSig = bitcoin.script.signature.encode(sig, sigType)
const compiled = bitcoin.script.compile([encodedSig])

dataTx.ins[i].script = Buffer.concat([compiled, dataTx.ins[i].script])

dataTx.ins[0].script = Buffer.concat([compiled, dataTx.ins[0].script])
return dataTx.toHex() // The resulting signed transaction in raw hex, ready to be broadcasted

Terms & Conventions


Everywhere in the API an asset is referenced by its name, not its ID. See the Counterparty protocol specification for what constitutes a valid asset name. Examples:

  • "BTC"
  • "XCP"
  • "FOOBAR"
  • "A7736697071037023001"


See the Counterparty protocol specification for what constitutes a valid subasset name. Examples:

  • "PIZZA.X"
  • "PIZZA.REALLY-long-VALID-Subasset-NAME"

Quantities and balances

Anywhere where an quantity is specified, it is specified in satoshis (if a divisible asset), or as whole numbers (if an indivisible asset). To convert satoshis to floating-point, simply cast to float and divide by 100,000,000.


  • 4381030000 = 43.8103 (if divisible asset)
  • 4381030000 = 4381030000 (if indivisible asset)

NOTE: XCP and BTC themselves are divisible assets.


Floats are ratios or floating point values with six decimal places of precision, used in bets and dividends.


See the Counterparty protocol specification for what constitutes a valid memo. Examples:

  • "for pizza"
  • "1ca6"


Filtering Read API results

The Counterparty API aims to be as simple and flexible as possible. To this end, it includes a straightforward way to filter the results of most Read API to get the data you want, and only that.

For each Read API function that supports it, a filters parameter exists. To apply a filter to a specific data field, specify an object (e.g. dict in Python) as this parameter, with the following members:

  • field: The field to filter on. Must be a valid field in the type of object being returned
  • op: The comparison operation to perform. One of: "==", "!=", ">", "<", ">=", "<=", "IN", "LIKE", "NOT IN", "NOT LIKE"
  • value: The value that the field will be compared against. Must be the same data type as the field is (e.g. if the field is a string, the value must be a string too)

If you want to filter by multiple fields, then you can specify a list of filter objects. To this end, API functions that take filters also take a filterop parameter, which determines how the filters are combined when multiple filters are specified. It defaults to "and", meaning that filters are ANDed togeher (and that any match must satisfy all of them). You can also specify "or" as an alternative setting, which would mean that filters are ORed together, and that any match must satisfy only one of them.

To disable filtering, you can just not specify the filter argument (if using keyword-based arguments), or, if using positional arguments, just pass null or [] (empty list) for the parameter.

For examples of filtering in-use, please see the examples.

NOTE: Note that with strings being compared, operators like >= do a lexigraphic string comparison (which compares, letter to letter, based on the ASCII ordering for individual characters. For more information on the specific comparison logic used, please see this page.