The HTTP Client-Server API

This page assumes you already know an API Root URL for a BigchainDB node or reverse proxy. It should be something like https://example.com:9984 or https://12.34.56.78:9984.

If you set up a BigchainDB node or reverse proxy yourself, and you’re not sure what the API Root URL is, then see the last section of this page for help.

BigchainDB Root URL

If you send an HTTP GET request to the BigchainDB Root URL e.g. http://localhost:9984 or https://example.com:9984 (with no /api/v1/ on the end), then you should get an HTTP response with something like the following in the body:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "_links": {
    "api_v1": "http://example.com:9984/api/v1/",
    "docs": "https://docs.bigchaindb.com/projects/server/en/v0.10.3/"
  },
  "keyring": [
    "6qHyZew94NMmUTYyHnkZsB8cxJYuRNEiEpXHe1ih9QX3",
    "AdDuyrTyjrDt935YnFu4VBCVDhHtY2Y6rcy7x2TFeiRi"
  ],
  "public_key": "NC8c8rYcAhyKVpx1PCV65CBmyq4YUbLysy3Rqrg8L8mz",
  "software": "BigchainDB",
  "version": "0.10.3"
}

API Root Endpoint

If you send an HTTP GET request to the API Root Endpoint e.g. http://localhost:9984/api/v1/ or https://example.com:9984/api/v1/, then you should get an HTTP response that allows you to discover the BigchainDB API endpoints:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "_links": {
    "docs": "https://docs.bigchaindb.com/projects/server/en/v0.10.3/http-client-server-api.html",
    "self": "http://example.com:9984/api/v1/",
    "statuses": "http://example.com:9984/api/v1/statuses/",
    "streams_v1": "ws://example.com:9985/api/v1/streams/valid_tx",
    "transactions": "http://example.com:9984/api/v1/transactions/"
  }
}

Transactions

GET /api/v1/transactions/{tx_id}

Get the transaction with the ID tx_id.

This endpoint returns a transaction if it was included in a VALID block, if it is still waiting to be processed (BACKLOG) or is still in an undecided block (UNDECIDED). All instances of a transaction in invalid blocks are ignored and treated as if they don’t exist. If a request is made for a transaction and instances of that transaction are found only in invalid blocks, then the response will be 404 Not Found.

Parameters:
  • tx_id (hex string) – transaction ID

Example request:

GET /api/v1/transactions/04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "asset": {
    "data": {
      "msg": "Hello BigchainDB!"
    }
  },
  "id": "04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd",
  "inputs": [
    {
      "fulfillment": "cf:4:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5waJ_J9gSpMu3q4p4VdDO8BMroPZTaI01B2eWbvR59_yr-WZOjxVDfmrtLGSfiqsYEPdaeTS3KAMZ2Mt_jv8AMH",
      "fulfills": null,
      "owners_before": [
        "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
      ]
    }
  ],
  "metadata": {
    "sequence": 0
  },
  "operation": "CREATE",
  "outputs": [
    {
      "amount": 1,
      "condition": {
        "details": {
          "bitmask": 32,
          "public_key": "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD",
          "signature": null,
          "type": "fulfillment",
          "type_id": 4
        },
        "uri": "cc:4:20:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5w:96"
      },
      "public_keys": [
        "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
      ]
    }
  ],
  "version": "0.10"
}
Response Headers:
 
Status Codes:
  • 200 OK – A transaction with that ID was found.
  • 404 Not Found – A transaction with that ID was not found.
GET /api/v1/transactions

The unfiltered /api/v1/transactions endpoint without any query parameters returns a status code 400. For valid filters, see the sections below.

There are however filtered requests that might come of use, given the endpoint is queried correctly. Some of them include retrieving a list of transactions that include:

In this section, we’ve listed those particular requests, as they will likely to be very handy when implementing your application on top of BigchainDB.

Note

Looking up transactions with a specific metadata field is currently not supported, however, providing a way to query based on metadata data is on our roadmap.

A generalization of those parameters follows:

Query Parameters:
 
  • asset_id (string) – The ID of the asset.
  • operation (string) – (Optional) One of the two supported operations of a transaction: CREATE, TRANSFER.
