From navan-pack
Manages Navan travel booking lifecycle via REST API: OAuth auth, retrieve/filter trips by date/status, download itineraries/PDFs/invoices.
How this skill is triggered — by the user, by Claude, or both
Slash command
/navan-pack:navan-core-workflow-aThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill provides the complete travel booking workflow through the Navan REST API. Navan has no public SDK — all access is via raw HTTP calls using OAuth 2.0 bearer tokens. This skill covers trip retrieval for both user and admin scopes, itinerary PDF downloads, invoice access, and trip filtering by date range and status. Every booking is keyed by a UUID primary key that must be tracked for d...
This skill provides the complete travel booking workflow through the Navan REST API. Navan has no public SDK — all access is via raw HTTP calls using OAuth 2.0 bearer tokens. This skill covers trip retrieval for both user and admin scopes, itinerary PDF downloads, invoice access, and trip filtering by date range and status. Every booking is keyed by a UUID primary key that must be tracked for deduplication and updates.
/ta-auth/oauth/token (see navan-install-auth)node-fetch or Python 3.8+ with requestsNAVAN_CLIENT_ID, NAVAN_CLIENT_SECRET, NAVAN_BASE_URLNAVAN_BASE_URL should be set to https://api.navan.comconst tokenRes = await fetch(`${process.env.NAVAN_BASE_URL}/ta-auth/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.NAVAN_CLIENT_ID!,
client_secret: process.env.NAVAN_CLIENT_SECRET!,
}),
});
const { access_token } = await tokenRes.json();
const headers = { Authorization: `Bearer ${access_token}` };
// GET /v1/bookings — returns booking records (paginated via page + size)
// Response: records in .data array, primary key uuid
const bookingsRes = await fetch(
`${process.env.NAVAN_BASE_URL}/v1/bookings?page=0&size=50`,
{ headers }
);
const { data: bookings } = await bookingsRes.json();
bookings.forEach((booking: any) => {
console.log(`UUID: ${booking.uuid}`);
console.log(` Route: ${booking.origin} -> ${booking.destination}`);
console.log(` Status: ${booking.status}`);
console.log(` Dates: ${booking.start_date} to ${booking.end_date}`);
});
// GET /v1/bookings with createdFrom/createdTo for incremental pulls
const filteredRes = await fetch(
`${process.env.NAVAN_BASE_URL}/v1/bookings?createdFrom=2026-01-01&createdTo=2026-03-31&page=0&size=50`,
{ headers }
);
const { data: filteredBookings } = await filteredRes.json();
console.log(`Total bookings in range: ${filteredBookings.length}`);
// Paginate using page + size query params (start from page 0, page_size 50)
async function getAllBookings(): Promise<any[]> {
const allBookings: any[] = [];
let page = 0;
const size = 50;
while (true) {
const res = await fetch(
`${process.env.NAVAN_BASE_URL}/v1/bookings?page=${page}&size=${size}`,
{ headers }
);
const { data } = await res.json();
if (!data || data.length === 0) break;
allBookings.push(...data);
if (data.length < size) break; // last page
page++;
}
return allBookings;
}
const allBookings = await getAllBookings();
console.log(`Total bookings: ${allBookings.length}`);
// Re-authenticate by requesting a new token (same client_credentials flow)
const refreshRes = await fetch(`${process.env.NAVAN_BASE_URL}/ta-auth/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.NAVAN_CLIENT_ID!,
client_secret: process.env.NAVAN_CLIENT_SECRET!,
}),
});
const refreshed = await refreshRes.json();
console.log('Token refreshed successfully');
Successful execution returns:
.data array with UUID primary keys, origin/destination, dates, and statuspage + size query paramscreatedFrom / createdTo params| Error | HTTP Code | Cause | Solution |
|---|---|---|---|
| Unauthorized | 401 | Expired or invalid bearer token | Re-authenticate via POST /ta-auth/oauth/token |
| Forbidden | 403 | Insufficient API scope | Verify credentials have correct permissions |
| Not Found | 404 | Invalid endpoint or UUID | Confirm endpoint path starts with /v1/ |
| Rate Limited | 429 | Too many requests | Implement exponential backoff (start at 1s) |
| Server Error | 500 | Navan platform issue | Retry with backoff; check Navan status page |
Python — Retrieve trips with date filtering:
import requests
import os
base_url = os.environ.get('NAVAN_BASE_URL', 'https://api.navan.com')
# Authenticate (form-encoded, not JSON)
auth = requests.post(f'{base_url}/ta-auth/oauth/token', data={
'grant_type': 'client_credentials',
'client_id': os.environ['NAVAN_CLIENT_ID'],
'client_secret': os.environ['NAVAN_CLIENT_SECRET'],
})
token = auth.json()['access_token']
headers = {'Authorization': f'Bearer {token}'}
# Get bookings for Q1 (records in .data array)
resp = requests.get(
f'{base_url}/v1/bookings',
params={'createdFrom': '2026-01-01', 'createdTo': '2026-03-31', 'page': 0, 'size': 50},
headers=headers,
).json()
for booking in resp['data']:
print(f"{booking['uuid']}: {booking.get('origin')} -> {booking.get('destination')}")
After retrieving trip data, proceed to navan-core-workflow-b for expense management or navan-data-handling for bulk data extraction patterns.
npx claudepluginhub luxdevnet/claude-plus-lux --plugin navan-pack5plugins reuse this skill
First indexed Jul 10, 2026
Manages the complete Navan travel booking lifecycle via REST API. Use for building travel dashboards, automating trip reporting, or syncing booking data to internal systems.
Guides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.