Skip to main content
navan-sdk-patterns Build a typed API wrapper around Navan REST endpoints since no official SDK exists.
Use when you need production-grade API access with auto token refresh, retry logic, and typed responses.
Trigger with "navan sdk patterns", "navan api wrapper", "navan client class", "navan typed client".
الانتقال إلى التثبيت سوق المهارات اكتشف واستكشف مهارات الذكاء الاصطناعي التي بناها المجتمع.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
نسخ Promptعرض تفاصيل Prompt يتجاوز الأمر المباشر Prompt المخصّص للمراجعة. افحص المصدر قبل تشغيله.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill navan-sdk-patternsيبقى الأمر في سطر واحد. مرّر أفقيًا لمراجعته كاملًا قبل النسخ.
تفضّل نسخة محلية؟ نزّل الملفات المتاحة حاليًا لدى SkillsMP.
تحميل Zip جاري التحميل... المزيد من هذا المستودع Implement user sign-up and sign-in flows with Clerk.
Use when building authentication UI, customizing sign-in experience,
or implementing OAuth social login.
Trigger with phrases like "clerk sign-in", "clerk sign-up",
"clerk login flow", "clerk OAuth", "clerk social login".
Implement session management and middleware with Clerk.
Use when managing user sessions, configuring route protection,
or implementing token refresh and custom JWT templates.
Trigger with phrases like "clerk session", "clerk middleware",
"clerk route protection", "clerk token", "clerk JWT".
Configure enterprise SSO, role-based access control, and organization management.
Use when implementing SSO integration, configuring role-based permissions,
or setting up organization-level controls.
Trigger with phrases like "clerk SSO", "clerk RBAC",
"clerk enterprise", "clerk roles", "clerk permissions", "clerk organizations".
المهن ذات الصلة SOC
استنادا إلى تصنيف SOC المهني
name navan-sdk-patterns description Build a typed API wrapper around Navan REST endpoints since no official SDK exists.
Use when you need production-grade API access with auto token refresh, retry logic, and typed responses.
Trigger with "navan sdk patterns", "navan api wrapper", "navan client class", "navan typed client".
allowed-tools Read, Write, Edit, Bash(npm:*), Grep version 1.7.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io> tags ["saas","navan","travel"] compatibility Designed for Claude Code
Navan SDK Patterns
Overview
Build a typed API wrapper around Navan REST endpoints since no official SDK exists (@navan/sdk is not a real package). These patterns provide automatic token lifecycle management, typed responses, retry middleware, and centralized error handling.
Purpose: Create a reusable NavanAPI class encapsulating authentication, request handling, and error recovery.
Prerequisites
Completed navan-install-auth with working OAuth 2.0 credentials
TypeScript 5+ project with dotenv installed
Familiarity with the Navan endpoints from navan-hello-world
Instructions
Step 1: Define Response Interfaces
Type the known API response shapes so every call returns structured data:
export interface NavanTrip {
uuid : string ;
traveler_name : string ;
origin : string ;
destination : string ;
departure_date : string ;
return_date : string ;
booking_status : string ;
booking_type : 'flight' | 'hotel' | 'car' ;
}
export interface NavanUser {
id : string ;
email : string ;
first_name : string ;
last_name : string ;
department : string ;
role : ;
}
{
: ;
: ;
: ;
: ;
}
string
export
interface
NavanApiError
status
number
message
string
endpoint
string
timestamp
string
Step 2: Build the NavanAPI Wrapper Class Create a singleton client with automatic token management:
import 'dotenv/config' ;
import type { NavanTrip , NavanUser , NavanApiError } from './navan-types' ;
export class NavanAPI {
private baseUrl : string ;
private clientId : string ;
private clientSecret : string ;
private accessToken : string | null = null ;
private tokenExpiry : number = 0 ;
constructor ( ) {
this .baseUrl = process.env .NAVAN_BASE_URL ?? 'https://api.navan.com' ;
this .clientId = process.env .NAVAN_CLIENT_ID ?? '' ;
this .clientSecret = process.env .NAVAN_CLIENT_SECRET ?? '' ;
if (!this .clientId || !this .clientSecret ) {
throw new Error ('NAVAN_CLIENT_ID and NAVAN_CLIENT_SECRET must be set' );
}
}
private async authenticate (): Promise <string > {
if (this .accessToken && Date .now () < this .tokenExpiry ) {
return this .accessToken ;
}
const response = await fetch (`${this .baseUrl} /ta-auth/oauth/token` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/x-www-form-urlencoded' },
body : new URLSearchParams ({
grant_type : 'client_credentials' ,
client_id : this .clientId ,
client_secret : this .clientSecret ,
}),
});
if (!response.ok ) {
throw this .toApiError (response.status , 'Authentication failed' , '/ta-auth/oauth/token' );
}
const data = await response.json ();
this .accessToken = data.access_token ;
this .tokenExpiry = Date .now () + (data.expires_in - 60 ) * 1000 ;
return this .accessToken !;
}
private async request<T>(endpoint : string , options : RequestInit = {}): Promise <T> {
const token = await this .authenticate ();
const url = `${this .baseUrl} ${endpoint} ` ;
for (let attempt = 0 ; attempt < 3 ; attempt++) {
const response = await fetch (url, {
...options,
headers : {
Authorization : `Bearer ${token} ` ,
'Content-Type' : 'application/json' ,
...options.headers ,
},
});
if (response.ok ) return response.json () as Promise <T>;
if ((response.status === 429 || response.status === 503 ) && attempt < 2 ) {
const delay = Math .pow (2 , attempt + 1 ) * 1000 ;
await new Promise ((r ) => setTimeout (r, delay));
continue ;
}
if (response.status === 401 && attempt < 2 ) {
this .accessToken = null ;
this .tokenExpiry = 0 ;
continue ;
}
throw this .toApiError (response.status , await response.text (), endpoint);
}
throw this .toApiError (0 , 'Max retries exceeded' , endpoint);
}
private toApiError (status : number , message : string , endpoint : string ): NavanApiError {
return { status, message, endpoint, timestamp : new Date ().toISOString () };
}
async getBookings (page = 0 , size = 50 ): Promise <NavanTrip []> {
const data = await this .request <{ data : NavanTrip [] }>(`/v1/bookings?page=${page} &size=${size} ` );
return data.data ?? [];
}
async getUsers (): Promise <NavanUser []> {
const data = await this .request <{ data : NavanUser [] }>('/v1/users' );
return data.data ?? [];
}
}
Step 3: Implement a Singleton Factory Ensure one client instance per process to reuse the token:
import { NavanAPI } from './navan-client' ;
let instance : NavanAPI | null = null ;
export function getNavanClient ( ): NavanAPI {
if (!instance) instance = new NavanAPI ();
return instance;
}
const navan = getNavanClient ();
const bookings = await navan.getBookings ();
const users = await navan.getUsers ();
Step 4: Add Error Handling Middleware Wrap API calls with structured error handling for calling code:
import type { NavanApiError } from './navan-types' ;
type Result <T> = { ok : true ; data : T } | { ok : false ; error : NavanApiError };
export async function safeCall<T>(fn : () => Promise <T>): Promise <Result <T>> {
try {
const data = await fn ();
return { ok : true , data };
} catch (err) {
const apiError = err as NavanApiError ;
console .error (`Navan API error [${apiError.status} ] ${apiError.endpoint} : ${apiError.message} ` );
return { ok : false , error : apiError };
}
}
const result = await safeCall (() => navan.getUserTrips ());
if (result.ok ) {
console .log (`Found ${result.data.length} trips` );
} else {
console .error (`Failed: ${result.error.message} ` );
}
Step 5: Python Equivalent Pattern
import os
import time
import requests
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
@dataclass
class NavanAPIError (Exception ):
status: int
message: str
endpoint: str
class NavanAPI :
def __init__ (self ):
self .base_url = os.environ.get("NAVAN_BASE_URL" , "https://api.navan.com" )
self .client_id = os.environ["NAVAN_CLIENT_ID" ]
self .client_secret = os.environ["NAVAN_CLIENT_SECRET" ]
self ._token: str | None = None
self ._token_expiry: float = 0
def _authenticate (self ) -> str :
if self ._token and time.time() < self ._token_expiry:
return self ._token
resp = requests.post(f"{self.base_url} /ta-auth/oauth/token" , data={
"grant_type" : "client_credentials" ,
"client_id" : self .client_id,
"client_secret" : self .client_secret,
})
resp.raise_for_status()
data = resp.json()
self ._token = data["access_token" ]
self ._token_expiry = time.time() + data.get("expires_in" , 3600 ) - 60
return self ._token
def _request (self, method: str , endpoint: str , **kwargs ) -> dict :
token = self ._authenticate()
for attempt in range (3 ):
resp = requests.request(method, f"{self.base_url} {endpoint} " ,
headers={"Authorization" : f"Bearer {token} " }, **kwargs)
if resp.ok:
return resp.json()
if resp.status_code in (429 , 503 ) and attempt < 2 :
time.sleep(2 ** (attempt + 1 ))
continue
if resp.status_code == 401 and attempt < 2 :
self ._token = None
token = self ._authenticate()
continue
raise NavanAPIError(resp.status_code, resp.text, endpoint)
raise NavanAPIError(0 , "Max retries exceeded" , endpoint)
def get_bookings (self, page: int = 0 , size: int = 50 ) -> list [dict ]:
return self ._request("GET" , f"/v1/bookings?page={page} &size={size} " ).get("data" , [])
def get_users (self ) -> list [dict ]:
return self ._request("GET" , "/v1/users" ).get("data" , [])
Output Successful implementation produces:
A typed NavanAPI class with automatic token refresh and retry logic
Response interfaces for trips, users, and errors
A singleton factory for client reuse across the application
A safeCall wrapper for structured error handling in calling code
Error Handling Error Code Cause Solution Invalid credentials 401 Expired token or wrong secrets Client auto-retries with fresh token; check .env Forbidden 403 Insufficient permissions or wrong tier Verify admin role; check Navan plan tier Not found 404 Invalid endpoint path Verify endpoint against known paths in this guide Rate limited 429 Too many requests Client auto-retries with exponential backoff Server error 500 Navan service issue Client auto-retries; escalate if persistent Maintenance 503 Navan downtime Client auto-retries; check for scheduled windows
Examples Fetch all bookings with error handling:
const navan = getNavanClient ();
const result = await safeCall (() => navan.getBookings ());
if (result.ok ) {
const flights = result.data .filter ((t ) => t.booking_type === 'flight' );
console .log (`${flights.length} flight bookings found` );
}
Resources
Next Steps With your typed wrapper in place, see navan-common-errors for a comprehensive error reference, or navan-local-dev-loop to set up token caching and request logging for development.