Skip to main content

TypeScript client

A minimal, dependency-free TypeScript client. Drop it in, import it, ship. Handles OAuth bearer and API key auth, discriminates the three partner error envelopes documented in Pagination + errors (plus the empty-body OAuth 401/403 and the OAuth token-endpoint shape), and throws a typed FlynetError you can match on status and code. Download flynet.ts: ~220 lines, no dependencies, MIT-style use-it-however.

Why use it

Hand-rolling the basics costs ~30 to 60 minutes before you write any business logic:
Without flynet.tsWith flynet.ts
Build a fetch wrapper per auth schemeflynet({ credentials }) per scheme
Discriminate the error envelopes by handFlynetError with typed .code
Parse WWW-Authenticate for OAuth 401/403 causesDone; reason surfaces as err.code
Track which routes need which headerMethods only accept the right credential’s client

What’s inside

// Two credentials, one discriminated union
export type FlynetCredentials =
  | { type: "oauth"; accessToken: string }
  | { type: "apikey"; apiKey: string };

// One client factory
export function flynet(opts: FlynetClientOptions): {
  request: <T>(path: string, init?: RequestInit) => Promise<T>;

  // Discovery (API key only)
  listRestaurants, getRestaurant, listRestaurantLocations,
  listLocations, getLocationOpenHours,

  // Member context (OAuth only): subject resolved from the token
  getMe, getMyStatus, getMyWallets, getMyTags, getMyCheckIns,
  listMemberships, listCheckIns,

  // Payments (OAuth + merchant_id)
  createPaymentIntent,
};

// One typed error class, with autocomplete on known codes
export class FlynetError extends Error {
  readonly status: number;
  readonly code?: FlynetErrorCode;  // "MISSING_API_KEY" | "invalid_token" | "payment0030" | ...
  readonly raw: unknown;
}

// Optional helper: decode the member UUID from the token (sub claim)
export function getAuthenticatedUserId(accessToken: string): string;

Configure

Most apps end up with both schemes in the same codebase. The file lets you spin up one client per scheme and never mix them up:
import { flynet, FlynetError } from "./flynet";

const discovery = flynet({
  credentials: { type: "apikey", apiKey: process.env.API_KEY! },
});

const member = flynet({
  credentials: { type: "oauth", accessToken: userAccessToken },
});
baseUrl defaults to staging. Override when shipping to production:
const discovery = flynet({
  baseUrl: "https://api.blackbird.xyz/flynet/v1",
  credentials: { type: "apikey", apiKey: process.env.API_KEY! },
});

Use

const restaurants = await discovery.listRestaurants({ page: 0, page_size: 20 });
const wallets = await member.getMyWallets();
const recentVisits = await member.getMyCheckIns({
  sort_order: "desc",
  page_size: 25,
});

console.log(restaurants.restaurants.length, wallets.wallets.length, recentVisits.check_ins.length);

Handle errors

FlynetError exposes a typed .code, so you can switch on the codes in Debugging:
try {
  const wallets = await member.getMyWallets();
} catch (err) {
  if (!(err instanceof FlynetError)) throw err;

  switch (err.code) {
    case "invalid_token":
      // expired or malformed: refresh and retry, then re-auth if that fails
      await refreshAccessToken();
      return retry();
    case "insufficient_scope":
      // token lacks read:wallets: re-authorize with the right scope set
      return reauth();
    case "resource_not_found":
      // no matching resource (e.g. no active status for the member)
      return notFoundUI();
    default:
      console.error(err.status, err.code, err.message);
      throw err;
  }
}

Identify the authenticated member

/users/me/* resolves the subject from the token, so you usually don’t need the UUID; member.getMe() returns the authenticated member directly. When you do need the UUID for your own state, the helper decodes it from the access token’s sub claim, the same pattern documented in OAuth → Identify the authenticated member:
import { getAuthenticatedUserId } from "./flynet";

const me = await member.getMe();        // full profile, no UUID needed
const userId = getAuthenticatedUserId(accessToken);  // UUID, if you need it
Decode once when you receive a fresh token; cache alongside the token in memory; re-decode on refresh.

What’s not included

flynet.ts is starter code, not a full SDK. Out of scope:
FeatureWhere to find it
401 → refresh → retry wrapperOAuth Step 3: the callApi wrapper
Refresh-token race-condition handling (singleton in-flight refresh)Add yourself; see OAuth Step 4 for the contract
Webhook verificationWebhooks ship post-launch; see FAQ
Per-method response typing (vs unknown)Generate from the OpenAPI spec; next section

Generated types

For exhaustively-typed responses, generate from the OpenAPI spec and swap unknown for the generated types:
npx openapi-typescript https://flynet-dev-portal.mintlify.app/api-reference/openapi.yaml \
  -o flynet-types.ts
Then adjust flynet.ts return types from unknown to paths["…"]["get"]["responses"]["200"]…. Stretch into a full SDK at your own pace. The file is yours.

Maintenance

flynet.ts is a reference implementation, not a maintained library. We update it when a breaking API change ships; track those in Changelog. If the snippet drifts from the live API, file an issue via Support.