GET /api/v1/transactions?asset_id={asset_id}&operation={CREATE|TRANSFER}

Get a list of transactions that use an asset with the ID asset_id. Every TRANSFER transaction that originates from a CREATE transaction with asset_id will be included. This allows users to query the entire history or provenance of an asset.

This endpoint returns transactions only if they are decided VALID by the server.

Query Parameters:
 
  • operation (string) – (Optional) One of the two supported operations of a transaction: CREATE, TRANSFER.
  • asset_id (string) – asset ID.

Example request:

GET /api/v1/transactions?operation=TRANSFER&asset_id=04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

[{
  "asset": {
    "id": "04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd"
  },
  "id": "7eb94bc4114e221f9d9f24f5930c9bd3722b4e226ffc267e40fd3739a2a09670",
  "inputs": [
    {
      "fulfillment": "cf:4:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5w6if-1q8bn4mVvF1Q53cnrV62_1a4x2kTXF0q3f1ublGpv7XrvIid2W18UJx_xFtlKbyMwFc7HY44vee49hEwJ",
      "fulfills": {
        "output": 0,
        "txid": "04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd"
      },
      "owners_before": [
        "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
      ]
    }
  ],
  "metadata": {
    "sequence": 1
  },
  "operation": "TRANSFER",
  "outputs": [
    {
      "amount": 1,
      "condition": {
        "details": {
          "bitmask": 32,
          "public_key": "3yfQPHeWAa1MxTX9Zf9176QqcpcnWcanVZZbaHb8B3h9",
          "signature": null,
          "type": "fulfillment",
          "type_id": 4
        },
        "uri": "cc:4:20:LDtSX5zaUbo0VsencVspELt-K9Bw0Y7sZLjBXcD7VCA:96"
      },
      "public_keys": [
        "3yfQPHeWAa1MxTX9Zf9176QqcpcnWcanVZZbaHb8B3h9"
      ]
    }
  ],
  "version": "0.10"
},
{
  "asset": {
    "id": "04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd"
  },
  "id": "5db2aaa9f0b0c29f5edab2a89fa46bd213890551e9e607e66e903c406a1a804b",
  "inputs": [
    {
      "fulfillment": "cf:4:LDtSX5zaUbo0VsencVspELt-K9Bw0Y7sZLjBXcD7VCD586DqUyvuUOj9DiRqoAfbrS-XX_PkE-drJ9RXvtuCtxhcg16T0aCAi4_WQBanUfPccanb-RgTu3D6UEgYsNAP",
      "fulfills": {
        "output": 0,
        "txid": "7eb94bc4114e221f9d9f24f5930c9bd3722b4e226ffc267e40fd3739a2a09670"
      },
      "owners_before": [
        "3yfQPHeWAa1MxTX9Zf9176QqcpcnWcanVZZbaHb8B3h9"
      ]
    }
  ],
  "metadata": {
    "sequence": 2
  },
  "operation": "TRANSFER",
  "outputs": [
    {
      "amount": 1,
      "condition": {
        "details": {
          "bitmask": 32,
          "public_key": "3Af3fhhjU6d9WecEM9Uw5hfom9kNEwE7YuDWdqAUssqm",
          "signature": null,
          "type": "fulfillment",
          "type_id": 4
        },
        "uri": "cc:4:20:IDCer8T9kVLE8s13_Jc8SbzW3Cq9Jv_MgnLYYFQXXjQ:96"
      },
      "public_keys": [
        "3Af3fhhjU6d9WecEM9Uw5hfom9kNEwE7YuDWdqAUssqm"
      ]
    }
  ],
  "version": "0.10"
}]
Response Headers:
 
Status Codes:
  • 200 OK – A list of transactions containing an asset with ID asset_id was found and returned.
  • 400 Bad Request – The request wasn’t understood by the server, e.g. the asset_id querystring was not included in the request.
POST /api/v1/transactions

Push a new transaction.

Note

The posted transaction should be structurally valid and not spending an already spent output. The steps to build a valid transaction are beyond the scope of this page. One would normally use a driver such as the BigchainDB Python Driver to build a valid transaction.

Example request:

