This specification defines a REST API that can be hosted by an individual PFI that wants to provide liquidity via tbDEX
Version: Draft
Note
This specification will continue to be in a Draft state until there are two separate PFIs deployed and providing liquidity to individuals or other institutions
- Discoverability
- Error Responses
- Query Params
- Idempotency
- Callbacks
- Protected Endpoints
- Offerings
- Exchanges
- References
PFIs can become publicly discoverable by advertising their API endpoint as a Service within their DID Document. In order to increase the likelihood of being discovered The service
entry SHOULD include the following properties:
Property | Value |
---|---|
id |
see DID-CORE spec |
type |
PFI |
serviceEndpoint |
PFI's publicly addressable API endpoint, the base URL with which tbDEX HTTP requests can be made. |
{
"id": "did:example:pfi",
"service": [{
"id":"#my-pfi",
"type": "PFI",
"serviceEndpoint": "https://pfi.organization.com"
}]
}
This example illustrates that:
- The ID can be chosen at the discretion of the PFI, but the service entry should be of type
PFI
. - The
serviceEndpoint
of the PFIhttps://pfi.organization.com
is the PFI's base URL.- The PFI's base URL is combined with relative paths listed below (i.e.
POST /exchanges
) by the client to create an tbDEX HTTP request to the PFI.
- The PFI's base URL is combined with relative paths listed below (i.e.
Note
Decentralized discoverability is dependent upon whether the underlying verifiable registry of the selected DID Method is crawlable
- An error response is one whose status code is
>= 400
. - If present, the body of an error response will conform to the following:
Field | Type | Required | Description |
---|---|---|---|
message |
String | Y | A human-readable explanation specific to this occurrence of the problem. |
details |
ErrorDetail[] | N | Optional array of ErrorDetail objects |
Field | Type | Required | Description |
---|---|---|---|
id |
String | N | Optional server-generated request-specific ID, useful for diagnosing unexpected errors |
message |
String | N | A human-readable explanation specific to this occurrence of the problem. |
path |
String | N | Path where validation failed (i.e. JSON schema path) |
{
"message": "Missing field: payin.amount",
"details": [{
"id": "9af2bf88-e4f4-4f81-8ba9-55eaeeb718e2",
"message": "Payin amount must be present.",
"path": "$.payin.amount"
}]
}
Query parameters, also known as query strings, are a way to send additional information to the server as part of a URL. They allow clients to provide specific input or customize the server's response. Query parameters typically follow the main URL and start with a ?
character. They consist of key-value pairs, and multiple pairs can be separated by &
characters
Query params are supported by many of the GET /${resource}
endpoints in the following ways
- Simple Example:
?field=simpleValue
- Same Field; Multiple Values:
field=value&field=anotherValue
Query Param support is optional for all GET
endpoints that return a list that can be filtered by parameters.
Pagination is supported using the following query params:
Param | Description | Default |
---|---|---|
page[offset] |
Specifies the starting position from where the records should be retrieved. | 0 |
page[limit] |
Specifies the maximum number of records to be retrieved. | 10 |
/?page[offset]=0&page[limit]=10
Pagination support is optional for all GET
endpoints that return a list.
The IDs of individual tbDEX messages are used as idempotency keys. For example, in a tbDEX Order message:
{
"metadata": {
"from": "did:key:z6MkvUm6mZxpDsqvyPnpkeWS4fmXD2jJA3TDKq4fDkmR5BD7",
"to": "did:ex:pfi",
"exchangeId": "rfq_01ha83pkgnfxfv41gpa44ckkpz",
"kind": "order",
"id": "order_01ha83pkgsfk6t1kxg7q42s48j",
"createdAt": "2023-09-13T20:28:40.345Z",
"protocol": "2.0"
},
"data": {},
"signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3ZVbTZtWnhwRHNxdnlQbnBrZVdTNGZtWEQyakpBM1RES3E0ZkRrbVI1QkQ3I3o2TWt2VW02bVp4cERzcXZ5UG5wa2VXUzRmbVhEMmpKQTNUREtxNGZEa21SNUJENyJ9..tWyGAiuUXFuVvq318Kdz-EJJgCPCWEMO9xVMZD9amjdwPS0p12fkaLwu1PSLxHoXPKSyIbPQnGGZayI_v7tPCA"
}
The ID
here that serves as idempotency key is order_01ha83pkgsfk6t1kxg7q42s48j
. Each tbDEX message's idempotency key can be accessed via message.metadata.id
like so:
val order = Order.create(...)
val orderId = order.metadata.id
Callbacks are implemented via the replyTo
property on the request to create an exchange.
replyTo
is a fully qualified URI which could either be a DID or a URL.
If replyTo
is present, a PFI will send any/all new messages for a given exchange to the supplied URI. This makes the URI scoped to each exchange, allowing the caller to specify a different URI per exchange if they so wish.
If replyTo
is not present, the caller will have to poll the PFI for the exchange in question to receive new messages.
A protected endpoint is defined as one that requires the requester to be authenticated. These endpoints respond with resources specific to the authenticated DID e.g.
- Messages sent by or intended for the requester only
- Balances
Note
Each individual endpoint section in this specification will include Protected: true/false
to indicate whether it is protected
When accessing a protected endpoint, a tbdex http client MUST set the HTTP Authorization Header in order to be authenticated.
The value of the header MUST use the Bearer Authentication Scheme as defined in RFC 6750, and the access token MUST be a JWT.
The bearer token is a JSON Web Token (JWT) generated by the requester. It must consist of JWT header and Claims Set containing the below fields, and signed using the verification method published as the authentication verification relationship in the requester's DID document.
Key | Description |
---|---|
typ |
JWT. Setting typ to JWT as recommended by the JWT spec provides a means to disambiguate among different types of signatures and tokens. |
kid |
Fully qualified VerificationMethod ID. Used to locate the DID Document verification method utilized to verify the integrity of the JWT. |
alg |
Cryptographic algorithm used to compute the JWT signature. |
Key | Description |
---|---|
aud |
The intended PFI's DID. Incorporating the aud claim limits the risk to just one PFI in case a request token is compromised, thereby reducing the surface area for misuse. |
iss |
The requester's DID. Included to be informative, though it is technically duplicative as the kid also includes the requester's DID. |
exp |
Expiration timestamp. Limits the amount of time a compromised request token can be used. |
iat |
Indicates when the JWT was created. Included to be informative. |
jti |
Used as a nonce to prevent replay attacks. The specification text should include more detail on how to prevent these attacks. |
Note
This bearer token is sufficient for authentication as it proves that the requester controls the private key associated to the DID that the requester is identifying as.
The receiver of the token must evaluate it to ensure its validity. A bearer token is valid if these conditions are met:
exp
timestamp is not in the pastaud
is the DID of the receiving PFI's DIDkid
is resolved to be a valid DID whose DID Document verification method is used to verify the integrity of the JWTjti
is not a nonce that was previously used by the same requester
The Offering
resource is used to convey the currency pairs a PFI is offering. It includes information about payment methods and associated fees.
Used to fetch offerings from a PFI
GET /offerings
False
Status | Body |
---|---|
200: OK |
{ data: Offering[] } |
400: Bad Request |
{ errors: Error[] } |
An exchange is a series of linked tbDEX messages between Alice and a PFI for a single exchange. An exchange can be created by submitting an RFQ.
Submits an RFQ (Request For Quote). Alice is asking the PFI to provide a Quote so she can evaluate it.
POST /exchanges
False
field | data type | required | description |
---|---|---|---|
message |
object | Y | The request for quote (RFQ) tbDEX message |
replyTo |
string | N | A string containing a valid URI where new messages from the PFI will be sent |
Important
See RFQ structure here
{
"message": {
{
"metadata": {
"from": "did:key:z6Mks4N5XdrE6VieJsgH8SMSRavmTox74RqoroW7bZzBLQBi",
"to": "did:ex:pfi",
"kind": "rfq",
"id": "rfq_01ha835rhefwmagsknrrhvaa0k",
"exchangeId": "rfq_01ha835rhefwmagsknrrhvaa0k",
"createdAt": "2023-09-13T20:19:28.430Z",
"protocol": "2.0"
},
"data": {
"offeringId": "abcd123",
"payinMethod": {
"kind": "DEBIT_CARD",
"paymentDetails": "<HASH_PRIVATE_PAYIN_METHOD_PAYMENT_DETAILS>"
},
// ...
}
}
},
"replyTo": "https://alice.wallet.com/events"
}
Status | Body |
---|---|
202: Accepted |
N/A |
400: Bad Request |
{ errors: Error[] } |
Status | Description |
---|---|
400 | Validation error(s) |
400 | Failed Signature Check |
409 | Exchange already exists |
This endpoint can receive either an Order or a Cancel message. Alice can submit an Order, which indicates that she wants the PFI to execute on the Quote she received. Alice can submit a Cancel, which indicates that Alice is no longer interested in continuing with the exchange.
PUT /exchanges/:exchange_id
False
{
"message": { } // order or cancel message
}
Status | Body |
---|---|
202: Accepted |
N/A |
400: Bad Request |
{ errors: Error[] } |
Status | Description |
---|---|
400 | Failed Signature Check |
404 | Exchange not found |
409 | Order or Cancel not allowed |
Retrieves all messages associated to a specific exchange
GET /exchanges/:exchange_id
True. See Authentication section for more details.
Status | Body |
---|---|
200: OK |
{ data: TbdexMessage[] } |
400: Bad Request |
{ errors: Error[] } |
401: Unauthorized |
{ errors: Error[] } |
404: Not Found |
N/A |
Returns a list of exchange IDs
GET /exchanges
True. See Authentication section for more details.
Status | Body |
---|---|
200: OK |
List of exchangeIds, i.e. { data: string[] } |
400: Bad Request |
{ errors: Error[] } |
401: Unauthorized |
{ errors: Error[] } |
404: Not Found |
N/A |
This endpoint is OPTIONAL. It is only relevant for PFIs which expose a stored balance functionality either for institutional or retail customers.
Returns an array of balance resources for each currency held by the caller.
True. See Authentication section for more details.
GET /balances
Status | Body |
---|---|
200: OK |
{ data: Balance[] } See Balance structure |
400: Bad Request |
{ errors: Error[] } |
401: Unauthorized |
{ errors: Error[] } |
404: Not Found |
N/A |
- JSON:API spec: https://jsonapi.org/format/