The official Fly client for Node.js and TypeScript. Strongly typed, zero production dependencies — uses the platform fetch API.
Pre-release — not yet on npm. The SDK (@nexus/api-client) currently lives in the Fly monorepo and has not been published to a public registry, and its API-key authentication is not yet enabled server-side. This page documents the client as it exists in source so you can evaluate the surface ahead of release. Want early access? Email developers@useflysystems.com.
Code samples on this page are illustrative examples — they reference example IDs and do not run against the live API. To call the API against your own seeded test org, use the read-write sandbox.
Package name: @nexus/api-client — coming soon to npm. The commands below will work once the package is published:
npm install @nexus/api-client # coming soon
pnpm add @nexus/api-client # coming soon
yarn add @nexus/api-client # coming soon
The SDK is built around API Key authentication for server-to-server use cases, with OAuth 2.0 for partner integrations that act on behalf of a merchant.
The client presents the key as Authorization: Bearer <key>, but there is no API-key program yet — nothing validates an API key server-side and no keys are issued, so these calls will return 401 until that program launches with the npm release. The credential the platform accepts from third parties today is OAuth 2.0 client_credentials.
import { NexusClient } from '@nexus/api-client';
const epos = new NexusClient({
apiKey: process.env.ELEVATEDPOS_API_KEY!, // never expose this in the browser
});After completing the OAuth flow and receiving tokens, pass the access token via the standard Authorization header by constructing a thin wrapper. Refer to the OAuth docs for the full flow — OAuth API access is currently in private beta.
// OAuth: use a proxy that injects the bearer token server-side // and call your own backend endpoints to avoid exposing the token. // The Fly API accepts: Authorization: Bearer <access_token> // Set this at the gateway level when forwarding merchant requests.
Example code — not executed.
import { NexusClient } from '@nexus/api-client';
const epos = new NexusClient({ apiKey: process.env.ELEVATEDPOS_API_KEY! });
// List products with pagination
const { data: products, meta } = await epos.catalog.products.list({
isActive: true,
limit: 20,
});
console.log(`Showing ${products.length} of ${meta.totalCount} products`);
// Search customers
const { data: customers } = await epos.customers.list({ search: 'Jane' });
// Create an order
const { data: order } = await epos.orders.create({
locationId: 'loc_01HXXXXXXXXXX',
customerId: customers[0].id,
lines: [
{ productId: products[0].id, quantity: 2, unitPrice: 4.50 },
],
});
console.log('Order created:', order.orderNumber, order.status);
// Adjust inventory after a manual count
await epos.inventory.stock.adjust(products[0].id, {
locationId: 'loc_01HXXXXXXXXXX',
quantity: -3,
reason: 'manual_count',
});
// Accrue loyalty points
await epos.loyalty.accounts.accruePoints(customers[0].id, {
orderId: order.id,
points: 50,
description: 'Purchase reward',
});All methods are async and return typed promises. Errors throw NexusApiError.
client.catalog.products.list(params?) → Promise<PaginatedResponse<Product>>List products. Filter by categoryId, search, isActive, limit, cursor.
client.catalog.products.get(id) → Promise<ApiResponse<Product>>Retrieve a single product by ID.
client.catalog.products.create(data) → Promise<ApiResponse<Product>>Create a new product.
client.catalog.products.update(id, data) → Promise<ApiResponse<Product>>Partial update a product.
client.catalog.products.delete(id) → Promise<void>Delete a product.
client.catalog.categories.list() → Promise<PaginatedResponse<Category>>List all categories for the org.
client.catalog.categories.get(id) → Promise<ApiResponse<Category>>Retrieve a single category by ID.
client.orders.list(params?) → Promise<PaginatedResponse<Order>>List orders. Filter by status, customerId, locationId, limit, cursor.
client.orders.get(id) → Promise<ApiResponse<Order>>Retrieve a full order with line items.
client.orders.create(data) → Promise<ApiResponse<Order>>Create a new order with line items.
client.orders.updateStatus(id, status) → Promise<ApiResponse<Order>>Transition an order to a new status.
client.customers.list(params?) → Promise<PaginatedResponse<Customer>>List customers. Filter by search, limit, cursor.
client.customers.get(id) → Promise<ApiResponse<Customer>>Retrieve a single customer by ID.
client.customers.create(data) → Promise<ApiResponse<Customer>>Create a new customer record.
client.customers.update(id, data) → Promise<ApiResponse<Customer>>Partial update a customer.
client.inventory.stock.list(params?) → Promise<PaginatedResponse<StockLevel>>List stock levels. Filter by locationId, lowStock, productId.
client.inventory.stock.get(productId, locationId) → Promise<ApiResponse<StockLevel>>Get stock level for a specific product at a location.
client.inventory.stock.adjust(productId, data) → Promise<ApiResponse<StockAdjustment>>Adjust inventory quantity with a reason.
client.loyalty.accounts.get(customerId) → Promise<ApiResponse<LoyaltyAccount>>Get loyalty account for a customer.
client.loyalty.accounts.transactions(accountId, params?) → Promise<PaginatedResponse<LoyaltyTransaction>>List points transactions for an account.
client.loyalty.accounts.accruePoints(accountId, data) → Promise<ApiResponse<LoyaltyTransaction>>Add loyalty points to an account.
client.loyalty.accounts.redeemPoints(accountId, data) → Promise<ApiResponse<LoyaltyTransaction>>Redeem loyalty points from an account.
client.webhooks.list() → Promise<PaginatedResponse<Webhook>>List all registered webhooks for the org.
client.webhooks.create(data) → Promise<ApiResponse<Webhook>>Register a new webhook endpoint. Secret returned only once.
client.webhooks.update(id, data) → Promise<ApiResponse<Webhook>>Update URL, events, label, or enabled state.
client.webhooks.delete(id) → Promise<void>Remove a webhook endpoint.
client.webhooks.test(id) → Promise<ApiResponse<...>>Send a test ping to the webhook URL.
client.webhooks.deliveries(id, params?) → Promise<PaginatedResponse<WebhookDelivery>>List recent delivery attempts with status and duration.
Fly signs every delivery with X-ElevatedPOS-Signature: sha256=HMAC(secret, body). Use verifyWebhookSignature to validate incoming requests.
import { verifyWebhookSignature } from '@nexus/api-client';
// Next.js App Router example
export async function POST(request: Request) {
const rawBody = await request.text();
const signature = request.headers.get('x-elevatedpos-signature') ?? '';
const isValid = await verifyWebhookSignature(
rawBody,
signature,
process.env.ELEVATEDPOS_WEBHOOK_SECRET!,
);
if (!isValid) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(rawBody);
console.log('Event type:', event.event); // e.g. 'test.ping'
console.log('Payload:', event.data);
return new Response('OK', { status: 200 });
}const { data: webhook } = await epos.webhooks.create({
url: 'https://yourapp.com/api/elevatedpos-webhook',
events: ['order.completed', 'payment.captured', 'inventory.low_stock'],
label: 'Production handler',
});
// IMPORTANT: save webhook.secret securely now — it is never shown again
// Store it in an environment variable or secrets manager, never log it
// Later: list deliveries for debugging
const { data: deliveries } = await epos.webhooks.deliveries(webhook.id);
deliveries.forEach((d) => {
console.log(d.event, d.success, d.statusCode, `${d.durationMs}ms`);
});All failed requests throw a typed NexusApiError. Network timeouts throw with status === 0.
import { NexusClient, NexusApiError } from '@nexus/api-client';
const epos = new NexusClient({ apiKey: process.env.ELEVATEDPOS_API_KEY! });
try {
const { data: product } = await epos.catalog.products.get('nonexistent-id');
} catch (err) {
if (err instanceof NexusApiError) {
// Strongly typed properties:
console.error(err.status); // 404
console.error(err.message); // "Not Found"
console.error(err.detail); // "Product not found"
console.error(err.type); // "https://elevatedpos.com.au/errors/not-found"
// Convenience getters:
if (err.isNotFound) { /* handle 404 */ }
if (err.isValidationError) { /* handle 422 — check err.detail */ }
if (err.isUnauthorized) { /* re-authenticate */ }
if (err.isForbidden) { /* insufficient permissions */ }
if (err.isServerError) { /* 5xx — retry with backoff */ }
}
}All types are exported from @nexus/api-client.
interface NexusClientConfig {
apiKey: string; // required — server-side only (API-key auth not yet live, see above)
baseUrl?: string; // default: https://api.useflyconnect.com
timeout?: number; // default: 30_000 ms
}interface Product {
id: string; orgId: string; name: string; sku: string;
basePrice: number; costPrice?: number; isActive: boolean;
categoryId?: string; tags: string[]; description?: string;
imageUrl?: string; barcode?: string; taxRate?: number;
createdAt: string; updatedAt: string;
}interface Order {
id: string; orgId: string; orderNumber: string;
status: 'pending'|'confirmed'|'in_progress'|'ready'|'completed'|'cancelled'|'refunded';
subtotal: number; discountTotal: number; taxTotal: number; total: number;
customerId?: string; locationId?: string; lines: OrderLine[];
createdAt: string; updatedAt: string;
}interface Webhook {
id: string; orgId: string; label: string; url: string;
events: WebhookEvent[]; enabled: boolean;
secret?: string; // only present on creation response
createdAt: string; updatedAt: string;
}class NexusApiError extends Error {
status: number; // HTTP status code (0 = network error)
type: string; // RFC 9457 problem type URI
message: string; // human-readable title
detail?: string; // additional detail
get isUnauthorized(): boolean // status === 401
get isForbidden(): boolean // status === 403
get isNotFound(): boolean // status === 404
get isValidationError(): boolean // status === 422
get isServerError(): boolean // status >= 500
}We'll share the package and release timeline with beta developers.