Open Payments endpoints
A Dassie node serves Open Payments 1.4.0 as its specs define it: wallet addresses, a GNAP authorization server and a resource server. This page lists the routes and the few things Dassie adds.
To build on these routes, start with Accept payments with Open Payments.
On this page
Where they are served
Every route is under the origin of one of the node's names, and the node writes that origin into every URL it answers with:
-
A node with a hostname answers under that
hostname alone, such as
https://pay.example.com. -
A node without one answers through a front door:
at
https://<node ID>.dassiepay.net, at the names its owner adds inDASSIE_PUBLIC_NAMES, and at<name>.<domain>for each name it holds, such ashttps://alice.dassiepay.net. Those domains aredassiepay.netand the domain of each other front door whose<node ID>.<domain>its owner added, up to four front doors in all. Payment names has more.
A front door reaches the node only while the node keeps a
connection to it, as its relay. By default, a node without a
hostname keeps the dassiepay.net front door, at
relay.dassiepay.net.
DASSIE_RELAY
names another relay, or none. While the node holds no connection
to a front door, that front door answers requests for it with
503.
A request for a name the node doesn't answer to gets
421 with the code misdirected_request.
Before the node is set up, every route answers 404.
Wallet addresses
| Method | Path | What it answers |
|---|---|---|
GET, HEAD |
/.well-known/pay |
The wallet address of the default asset, which the payment
pointer $host names
|
GET, HEAD |
/pay/<asset> |
The wallet address of one asset: xrp,
dsusd, or usd for the stub in
development builds. rlusd belongs to the live
realm, which a node refuses
|
GET, HEAD |
<wallet address>/jwks.json |
The key that signs the node's own requests to other
wallets, such as /pay/xrp/jwks.json
|
There is one wallet address per asset the node is paid in. A wallet address names an asset, never a hub or another place the owner keeps money, so where the owner keeps it never shows in a URL. The default asset is the one the owner chose, or else the first active one. Wallet address documents may be cached for 60 seconds, key sets for 300.
Authorization server
| Method | Path | What it does |
|---|---|---|
POST |
/gnap |
Asks for a grant |
GET |
/gnap/interact/<id>/<key> |
Sends the owner on to the wallet's consent page
(302). A request that is no longer waiting
answers 404 in plain text.
|
POST |
/gnap/continue/<id> |
Continues a grant once the owner has decided |
DELETE |
/gnap/continue/<id> |
Revokes the grant |
POST |
/gnap/token/<id> |
Rotates an access token |
DELETE |
/gnap/token/<id> |
Revokes an access token |
Grants for incoming payments and quotes are approved at once.
Anything that moves the owner's money or shows more than the
client's own resources waits for the owner: outgoing payments, any
list-all, read-all on quotes, and a
subject request. Reading any incoming payment needs no owner,
since a sender's wallet reads one it didn't create to learn how to
pay it. A consent
request lapses after 10 minutes.
Grants and limits covers the
rules, such as which limits a grant may set.
Under a front door's name, grants that wait for the owner can't be
approved yet: the front door answers
/gnap/interact/ itself, with 501 and the
code interaction_unavailable, since the owner's app it
would send the owner to isn't there yet. They work at a node with a
hostname of its own.
What's next
Resource server
| Method | Path | What it does |
|---|---|---|
POST |
/incoming-payments |
Creates an incoming payment |
GET |
/incoming-payments?wallet-address=… |
Lists incoming payments, newest first |
GET, HEAD |
/incoming-payments/<id> |
Reads one. Without a token, anyone gets what it has received and where to get a grant; with a token whose grant may read it, the full payment, and fresh credentials while it still takes money |
POST |
/incoming-payments/<id>/complete |
Completes it, so it takes no more money |
POST |
/quotes |
Measures the route to a receiver, and quotes it |
GET, HEAD |
/quotes/<id> |
Reads a quote |
POST |
/outgoing-payments |
Creates an outgoing payment, from a quote or from an incoming payment with an amount to debit |
GET |
/outgoing-payments?wallet-address=… |
Lists outgoing payments, newest first |
GET, HEAD |
/outgoing-payments/<id> |
Reads one |
GET, HEAD |
/outgoing-payment-grant |
What the token's grant has spent in the current window of its interval |
Each payment method is ilp: STREAM credentials, an ILP
address and a shared secret. Every read of an incoming payment hands
out fresh credentials, so each sender gets a STREAM connection of
its own, and credentials pay one outgoing payment only. An incoming
payment counts as paid in full once what it received is within a
small tolerance of its amount, so receivedAmount can be
a little over incomingAmount.
STREAM on Dassie explains why.
Requests
- Signed as the specs say. Every call the specs protect carries a GNAP token and an HTTP message signature by the client's key. The signature covers the URL at the name the request is answered under, so a request signed for another server never verifies here.
-
Read strictly. Request bodies are JSON
(
application/json) of at most 16 KiB, and unknown fields are refused where the schemas forbid them. -
Open to every origin. The routes check no
Originand answer CORS for any origin, without credentials: a client proves itself by its token and signature, never by a cookie. A preflight allowsGET,HEAD,POST,DELETEandOPTIONS, and the headersAccept,Authorization,Content-Type,Content-Digest,Content-Length,Signature,Signature-InputandDassie-Nonce. -
Rate-limited per client, an IPv4 address or an
IPv6 /56: a burst of 120 requests, then one every 250 ms. Requests
that create something (grants, incoming payments, quotes and
outgoing payments) have a tighter limit on top: a burst of 20,
then one every 3 seconds. Past a limit, the node answers
429. Through a front door, the front door limits each client as the node does for all requests, and to a quarter of that for each node: a burst of 30, then one a second. It sets no limit of its own on creating. The node counts the front door as one client, so every client that comes through it shares the node's budget, its limit on creating included.
Errors
An error has the specs' shape,
{"error": {"code": "…", "description": "…"}}. The
resource server's 401 carries
WWW-Authenticate: GNAP as_uri=…, naming the grant
endpoint; the authorization server's does not.
| Status | Code | When |
|---|---|---|
400 |
invalid_request |
The request is malformed |
401 |
unauthorized |
At the resource server: no usable token or signature, or a grant that isn't approved |
401 |
invalid_client,
invalid_continuation
|
At the authorization server: no usable token or signature, or a continuation the node doesn't know |
403 |
forbidden |
The grant doesn't allow this |
403 |
invalid_request |
An outgoing payment the grant's limits don't leave room for, such as one to another receiver than the grant names, or in another asset than its debit limit |
404 |
not_found |
No such route or resource, or the node isn't set up |
408, 413, 415
|
invalid_request |
The body took too long, was over 16 KiB, or wasn't JSON |
421 |
misdirected_request |
The node doesn't answer to that name |
429 |
too_many_requests |
A rate limit, or too many live grants or quotes from one client |
502 |
unreachable |
A quote's receiver can't be read, has no route, or its path delivers nothing |
503 |
unavailable |
The node isn't ready to answer yet |
Under a front door's name, the front door answers some requests itself, in the same shape. These answers carry no node signature.
| Status | Code | When |
|---|---|---|
404 |
not_found |
No node has that name, or the path isn't an Open Payments route |
421 |
misdirected_request |
The name isn't under the front door's domain, or names a port other than 443 |
429 |
too_many_requests |
The front door's own rate limit |
501 |
interaction_unavailable |
A request for /gnap/interact/ |
502 |
bad_gateway |
The node's answer broke, or wasn't JSON |
503 |
unavailable |
The node holds no connection to the front door, or is busy |
504 |
gateway_timeout |
The node didn't answer in time |
A browser that opens a name at / gets a short page
from the front door instead: the name, the node's ID, whether the
node is connected to the front door now, and its wallet address.
The authorization server also answers with GNAP's other codes where
they fit, such as request_denied,
too_fast and invalid_rotation.
Error codes also covers the
Interledger and STREAM errors a payment can end with.
Dassie's additions
These add to what the specs say, and a client that knows nothing of Dassie can ignore every one of them.
- Signed answers
-
The node signs every answer it gives under a name with its node key, errors included, by RFC 9421. The signature is tagged
dassie, itsalgised25519, and itskeyidis the node's two public keys in base64url. It covers@status, the answer'sContent-Digest(SHA-512), and the request's@method,@target-uriandDassie-Nonce. Send a freshDassie-Nonceheader with your request so an old answer can't be replayed. Only CORS preflights and the refusals of an unknown name or of a node not set up go unsigned. Signed responses and names shows how to check one. - The
dassiefield -
A wallet address document carries
"dassie": {"nodeId": "…"}, the ID of the node whose key signs its answers. The specs' schema allows extra properties, so other clients ignore it. - A wallet address per asset
-
/pay/<asset>, beside/.well-known/payfor the default. Grants and payments record the/pay/<asset>form, so changing the default never moves them to another asset. - One node, several names
-
Each request is answered under the name it came through, and a name the node doesn't hold gets
421, so whoever passes requests on can't make the node sign for a name its owner never chose.
Not supported
- SPSP. Wallets that speak only SPSP can't pay a Dassie node. Payment pointers work as another way to write a wallet address.
- Card authorization: a node has no cards.
- STREAM receipts, which are not part of Open Payments.
Why it works this way and, for each choice the specs leave open, the payments spec.