API Specification

Modified on Tue, 25 Feb at 1:50 PM

TABLE OF CONTENTS

In this article, we explore the essential components of an API specification, including its structure, endpoints, and data formats.

API Specification

Obtain current exchange rates

The GET /rates request allows the Wallet App to obtain current exchange rates or transaction fees from APS. By accessing this information, the wallet can make an informed decision about proceeding with APS for specific transactions.


Example request:

curl --location 'https://web3.aps.money/gollum/api/v1/rates?direction=on-ramp&blockchain=stellar&asset_in=USD&asset_out=USDC&amount_in=100'

Example response:

{
  "amount_in": "100",
  "amount_out": "95.5",
  "asset_in": "BUSD_BSC_TEST",
  "asset_out": "usd",
  "blockchain": "ethereum",
  "direction": "off-ramp",
  "expires_at": "2024-12-06T12:03:01Z"
}

Response body contains the following elements:


NameTypeDescription
directionstringWhich direction it will be?
blockchainstringWhich blockchain do you intend to use? 
amount_instringHow many tokens do you want to send? 
asset_instringWhich asset do you want to send? Example, USDC
asset_outstringWhich asset do you want to receive? Example, USD


Response error examples:

CodeDescription
400
bad request
401
unauthorized
403forbidden
409conflict
500something went wrong 


/decision-flow request overview

/Decision-flow is a web page on which the customer goes through the steps of preparing a payment.


The external wallet populates a JSON object with specific parameters and signs it using its private RSA key. The resulting JSON Web Token (JWT) is sent as a query-parameter signature. On our side, we expect the filled parameters, specifically structured as follows:



FieldDescription
flow_typeThe type of transaction flow, which can be either on-ramp (converting fiat to cryptocurrency) or off-ramp (converting cryptocurrency to fiat)
blockchain
The blockchain network being used.
For example:
- stellar
base_currency_code
For on-ramp transaction it is code of fiat.
For off-ramp transaction  it is code of cryptocurrency.


Currencies should be in the form:
scheme:name:issuer, issuer is optional.
Example: 
stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZV
 - the USDC dollar on the Stellar blockchain
base_currency_amountThe amount of the base currency involved in the transaction.
quote_currency_code
For on-ramp transaction it is code of fiat.
For off-ramp transaction  it is code of cryptocurrency.


Currencies should be in the form:
scheme:name:issuer, issuer is optional.
Example:
iso4217:USD  - fiat dollar
wallet_addressThe Blockchain address where funds will be sent (required for on-ramp transactions).
redirect_url The URL to which users will be redirected after the transaction is completed.  


This structure ensures that we receive the necessary information to process the transaction correctly.
Example:


{
  "exp": 1728208421,
  "payload": {
    "blockchain": "stellar",
    "flow_type": "off-ramp",
    "base_currency_code": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
    "base_currency_amount": "15",
    "quote_currency_code": "iso4217:USD",
    "wallet_address": "GCGBYAZA2C3A62ZVBRVCRE65W7RWAA7F5H4RL3JWU56ZDQ63UJKFSMYJ",
    "redirect_url": "http://localhost/callback"
  }
}

Get information about a transaction

The GET /transaction-info request enables the Wallet App to retrieve the status of a completed transaction after payment processing. This request provides essential details, ensuring that the wallet can confirm transaction outcomes and update users accordingly. 


Example request:

curl --location 'https://web3.aps.money/gollum/api/v1/transaction-info/a0ff618a-bdf3-11ef-8476-864889f7d0a8'

Example response: 

{
  "amount_in": "18.54",
  "amount_in_asset": "stellar:USDC:GABW6Q2Z6...",
  "amount_out": "18.54",
  "amount_out_asset": "iso4217:USD",
  "status": "string",
  "postMessage": "string",
  "kind": "deposit"
}

Response error examples:

CodeDescription
404
transaction not found
500something went wrong

Generate a 2048-bit RSA private key through openssl

openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -out jwtRS256.key -traditional

Code example on Golang

package main

import (
  "crypto/rsa"
  "crypto/x509"
  "encoding/pem"
  "errors"
  "io"
  "os"
  "time"

  "github.com/golang-jwt/jwt"
)

