All templatesBusiness banking on the Whop API: balances, corporate cards and money movement.
Neobank
Code
$git clone https://github.com/whopio/templates.git && cd templates/ios/neobankAPI/NeobankEndpoints.swiftprotocol NeobankEndpoints: Sendable {
func account() async throws -> Account
func financialActivity(accountID: String) async throws -> Paginated<LedgerLine>
func cards(accountID: String) async throws -> [Card]
func cardTransactions(accountID: String) async throws -> [CardTransaction]
func createTransfer(originID: String, destinationID: String, amount: Double, notes: String?) async throws -> Transfer
func updateCard(_ cardID: String, frozen: Bool) async throws -> Card
}API/HTTPEndpoints.swiftstruct HTTPEndpoints: NeobankEndpoints {
static let baseURL = URL(string: "https://api.whop.com/api/v1")!
static let apiVersionDate = "2026-08-03"
let accessToken: String
private var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}
func account() async throws -> Account {
try await get("/accounts/me")
}
func cards(accountID: String) async throws -> [Card] {
let page: Paginated<Card> = try await get("/cards", query: ["account_id": accountID])
return page.data
}
private func get<T: Decodable>(_ path: String, query: [String: String] = [:]) async throws -> T {
var components = URLComponents(url: Self.baseURL.appending(path: path), resolvingAgainstBaseURL: false)!
if !query.isEmpty {
components.queryItems = query.map { URLQueryItem(name: $0.key, value: $0.value) }
}
return try await perform(URLRequest(url: components.url!), path: path)
}
private func perform<T: Decodable>(_ request: URLRequest, path: String) async throws -> T {
var request = request
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
request.setValue(Self.apiVersionDate, forHTTPHeaderField: "Api-Version-Date")
let (data, response) = try await URLSession.shared.data(for: request)
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
if status == 401 || status == 403 { throw NeobankError.unauthorized }
guard (200 ..< 300).contains(status) else { throw NeobankError.http(status: status, path: path) }
return try decoder.decode(T.self, from: data)
}
}NeobankApp.swift@main
struct NeobankApp: App {
var body: some Scene {
WindowGroup {
RootView(endpoints: NeobankConfiguration.endpoints())
}
}
}
// Swap the protocol for fixtures, a cache, or your own backend proxy — every screen follows.
enum NeobankConfiguration {
static func endpoints() -> any NeobankEndpoints {
guard let token = value("NEOBANK_ACCESS_TOKEN"), !usesFixtures else {
return FixtureEndpoints()
}
return HTTPEndpoints(accessToken: token)
}
}Endpoints used
GET
Balances, pending funds and reserve for the header/accounts/meGET
The activity ledger, newest first/financial-activityGET
Issued cards with spend limits and frozen state/cardsGET
Card spend feed with merchant and status/card_transactionsGET
Transfers sent from this account/transfersGET
Who you can send to/transfers/recipientsGET
Withdrawals to bank, with fee and status/payoutsGET
Linked bank accounts and their payout speeds/payouts/methodsGET
Daily series behind the Overview and Cards charts/stats/{metric}GET
The income statement on the Report tab/financial_reportsPOST
Send money to a recipient/transfersPOST
Withdraw to a linked bank account/payoutsPOST
Bank details and crypto addresses for adding funds/depositsPOST
Issue a new card/cardsPATCH
Freeze or unfreeze a card/cards/{id}POST
Quote a currency conversion/swaps/quotePOST
Execute the conversion/swaps