Finance Manager API: Overview

Background

This vignette provides an overview of how to interact with the Finance Manager API from R. It demonstrates how to simulate client interactions such as account registration, login, and balance queries.

API set up

This part is only needed if the API is not hosted. Since the server is not deployed publicly, we start the API server locally in the background using the callr package. This lets us send HTTP requests to the locally running server just like a real client would.

First set up the env variables

tmp_dir <- tempfile("test-api-deposit-") 
dir.create(tmp_dir, recursive = TRUE)    # creating a temporally accounts folder
                                         # this will be permanent dir on deployment
Sys.setenv(ACCOUNT_BASE_DIR = tmp_dir)   # use tempfile to avoid contamination
Sys.setenv(ACCOUNT_BACKEND = "file")     # default storage backend(local dir)
Sys.setenv(MAX_REQUESTS = 1000)          # maximum requests per rate limit
Sys.setenv(WINDOW_SIZE = 3600)           # rate limit window size
Sys.setenv(JWT_SECRET = "test-secret")   # secret Key for signing tokens
secret_key <- Sys.getenv("JWT_SECRET")

Function to listen if server is live

This function pings the server after every 0.1 seconds with a timeout of 80 seconds to establish if its live. Its a way to avoid sending request when server is not live.

wait_for_server_ready <- function(
  url = "http://127.0.0.1:8000/__ping__",
  timeout = 40
) {
  start_time <- Sys.time()
  while (as.numeric(Sys.time() - start_time, units = "secs") < timeout) {
    res <- tryCatch(httr::GET(url), error = function(e) NULL)
    if (!is.null(res) && httr::status_code(res) == 200) return(TRUE)
    Sys.sleep(0.10)
  }
  stop("Server did not become ready within timeout.")
}

Spin up the server

Spin up the server on background but capture both std output and stderr for debugging. > This approach is ideal for testing and documenting the API without requiring a live deployment.

log_out <- tempfile("server-out-", fileext = ".log")
log_err <- tempfile("server-err-", fileext = ".log")

server <- callr::r_bg(
  function(main_file, jwt, base_dir, project_dir) {
    setwd(project_dir)
    Sys.setenv(JWT_SECRET = jwt)
    Sys.setenv(ACCOUNT_BASE_DIR = base_dir)
    Sys.setenv(ACCOUNT_BACKEND = "file")
    source(main_file)
  },
  args = list(
    main_file = here("api", "main.R"),
    jwt = secret_key,
    base_dir = tmp_dir,
    project_dir = here()
  ),
  stdout = log_out,
  stderr = log_err
)


withr::defer({
  if (server$is_alive()) server$kill()
  cat("📤 Server stdout:\n")
  cat(readLines(log_out, warn = FALSE), sep = "\n")
  cat("\n📥 Server stderr:\n")
  cat(readLines(log_err, warn = FALSE), sep = "\n")
}, envir = parent.frame())
📤 Server stdout:


📥 Server stderr:

Wait for the server to be ready.

wait_for_server_ready("http://127.0.0.1:8000/__ping__")
[1] TRUE

Interact with the server.

This is where where to start if the server is hosted.

create auth token

You need to a signed JWT authorization token to access the API. this API being part of backend it is assumed when users are signed in from the front end they are granted the auth token below.

user_id =uuid::UUIDgenerate()                 # generate a user id
token <- jwt_encode_hmac(
  jwt_claim(user_id = user_id, role = "user"),
  secret = secret_key
)

1) create account.

create a user account. this will return the base/root account.

  res <- POST(
    url = "http://127.0.0.1:8000/register",
    httr::add_headers(Authorization = paste("Bearer", token)),
    body = list(user_id = user_id),
    encode = "json"
  )
  parsed <- jsonlite::fromJSON(rawToChar(res$content))
  print(parsed)
$success
[1] TRUE

$status
[1] 200

$user_id
[1] "75e265b3-8ba1-4c1d-a441-ad38c7e55fd9"

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$start_time
[1] "2025-08-01T19:34:35.312Z"

$end_time
[1] "2025-08-01T19:34:35.612Z"

$execution_time
[1] 0.3005

check account balance

  1. lets see the balance of the created account
  res1 <- httr::GET(
    url = "http://127.0.0.1:8000/get_balance",
    query = list(uuid = parsed$uuid),
    httr::add_headers(Authorization = paste("Bearer", token))
  )
  parsed1 <- jsonlite::fromJSON(rawToChar(res1$content))
  print(parsed1)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$balance
[1] 0

$start_time
[1] "2025-08-01T19:34:35.671Z"

$end_time
[1] "2025-08-01T19:34:35.889Z"

$execution_time
[1] 0.2182

see transaction list

