SDK & types
The ApiResult / ApiError envelope and every resource data type, ready to copy into your project to type every call.
Acute ships its API contract as TypeScript types, not just prose. This page is
the canonical dump of the response envelope and every resource data shape: the
exact types the server emits. Copy these into your project (a single
acute.ts module works well) so your client and our API agree at compile time.
These are self-contained on purpose
The envelope depends on nothing else: it's designed to be copied verbatim into your codebase without pulling in any extra dependency. The snippets below are the real definitions; paste them as-is.
Every response is an ApiResult<T>: a discriminated union of success, paginated,
and error. Narrow on success first.
export interface Meta {
/** Correlation id for this exact request (also in the `X-Request-Id` header). */
requestId: string;
}
/** A successful single-resource response. */
export interface ApiSuccess<T> {
success: true;
statusCode: StatusCode;
data: T;
pagination?: never;
meta: Meta;
}
/** A successful list response with cursor pagination. */
export interface ApiPaginated<T> {
success: true;
statusCode: StatusCode;
data: T[];
pagination: CursorPagination;
meta: Meta;
}
/** Any failed response. */
export interface ApiErrorResult {
success: false;
statusCode: StatusCode;
error: ApiError;
meta: Meta;
}
export type SingleResult<T> = ApiSuccess<T> | ApiErrorResult;
export type PaginatedResult<T> = ApiPaginated<T> | ApiErrorResult;
export type ApiResult<T> = ApiSuccess<T> | ApiPaginated<T> | ApiErrorResult;There is no top-level message
Success responses are { success, statusCode, data, meta }, no message. The
human-readable string on a failure lives at error.message.
export type StatusCode =
| 200 | 201 | 202 | 204
| 400 | 401 | 403 | 404 | 409 | 422 | 429
| 500 | 502 | 503 | 504;
/** Cursor (keyset) pagination. No total count: follow `nextCursor` until
* `hasMore` is `false`. `nextCursor` is an opaque base64url token. */
export interface CursorPagination {
limit: number;
hasMore: boolean;
nextCursor: string | null;
}error is a union discriminated by type. Each member pins its code and the
shape of details.
interface ApiErrorBase {
/** Stable granular machine code (e.g. `WALLET_INSUFFICIENT_FUNDS`).
* Branch on this for precise handling; `type` is the broad category. */
code: string;
message: string;
}
export interface ApiFieldError {
field: string; // dotted path, e.g. `items.0.amount`
code: string; // machine reason, e.g. `invalid_type`, `too_small`
message: string;
constraint?: Record<string, unknown>;
}
export interface ValidationError extends ApiErrorBase {
type: "validation_error";
details: { fields: ApiFieldError[] };
}
export interface AuthenticationError extends ApiErrorBase {
type: "authentication_error";
details: Record<string, never>;
}
export interface AuthorizationError extends ApiErrorBase {
type: "authorization_error";
details: { requiredScopes?: string[]; providedScopes?: string[] };
}
export interface NotFoundError extends ApiErrorBase {
type: "not_found_error";
details: { resource: string; id: string };
}
export interface ConflictError extends ApiErrorBase {
type: "conflict_error";
details: Record<string, unknown>;
}
export interface UnprocessableError extends ApiErrorBase {
type: "unprocessable_error";
details: Record<string, unknown>;
}
export interface RateLimitError extends ApiErrorBase {
type: "rate_limit_error";
details: { limit: number; remaining: number; resetAt: string; retryAfterSeconds: number };
}
export interface InternalError extends ApiErrorBase {
type: "internal_error";
details: { retryable: boolean; service?: string };
}
export type ApiError =
| ValidationError
| AuthenticationError
| AuthorizationError
| NotFoundError
| ConflictError
| UnprocessableError
| RateLimitError
| InternalError;The full mapping of code → type/status lives in the
error catalog.
The T in ApiResult<T> is the resource's response view. These are the types you
import for each endpoint's data:
| Type | Endpoint(s) |
|---|---|
WalletResponseData | POST /wallets, GET /wallets, GET /wallets/:id |
WalletBalanceData | GET /wallets/:id/balance |
PaymentResponseData | POST /payments, GET /payments, GET /payments/:id |
TransferResponseData | POST /wallets/:id/transfer |
WithdrawalResponseData | POST /wallets/:id/withdraw |
RefundResponseData | POST /payments/:id/refund |
PayoutResponseData (+ PayoutItemData) | POST /payouts, GET /payouts, GET /payouts/:id |
export interface WalletResponseData {
id: string;
kind: "settlement" | "end_user";
email: string;
fullName: string | null;
phone: string | null;
externalReference: string | null;
kycStatus: "none" | "tier1";
status: "active" | "frozen" | "closed";
currency: string;
createdAt: string;
}
export interface WalletBalanceData {
walletId: string;
/** Available balance = ledger balance, in kobo. */
balance: number;
currency: string;
}export interface VirtualAccountData {
accountNumber: string;
bankName: string;
accountName: string;
expiresAt: string | null;
}
export interface PaymentResponseData {
id: string;
method: "bank_transfer" | "virtual_wallet";
status: "pending" | "partial" | "settled" | "expired" | "failed" | "refunded" | "partially_refunded";
baseAmount: number;
fee: number;
payableAmount: number;
amountReceived: number;
currency: string;
targetWalletId: string;
description: string | null;
expiresAt: string | null;
settledAt: string | null;
createdAt: string;
/** Present for `bank_transfer` payments. */
virtualAccount: VirtualAccountData | null;
}export interface TransferResponseData {
id: string;
sourceWalletId: string;
destinationWalletId: string;
amount: number;
fee: number;
status: "completed" | "failed";
description: string | null;
currency: string;
createdAt: string;
}
export interface WithdrawalCounterparty {
accountNumber: string;
accountName: string | null;
bankCode: string;
bankName: string | null;
}
export interface WithdrawalResponseData {
id: string;
sourceWalletId: string;
amount: number;
fee: number;
totalAmount: number;
status: "processing" | "completed" | "failed" | "returned";
counterparty: WithdrawalCounterparty;
failureReason: string | null;
currency: string;
createdAt: string;
completedAt: string | null;
}
export interface RefundResponseData {
id: string;
paymentId: string;
amount: number;
destination: "bank" | "wallet";
status: "processing" | "completed" | "failed";
failureReason: string | null;
currency: string;
createdAt: string;
completedAt: string | null;
}export interface PayoutItemData {
id: string;
amount: number;
fee: number;
status: "pending" | "processing" | "completed" | "failed";
counterparty: { accountNumber: string; accountName: string | null; bankCode: string };
failureReason: string | null;
}
export interface PayoutResponseData {
id: string;
sourceWalletId: string;
totalAmount: number;
totalFee: number;
itemCount: number;
status: "processing" | "completed" | "partially_completed" | "failed";
items: PayoutItemData[];
currency: string;
createdAt: string;
}Paste the blocks above into a local module (for example acute.ts) and import from
it. There's no package to install: you own the types.
import type { ApiResult, PaymentResponseData } from "./acute"; // the types from this page
async function getPayment(id: string, key: string): Promise<PaymentResponseData> {
const res = await fetch(`https://api.acute.network/v1/payments/${id}`, {
headers: { Authorization: `Bearer ${key}` },
});
const result: ApiResult<PaymentResponseData> = await res.json();
if (!result.success) {
throw new Error(`${result.error.code} (${result.meta.requestId})`);
}
return result.data; // typed PaymentResponseData
}There is no official runtime SDK yet
v1 ships the types, not a packaged HTTP client, so you stay in control of your fetch layer, retries, and idempotency keys. Bind these types to your client of choice; an OpenAPI 3.1 spec generated from the same contracts is on the roadmap.