<- tempfile("test-api-deposit-")
tmp_dir 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
<- Sys.getenv("JWT_SECRET") secret_key
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
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.
<- function(
wait_for_server_ready url = "http://127.0.0.1:8000/__ping__",
timeout = 40
) {<- Sys.time()
start_time while (as.numeric(Sys.time() - start_time, units = "secs") < timeout) {
<- tryCatch(httr::GET(url), error = function(e) NULL)
res 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.
<- tempfile("server-out-", fileext = ".log")
log_out <- tempfile("server-err-", fileext = ".log")
log_err
<- callr::r_bg(
server 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
)
::defer({
withrif (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.
=uuid::UUIDgenerate() # generate a user id
user_id <- jwt_encode_hmac(
token 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.
<- POST(
res url = "http://127.0.0.1:8000/register",
::add_headers(Authorization = paste("Bearer", token)),
httrbody = list(user_id = user_id),
encode = "json"
)<- jsonlite::fromJSON(rawToChar(res$content))
parsed 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
- lets see the balance of the created account
<- httr::GET(
res1 url = "http://127.0.0.1:8000/get_balance",
query = list(uuid = parsed$uuid),
::add_headers(Authorization = paste("Bearer", token))
httr
)<- jsonlite::fromJSON(rawToChar(res1$content))
parsed1 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
<- httr::GET(
res1 url = "http://127.0.0.1:8000/get_transactions",
::add_headers(Authorization = paste("Bearer", token)),
httrquery = list(uuid = parsed$uuid)
)<- jsonlite::fromJSON(rawToChar(res1$content))
parsed1 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
<- httr::GET(
res1 url = "http://127.0.0.1:8000/list_child_accounts",
::add_headers(Authorization = paste("Bearer", token)),
httrquery = list(uuid = parsed$uuid)
)<- jsonlite::fromJSON(rawToChar(res1$content))
parsed1 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
<- httr::POST(
res1 url = "http://127.0.0.1:8000/add_sub_account",
::add_headers(Authorization = paste("Bearer", token)),
httrencode = "json",
body = list(
parent_uuid = parsed$uuid,
name = "Needs",
allocation = 0.5
)
)<- jsonlite::fromJSON(rawToChar(res1$content))
parsed1 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
<- httr::POST(
res2 url = "http://127.0.0.1:8000/add_sub_account",
::add_headers(Authorization = paste("Bearer", token)),
httrencode = "json",
body = list(
parent_uuid = parsed$uuid,
name = "Wants",
allocation = 0.3
)
)<- jsonlite::fromJSON(rawToChar(res2$content))
parsed2 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
<- httr::POST(
res3 url = "http://127.0.0.1:8000/add_sub_account",
::add_headers(Authorization = paste("Bearer", token)),
httrencode = "json",
body = list(
parent_uuid = parsed$uuid,
name = "Savings",
allocation = 0.2
)
)<- jsonlite::fromJSON(rawToChar(res3$content))
parsed3 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
<- httr::POST(
res4 url = "http://127.0.0.1:8000/deposit",
::add_headers(Authorization = paste("Bearer", token)),
httrbody = list(uuid = parsed$uuid, amount = 40000, channel = "ABSA bank"),
encode = "form"
)<- jsonlite::fromJSON(rawToChar(res4$content))
parsed4 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
<- httr::POST(
res4 url = "http://127.0.0.1:8000/deposit",
::add_headers(Authorization = paste("Bearer", token)),
httrbody = list(uuid = parsed1$uuid, amount = 20000, channel = "Stanbic bank"),
encode = "form"
)<- jsonlite::fromJSON(rawToChar(res4$content))
parsed4 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
<- httr::POST(
res5 url = "http://127.0.0.1:8000/withdraw",
::add_headers(Authorization = paste("Bearer", token)),
httrbody = list(uuid = parsed1$uuid, amount = 20000, channel = "mpesa"),
encode = "form"
)<- jsonlite::fromJSON(rawToChar(res5$content))
parsed5 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
<- httr::GET(
res6 url = "http://127.0.0.1:8000/get_balance",
query = list(uuid = parsed1$uuid),
::add_headers(Authorization = paste("Bearer", token))
httr
)<- jsonlite::fromJSON(rawToChar(res6$content))
parsed6 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
<- httr::GET(
res7 url = "http://127.0.0.1:8000/compute_total_balance",
::add_headers(Authorization = paste("Bearer", token)),
httrquery = list(uuid = parsed$uuid)
)<- jsonlite::fromJSON(rawToChar(res7$content))
parsed7 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
<- httr::GET(
res8 url = "http://127.0.0.1:8000/compute_total_due",
::add_headers(Authorization = paste("Bearer", token)),
httrquery = list(uuid = parsed$uuid)
)<- jsonlite::fromJSON(rawToChar(res8$content))
parsed8 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
<- httr::GET(
res9 url = "http://127.0.0.1:8000/total_income",
::add_headers(Authorization = paste("Bearer", token)),
httrquery = list(uuid = parsed$uuid)
)<- jsonlite::fromJSON(rawToChar(res9$content))
parsed9 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
<- httr::GET(
res11 url = "http://127.0.0.1:8000/spending",
::add_headers(Authorization = paste("Bearer", token)),
httrquery = list(uuid = parsed$uuid)
)<- jsonlite::fromJSON(rawToChar(res11$content))
parsed11 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
<- httr::GET(
res10 url = "http://127.0.0.1:8000/income_utilization",
::add_headers(Authorization = paste("Bearer", token)),
httrquery = list(uuid = parsed$uuid)
)<- jsonlite::fromJSON(rawToChar(res10$content))
parsed10 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