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.

ApiResult<T>
ts
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.

StatusCode + CursorPagination
ts
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.

ApiError
ts
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:

TypeEndpoint(s)
WalletResponseDataPOST /wallets, GET /wallets, GET /wallets/:id
WalletBalanceDataGET /wallets/:id/balance
PaymentResponseDataPOST /payments, GET /payments, GET /payments/:id
TransferResponseDataPOST /wallets/:id/transfer
WithdrawalResponseDataPOST /wallets/:id/withdraw
RefundResponseDataPOST /payments/:id/refund
PayoutResponseData (+ PayoutItemData)POST /payouts, GET /payouts, GET /payouts/:id

WalletResponseData + WalletBalanceData
ts
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;
}

PaymentResponseData + VirtualAccountData
ts
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;
}

TransferResponseData / WithdrawalResponseData / RefundResponseData
ts
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;
}

PayoutResponseData + PayoutItemData
ts
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.

type a call, end to end
ts
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.