type signaturePayload struct {
  Blockchain         string `json:"blockchain"`
  FlowType           string `json:"flow_type"`
  BaseCurrencyCode   string `json:"base_currency_code"`
  BaseCurrencyAmount string `json:"base_currency_amount"`
  QuoteCurrencyCode  string `json:"quote_currency_code"`
  WalletAddress      string `json:"wallet_address,omitempty"`
  RedirectURL        string `json:"redirect_url"`
}

const pathPrivateKey = "jwtRS256.key"

func main() {
  privateKey, err := loadRSAPrivateKey(pathPrivateKey)
  if err != nil {
    panic(err)
  }

  payload := signaturePayload{
    Blockchain:         "ethereum",
    FlowType:           "off-ramp",
    BaseCurrencyCode:   "eth:USDC",
    BaseCurrencyAmount: "15",
    QuoteCurrencyCode:  "iso4217:USD",
    WalletAddress:      "0x807cF9A772d5a3f9CeFBc1192e939D62f0D9bD38",
    RedirectURL:        "http://localhost:8000/callback",
  }

  var signature string
  if signature, err = makeSignature(privateKey, payload); err != nil {
    panic(err)
  }

  println("signature: \n", signature)
}


func loadRSAPrivateKey(path string) (*rsa.PrivateKey, error) {
  privateKeyFile, err := os.Open(path)
  if err != nil {
    return nil, err
  }
  defer privateKeyFile.Close()

  pemBytes, err := io.ReadAll(privateKeyFile)
  if err != nil {
    return nil, err
  }

  block, _ := pem.Decode(pemBytes)
  if block == nil || block.Type != "RSA PRIVATE KEY" {
    return nil, errors.New("failed to decode PEM block containing private key")
  }

  privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
  if err != nil {
    return nil, err
  }

  return privateKey, nil
}


func makeSignature(privateKey *rsa.PrivateKey, payload signaturePayload) (string, error){
  token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
    "payload": payload,
    "exp":     time.Now().Add(time.Minute * 30).Unix(),
  })

  return token.SignedString(privateKey)
}

The result of this command will be the signature - encoded token:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzA4MDcyODMsInBheWxvYWQiOnsiYmxvY2tjaGFpbiI6ImV0aGVyZXVtIiwiZmxvd190eXBlIjoib2ZmLXJhbXAiLCJiYXNlX2N1cnJlbmN5X2NvZGUiOiJldGg6VVNEQyIsImJhc2VfY3VycmVuY3lfYW1vdW50IjoiMTUiLCJxdW90ZV9jdXJyZW5jeV9jb2RlIjoiaXNvNDIxNzpVU0QiLCJ3YWxsZXRfYWRkcmVzcyI6IjB4ODA3Y0Y5QTc3MmQ1YTNmOUNlRkJjMTE5MmU5MzlENjJmMEQ5YkQzOCIsInJlZGlyZWN0X3VybCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9jYWxsYmFjayJ9fQ.VsEjAfOWq6da0iqGS38yguNxxmJ77wwmX7HCXdbc4hfUrHRfSfeIqaLCopWBbWSu5VBhBpleImxosRDfiqpiurwq1Bfb1rUHHSg1um1iIhG77onBgXDPvn99Aghi7-JzF62UPZEsAPqUIrvboxuUMd05KaajEkKqWbVlJR62e5UsoFrpZ8eEfnh5vmjRyra_BfvSvkbmOgMHJ6YP00bFmyXOKXILTPrH0yEsPn6xfWBTSeckUm4BM291OqVlY0Sdjsv5_ieR8ycvRNiVMVA34b5-6dlJl14LIcRwgVXHH0goTaIVL6dXz71ncHBX_YbSnJY3I3Qr23p8o38M8TNLWQMMnZRPFuCLnX70H8JZeFYvWj5RoZWJH9uKtxrsdjx_9cvBv3f2O8NQSJbf04e0avNGvOrVJG7SpYXtFXn7cn8mRXpE-we3AofX88mblS5zOF0yNDhL1w3VsO0rQlZkGNxktk9yW75FxUzECZh2ZGjgtzmT2Xm5R-8uxgRlB_X-uu-iDFjDhTJbYh-d7IKHRHTkuRPWPp433oJI5Iv22i2FIBNQen3fyIFFz69OnPhv4vtVNUXE648KYNK-6JGZGQWv5gXxQEHOmp9KcJojIzobfOMfR3SQzzQsMI5MNxTBLO7MaEWTEhH1Po3LKw8JVxcMy9u4ybJoH4DH4Sd6bfM

that corresponds to a decoded object: 