currently the account has no transactions

  res1 <- httr::GET(
    url = "http://127.0.0.1:8000/get_transactions",
    httr::add_headers(Authorization = paste("Bearer", token)),
    query = list(uuid = parsed$uuid)
  )
  parsed1 <- jsonlite::fromJSON(rawToChar(res1$content))
  print(parsed1)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$transaction_count
[1] 0

$transactions
list()

$start_time
[1] "2025-08-01T19:34:35.947Z"

$end_time
[1] "2025-08-01T19:34:36.168Z"

$execution_time
[1] 0.2208

list child accounts

currently we havent added any child

  res1 <- httr::GET(
    url = "http://127.0.0.1:8000/list_child_accounts",
    httr::add_headers(Authorization = paste("Bearer", token)),
    query = list(uuid = parsed$uuid)
  )
  parsed1 <- jsonlite::fromJSON(rawToChar(res1$content))
  print(parsed1)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$account_name
[1] "Main"

$child_account_names
list()

$child_count
[1] 0

$start_time
[1] "2025-08-01T19:34:36.225Z"

$end_time
[1] "2025-08-01T19:34:36.448Z"

$execution_time
[1] 0.2228

attach child accounts

Attaching child accounts

res1 <- httr::POST(
    url = "http://127.0.0.1:8000/add_sub_account",
    httr::add_headers(Authorization = paste("Bearer", token)),
    encode = "json",
    body = list(
      parent_uuid = parsed$uuid,
      name = "Needs",
      allocation = 0.5
    )
  )
  parsed1 <- jsonlite::fromJSON(rawToChar(res1$content))
  print(parsed1)
$success
[1] TRUE

$status
[1] 200

$message
[1] "Needs added under Main"

$uuid
[1] "acc93bf00ed-86c5-4798-99df-1bcbf99f8293"

$child_type
[1] "ChildAccount"

$allocation
[1] 0.5

$start_time
[1] "2025-08-01T19:34:36.508Z"

$end_time
[1] "2025-08-01T19:34:37.030Z"

$execution_time
[1] 0.5226
res2 <- httr::POST(
    url = "http://127.0.0.1:8000/add_sub_account",
    httr::add_headers(Authorization = paste("Bearer", token)),
    encode = "json",
    body = list(
      parent_uuid = parsed$uuid,
      name = "Wants",
      allocation = 0.3
    )
  )
  parsed2 <- jsonlite::fromJSON(rawToChar(res2$content))
  print(parsed2)
$success
[1] TRUE

$status
[1] 200

$message
[1] "Wants added under Main"

$uuid
[1] "acce89fdf47-7c12-453a-8ed1-a51aac7bc732"

$child_type
[1] "ChildAccount"

$allocation
[1] 0.3

$start_time
[1] "2025-08-01T19:34:37.081Z"

$end_time
[1] "2025-08-01T19:34:37.600Z"

$execution_time
[1] 0.5197
res3 <- httr::POST(
    url = "http://127.0.0.1:8000/add_sub_account",
    httr::add_headers(Authorization = paste("Bearer", token)),
    encode = "json",
    body = list(
      parent_uuid = parsed$uuid,
      name = "Savings",
      allocation = 0.2
    )
  )
  parsed3 <- jsonlite::fromJSON(rawToChar(res3$content))
  print(parsed3)
$success
[1] TRUE

$status
[1] 200

$message
[1] "Savings added under Main"

$uuid
[1] "acc60f14f26-0faf-4702-9ee3-a374ceb8c330"

$child_type
[1] "ChildAccount"

$allocation
[1] 0.2

$start_time
[1] "2025-08-01T19:34:37.651Z"

$end_time
[1] "2025-08-01T19:34:38.173Z"

$execution_time
[1] 0.5224

deposit to main

res4 <- httr::POST(
    url = "http://127.0.0.1:8000/deposit",
    httr::add_headers(Authorization = paste("Bearer", token)),
    body = list(uuid = parsed$uuid, amount = 40000, channel = "ABSA bank"),
    encode = "form"
  )
  parsed4 <- jsonlite::fromJSON(rawToChar(res4$content))
print(parsed4)
$success
[1] TRUE

$status
[1] 200

$account_uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$amount
[1] 40000

$balance
[1] 0

$start_time
[1] "2025-08-01T19:34:38.235Z"

$end_time
[1] "2025-08-01T19:34:38.691Z"

$execution_time
[1] 0.4558
res4 <- httr::POST(
    url = "http://127.0.0.1:8000/deposit",
    httr::add_headers(Authorization = paste("Bearer", token)),
    body = list(uuid = parsed1$uuid, amount = 20000, channel = "Stanbic bank"),
    encode = "form"
  )
  parsed4 <- jsonlite::fromJSON(rawToChar(res4$content))
print(parsed4)
$success
[1] TRUE