POST /api/v1/transactions/ HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "asset": {
    "data": {
      "msg": "Hello BigchainDB!"
    }
  },
  "id": "04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd",
  "inputs": [
    {
      "fulfillment": "cf:4:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5waJ_J9gSpMu3q4p4VdDO8BMroPZTaI01B2eWbvR59_yr-WZOjxVDfmrtLGSfiqsYEPdaeTS3KAMZ2Mt_jv8AMH",
      "fulfills": null,
      "owners_before": [
        "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
      ]
    }
  ],
  "metadata": {
    "sequence": 0
  },
  "operation": "CREATE",
  "outputs": [
    {
      "amount": 1,
      "condition": {
        "details": {
          "bitmask": 32,
          "public_key": "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD",
          "signature": null,
          "type": "fulfillment",
          "type_id": 4
        },
        "uri": "cc:4:20:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5w:96"
      },
      "public_keys": [
        "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
      ]
    }
  ],
  "version": "0.10"
}

Example response:

HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "asset": {
    "data": {
      "msg": "Hello BigchainDB!"
    }
  },
  "id": "04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd",
  "inputs": [
    {
      "fulfillment": "cf:4:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5waJ_J9gSpMu3q4p4VdDO8BMroPZTaI01B2eWbvR59_yr-WZOjxVDfmrtLGSfiqsYEPdaeTS3KAMZ2Mt_jv8AMH",
      "fulfills": null,
      "owners_before": [
        "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
      ]
    }
  ],
  "metadata": {
    "sequence": 0
  },
  "operation": "CREATE",
  "outputs": [
    {
      "amount": 1,
      "condition": {
        "details": {
          "bitmask": 32,
          "public_key": "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD",
          "signature": null,
          "type": "fulfillment",
          "type_id": 4
        },
        "uri": "cc:4:20:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5w:96"
      },
      "public_keys": [
        "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
      ]
    }
  ],
  "version": "0.10"
}
Response Headers:
 
Status Codes:
  • 202 Accepted – The pushed transaction was accepted in the BACKLOG, but the processing has not been completed.
  • 400 Bad Request – The transaction was malformed and not accepted in the BACKLOG.

Transaction Outputs

The /api/v1/outputs endpoint returns transactions outputs filtered by a given public key, and optionally filtered to only include outputs that have not already been spent.

GET /api/v1/outputs?public_key={public_key}

Get transaction outputs by public key. The public_key parameter must be a base58 encoded ed25519 public key associated with transaction output ownership.

Returns a list of links to transaction outputs.

Parameters:
  • public_key – Base58 encoded public key associated with output ownership. This parameter is mandatory and without it the endpoint will return a 400 response code.
  • unspent – Boolean value (“true” or “false”) indicating if the result set should be limited to outputs that are available to spend. Defaults to “false”.

Example request:

GET /api/v1/outputs?public_key=1AAAbbb...ccc HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

[
  "../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/0",
  "../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/1"
]
Status Codes:
  • 200 OK – A list of outputs were found and returned in the body of the response.
  • 400 Bad Request – The request wasn’t understood by the server, e.g. the public_key querystring was not included in the request.

Statuses

GET /api/v1/statuses

Get the status of an asynchronously written transaction or block by their id.

A link to the resource is also provided in the returned payload under _links.

Query Parameters:
 
  • tx_id (string) – transaction ID
  • block_id (string) – block ID

Note

Exactly one of the tx_id or block_id query parameters must be used together with this endpoint (see below for getting transaction statuses and block statuses).

GET /api/v1/statuses?tx_id={tx_id}

Get the status of a transaction.

The possible status values are undecided, valid or backlog. If a transaction in neither of those states is found, a 404 Not Found HTTP status code is returned. We’re currently looking into ways to unambigously let the user know about a transaction’s status that was included in an invalid block.

Example request:

GET /statuses?tx_id=04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "valid",
  "_links": {
    "tx": "/transactions/04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd"
  }
}
Response Headers:
 
  • Content-Typeapplication/json
  • Location – Once the transaction has been persisted, this header will link to the actual resource.
Status Codes:
  • 200 OK – A transaction with that ID was found.
  • 404 Not Found – A transaction with that ID was not found.