{
  "exp": 1730807283,
  "payload": {
    "blockchain": "ethereum",
    "flow_type": "off-ramp",
    "base_currency_code": "eth:USDC",
    "base_currency_amount": "15",
    "quote_currency_code": "iso4217:USD",
    "wallet_address": "0x807cF9A772d5a3f9CeFBc1192e939D62f0D9bD38",
    "redirect_url": "http://localhost:8000/callback"
  }
}

Code example on JavaScript

const fs = require('fs')
const path = require('path')
const jwt = require('jsonwebtoken');

const privateKey = fs.readFileSync(path.join(__dirname, 'jwtRS256.key'), 'utf8')

const current_time = Math.floor(Date.now() / 1000);
const expiration_time = current_time + (60 * 30); // 30m

const claims = {
    'exp': expiration_time,
    'payload': {
        'blockchain': 'ethereum',
        'flow_type': 'off-ramp',
        'base_currency_code': 'eth:USDC',
        'base_currency_amount': '15',
        'quote_currency_code': 'iso4217:USD',
        'wallet_address': '0x807cF9A772d5a3f9CeFBc1192e939D62f0D9bD38',
        'redirect_url': 'http://localhost:8000/callback',
    }
};

const jwt_token =  jwt.sign(claims, privateKey, { algorithm: 'RS256'});
console.log(jwt_token);

The result of this command will be the signature - encoded token:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzA4MDc0MTksInBheWxvYWQiOnsiYmxvY2tjaGFpbiI6ImV0aGVyZXVtIiwiZmxvd190eXBlIjoib2ZmLXJhbXAiLCJiYXNlX2N1cnJlbmN5X2NvZGUiOiJldGg6VVNEQyIsImJhc2VfY3VycmVuY3lfYW1vdW50IjoiMTUiLCJxdW90ZV9jdXJyZW5jeV9jb2RlIjoiaXNvNDIxNzpVU0QiLCJ3YWxsZXRfYWRkcmVzcyI6IjB4ODA3Y0Y5QTc3MmQ1YTNmOUNlRkJjMTE5MmU5MzlENjJmMEQ5YkQzOCIsInJlZGlyZWN0X3VybCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9jYWxsYmFjayJ9LCJpYXQiOjE3MzA4MDU2MTl9.wyO7sbsYcnxbh3IvC3QiINONIsGm7ECrlFiWkd3hPWT0bhujBbVyGra20s_IotyC-bE1gewwN2OYaMOX-J_9SD37H4U35azxz0wf7VvNJXe7m_bi7wMud_5umAM-OlUSbS7ORk0_3HPZbADINan7_9cM-UmGwpPvVFpH3RNmh6NTfD7AQH7jkxyLIk_ljbRRbgUGMYwTLjvJVyhjgyzPqspWZuvtZhsRQLazuG_mbDU6cZtJiwinoEHycm7p4mn05DvvBShEKFLlBb-Rp8EySVtzcxBlOYV9Mv-0Q8ze_t_gEAw1WCo9ywFXk67fD8JcAN4CSTndMsaJV6_moweA4zp9AL2e54R1bRgEexqOBhLOd4tVcX_DcPMhZjnxCPBWNGs4OS2KL2g9qNWqk4wZPFXl1rJIMkhaxJ5gX8GU4frrHt3qJjp53UkAEuWhlp6u_T_GalEI56qmtDE2DGCqWK8MmGmhv2jeIjft7cEevi8PbQXUrwLU7btv0l2sN1LDNnYWCm7XvxRgHLhVzf6AGUnOyrOoPOWunJP78WpgV1-qdM1vmC-gJmVWRZTSQthnPI7ARbcGfZfphH4PsBjcKVgXkMrtYF2RfcN7Ym_lYVyNe8sWJURIGvPQsq2AzxmcFaIhaJWQbzOzMTbe5MHvSNTMPb6KWFarwVyMnoWWA80

that corresponds to a decoded object: 

{
  "exp": 1730807419,
  "payload": {
    "blockchain": "ethereum",
    "flow_type": "off-ramp",
    "base_currency_code": "eth:USDC",
    "base_currency_amount": "15",
    "quote_currency_code": "iso4217:USD",
    "wallet_address": "0x807cF9A772d5a3f9CeFBc1192e939D62f0D9bD38",
    "redirect_url": "http://localhost:8000/callback"
  },
  "iat": 1730805619
}

Extract the public key from the private key

openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub


Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article