$status
[1] 200

$account_uuid
[1] "acc93bf00ed-86c5-4798-99df-1bcbf99f8293"

$amount
[1] 20000

$balance
[1] 40000

$start_time
[1] "2025-08-01T19:34:38.747Z"

$end_time
[1] "2025-08-01T19:34:39.223Z"

$execution_time
[1] 0.4751

Withdraw from needs account

  res5 <- httr::POST(
    url = "http://127.0.0.1:8000/withdraw",
    httr::add_headers(Authorization = paste("Bearer", token)),
    body = list(uuid = parsed1$uuid, amount = 20000, channel = "mpesa"),
    encode = "form"
  )
  parsed5 <- jsonlite::fromJSON(rawToChar(res5$content))
  print(parsed5)
$success
[1] TRUE

$status
[1] 200

$account_uuid
[1] "acc93bf00ed-86c5-4798-99df-1bcbf99f8293"

$balance
[1] 20000

$start_time
[1] "2025-08-01T19:34:39.280Z"

$end_time
[1] "2025-08-01T19:34:39.715Z"

$execution_time
[1] 0.4358

Check needs balance

  res6 <- httr::GET(
    url = "http://127.0.0.1:8000/get_balance",
    query = list(uuid = parsed1$uuid),
    httr::add_headers(Authorization = paste("Bearer", token))
  )
  parsed6 <- jsonlite::fromJSON(rawToChar(res6$content))
  print(parsed6)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc93bf00ed-86c5-4798-99df-1bcbf99f8293"

$balance
[1] 20000

$start_time
[1] "2025-08-01T19:34:39.772Z"

$end_time
[1] "2025-08-01T19:34:39.988Z"

$execution_time
[1] 0.2156

compute total balance

  res7 <- httr::GET(
    url = "http://127.0.0.1:8000/compute_total_balance",
    httr::add_headers(Authorization = paste("Bearer", token)),
    query = list(uuid = parsed$uuid)
  )
  parsed7 <- jsonlite::fromJSON(rawToChar(res7$content))
  print(parsed7)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$total_balance
[1] 40000

$start_time
[1] "2025-08-01T19:34:40.050Z"

$end_time
[1] "2025-08-01T19:34:40.266Z"

$execution_time
[1] 0.2159

compute total due

  res8 <- httr::GET(
    url = "http://127.0.0.1:8000/compute_total_due",
    httr::add_headers(Authorization = paste("Bearer", token)),
    query = list(uuid = parsed$uuid)
    
  )
parsed8 <- jsonlite::fromJSON(rawToChar(res8$content))
print(parsed8)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$total_due
[1] 0

$start_time
[1] "2025-08-01T19:34:40.322Z"

$end_time
[1] "2025-08-01T19:34:40.566Z"

$execution_time
[1] 0.244

compute total income

  res9 <- httr::GET(
    url = "http://127.0.0.1:8000/total_income",
    httr::add_headers(Authorization = paste("Bearer", token)),
    query = list(uuid = parsed$uuid)
  )
  parsed9 <- jsonlite::fromJSON(rawToChar(res9$content))
  print(parsed9)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$from
[1] "1026-04-01"

$to
[1] "2025-08-01"

$total_income
[1] 60000

$start_time
[1] "2025-08-01T19:34:40.624Z"

$end_time
[1] "2025-08-01T19:34:40.872Z"

$execution_time
[1] 0.2478

Total spend

  res11 <- httr::GET(
    url = "http://127.0.0.1:8000/spending",
    httr::add_headers(Authorization = paste("Bearer", token)),
    query = list(uuid = parsed$uuid)
  )
  parsed11 <- jsonlite::fromJSON(rawToChar(res11$content))
  print(parsed11)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$from
[1] "1026-04-01"

$to
[1] "2025-08-01"

$total_spending
[1] 20000

$start_time
[1] "2025-08-01T19:34:40.933Z"

$end_time
[1] "2025-08-01T19:34:41.184Z"

$execution_time
[1] 0.251

Income utilization

  res10 <- httr::GET(
    url = "http://127.0.0.1:8000/income_utilization",
    httr::add_headers(Authorization = paste("Bearer", token)),
    query = list(uuid = parsed$uuid)
  )
  parsed10 <- jsonlite::fromJSON(rawToChar(res10$content))
  print(parsed10)
$success
[1] TRUE

$status
[1] 200

$uuid
[1] "acc0b018655-bf7d-41ed-a98d-8757c886a393"

$from
[1] "1026-04-01"

$to
[1] "2025-08-01"

$utilization
[1] 0.3333

$start_time
[1] "2025-08-01T19:34:41.243Z"

$end_time
[1] "2025-08-01T19:34:41.583Z"

$execution_time
[1] 0.3406