GET /api/v1/statuses?block_id={block_id}

Get the status of a block.

The possible status values are undecided, valid or invalid.

Example request:

GET /api/v1/statuses?block_id=71d0864a83f42e1770a09c2fd7a8675fe8c88c81ca8f2daff7094b0ad38b174f HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "invalid"
}

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "valid",
  "_links": {
    "block": "/blocks/71d0864a83f42e1770a09c2fd7a8675fe8c88c81ca8f2daff7094b0ad38b174f"
  }
}
Response Headers:
 
  • Content-Typeapplication/json
  • Location – Once the block has been persisted, this header will link to the actual resource.
Status Codes:
  • 200 OK – A block with that ID was found.
  • 404 Not Found – A block with that ID was not found.

Advanced Usage

The following endpoints are more advanced and meant for debugging and transparency purposes.

More precisely, the blocks endpoint allows you to retrieve a block by block_id as well the list of blocks that a certain transaction with tx_id occured in (a transaction can occur in multiple invalid blocks until it either gets rejected or validated by the system). This endpoint gives the ability to drill down on the lifecycle of a transaction

The votes endpoint contains all the voting information for a specific block. So after retrieving the block_id for a given tx_id, one can now simply inspect the votes that happened at a specific time on that block.

Blocks

GET /api/v1/blocks/{block_id}

Get the block with the ID block_id. Any blocks, be they VALID, UNDECIDED or INVALID will be returned. To check a block’s status independently, use the Statuses endpoint. To check the votes on a block, have a look at the votes endpoint.

Parameters:
  • block_id (hex string) – block ID

Example request:

GET /api/v1/blocks/71d0864a83f42e1770a09c2fd7a8675fe8c88c81ca8f2daff7094b0ad38b174f HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "block": {
    "node_pubkey": "DngBurxfeNVKZWCEcDnLj1eMPAS7focUZTE5FndFGuHT",
    "timestamp": "1498743009",
    "transactions": [
      {
        "asset": {
          "data": {
            "msg": "Hello BigchainDB!"
          }
        },
        "id": "04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd",
        "inputs": [
          {
            "fulfillment": "cf:4:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5waJ_J9gSpMu3q4p4VdDO8BMroPZTaI01B2eWbvR59_yr-WZOjxVDfmrtLGSfiqsYEPdaeTS3KAMZ2Mt_jv8AMH",
            "fulfills": null,
            "owners_before": [
              "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
            ]
          }
        ],
        "metadata": {
          "sequence": 0
        },
        "operation": "CREATE",
        "outputs": [
          {
            "amount": 1,
            "condition": {
              "details": {
                "bitmask": 32,
                "public_key": "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD",
                "signature": null,
                "type": "fulfillment",
                "type_id": 4
              },
              "uri": "cc:4:20:MTmLrdyfhfxPw3WxnaYaQkPmU1GcEzg9mAj_O_Nuv5w:96"
            },
            "public_keys": [
              "4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD"
            ]
          }
        ],
        "version": "0.10"
      }
    ],
    "voters": [
      "DngBurxfeNVKZWCEcDnLj1eMPAS7focUZTE5FndFGuHT"
    ]
  },
  "id": "71d0864a83f42e1770a09c2fd7a8675fe8c88c81ca8f2daff7094b0ad38b174f",
  "signature": "53wxrEQDYk1dXzmvNSytbCfmNVnPqPkDQaTnAe8Jf43s6ssejPxezkCvUnGTnduNUmaLjhaan1iRLi3peu6s5DzA"
}
Response Headers:
 
Status Codes:
  • 200 OK – A block with that ID was found.
  • 400 Bad Request – The request wasn’t understood by the server, e.g. just requesting /blocks without the block_id.
  • 404 Not Found – A block with that ID was not found.
GET /api/v1/blocks

The unfiltered /blocks endpoint without any query parameters returns a 400 status code. The list endpoint should be filtered with a tx_id query parameter, see the /blocks?tx_id={tx_id}&status={UNDECIDED|VALID|INVALID} endpoint.

Example request:

GET /api/v1/blocks HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 400 Bad Request
Status Codes:
  • 400 Bad Request – The request wasn’t understood by the server, e.g. just requesting /blocks without the block_id.
GET /api/v1/blocks?tx_id={tx_id}&status={UNDECIDED|VALID|INVALID}

Retrieve a list of block_id with their corresponding status that contain a transaction with the ID tx_id.

Any blocks, be they UNDECIDED, VALID or INVALID will be returned if no status filter is provided.

Note

In case no block was found, an empty list and an HTTP status code 200 OK is returned, as the request was still successful.

Query Parameters:
 
  • tx_id (string) – transaction ID (required)
  • status (string) – Filter blocks by their status. One of VALID, UNDECIDED or INVALID.

Example request:

GET /api/v1/blocks?tx_id=04c00267af82c161b4bf2ad4a47d1ddbfeb47eef1a14b8d51f37d6ee00ea5cdd HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

[
  "8e9f4613fc7c108ba112effcce5909aa110acefd8cfe60358de8549f1200e244",
  "71d0864a83f42e1770a09c2fd7a8675fe8c88c81ca8f2daff7094b0ad38b174f"
]
Response Headers:
 
Status Codes:
  • 200 OK – A list of blocks containing a transaction with ID tx_id was found and returned.
  • 400 Bad Request – The request wasn’t understood by the server, e.g. just requesting /blocks, without defining tx_id.

Votes

GET /api/v1/votes?block_id={block_id}

Retrieve a list of votes for a certain block with ID block_id. To check for the validity of a vote, a user of this endpoint needs to perform the following steps:

  1. Check if the vote’s node_pubkey is allowed to vote.
  2. Verify the vote’s signature against the vote’s body (vote.vote) and node_pubkey.
Query Parameters:
 
  • block_id (string) – The block ID to filter the votes.

Example request:

GET /api/v1/votes?block_id=71d0864a83f42e1770a09c2fd7a8675fe8c88c81ca8f2daff7094b0ad38b174f HTTP/1.1
Host: example.com

Example response:

HTTP/1.1 200 OK
Content-Type: application/json

[{
  "node_pubkey": "DngBurxfeNVKZWCEcDnLj1eMPAS7focUZTE5FndFGuHT",
  "signature": "2Qbe9Z3UsJk8oSuvafMwEwGWzR66WhrgDvJT4qYTzcV6PpSsCQKzP3kSAbB1Uxc8qT5zRVaUnVU68eJMv1CBEApS",
  "vote": {
    "invalid_reason": null,
    "is_block_valid": true,
    "previous_block": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    "timestamp": "1498743009",
    "voting_for_block": "71d0864a83f42e1770a09c2fd7a8675fe8c88c81ca8f2daff7094b0ad38b174f"
  }
}]
Response Headers:
 
Status Codes:
  • 200 OK – A list of votes voting for a block with ID block_id was found and returned.
  • 400 Bad Request – The request wasn’t understood by the server, e.g. just requesting /votes, without defining block_id.

Determining the API Root URL

When you start BigchainDB Server using bigchaindb start, an HTTP API is exposed at some address. The default is:

http://localhost:9984/api/v1/

It’s bound to localhost, so you can access it from the same machine, but it won’t be directly accessible from the outside world. (The outside world could connect via a SOCKS proxy or whatnot.)

The documentation about BigchainDB Server Configuration Settings has a section about how to set server.bind so as to make the HTTP API publicly accessible.

If the API endpoint is publicly accessible, then the public API Root URL is determined as follows:

  • The public IP address (like 12.34.56.78) is the public IP address of the machine exposing the HTTP API to the public internet (e.g. either the machine hosting Gunicorn or the machine running the reverse proxy such as Nginx). It’s determined by AWS, Azure, Rackspace, or whoever is hosting the machine.
  • The DNS hostname (like example.com) is determined by DNS records, such as an “A Record” associating example.com with 12.34.56.78
  • The port (like 9984) is determined by the server.bind setting if Gunicorn is exposed directly to the public Internet. If a reverse proxy (like Nginx) is exposed directly to the public Internet instead, then it could expose the HTTP API on whatever port it wants to. (It should expose the HTTP API on port 9984, but it’s not bound to do that by anything other than convention.)