Navan Local Dev Environment

VerifiedCaution

Set up a local development environment for Navan API integrations with token caching and request logging.

Sby Skills Guide Bot
DevelopmentIntermediate
9/8/2026
Claude Code
#navan#api-integration#local-development#oauth#token-caching

Recommended for

Automated summary

Sets up a local development environment for Navan API integrations with OAuth token caching, project structure, and mock fixture preparation.

Strengths

  • Provides a clear project layout that separates API client code, types, tests, logs, and environment files.
  • Includes a concrete token cache implementation with expiry handling and restrictive file permissions.
  • Covers basic safeguards for keeping secrets, logs, and token caches out of version control.
  • Acknowledges Navan’s lack of a sandbox by suggesting fixtures to reduce production API calls during iteration.

Limitations

  • Request logging and fixture usage are mostly outlined through directory structure and environment flags rather than full implementation code.
  • Assumes that Navan OAuth authentication and an existing API client are already in place.
  • Because Navan has no sandbox, misconfiguration can still send requests to production with real data.
When to use it

Use it when starting a Navan integration project or when you need a safer local setup for debugging API calls.

When not to use it

Avoid it if you need a complete offline testing guide or do not yet have valid Navan OAuth credentials.

Security analysis

Caution
Quality score76/100

The skill is a legitimate local dev-setup guide with no malicious payloads, but it operates against a production-only API, caches bearer tokens in plaintext, and instructs recording real booking/user responses into fixture files without PII handling or gitignore coverage — worth a caution rather than risky, since everything is transparent and aimed at reducing production calls.

Findings
  • •Persists the OAuth access token in plaintext on disk at `.token-cache` (mode 0o600 and gitignored, but still a bearer token at rest with no encryption/expiry purging beyond timestamp check).
  • •Step 7 `record-fixtures.ts` deliberately calls live production Navan endpoints (the skill notes Navan has no sandbox) and writes raw response bodies — booking records with traveler names/dates and `/v1/users` data — into `tests/fixtures/`. Fixtures are not added to `.gitignore` and no PII scrubbing step is provided, so real customer data can be committed to a repo.
  • •Request logger writes a 200-character response preview plus full URL to `logs/navan-api.log`; redaction only covers `client_secret` in query strings, so response-derived PII can land in logs (gitignored, but no rotation/purge).
  • •Several instructed commands fall outside the declared `Bash(npm:*)` tool scope (`echo ".env" >> .gitignore`, `tail -f logs/navan-api.log | python3 -m json.tool`), indicating the declared tool list understates what the skill asks the agent to run.
  • •No destructive, exfiltration, obfuscation, or safety-disabling behavior; network use is confined to the documented Navan API host taken from env.
  • •Step 7 ('Recording Fixtures from Production') pulls real /v1/bookings and /v1/users responses, which contain traveler PII (names, itineraries), and writes them verbatim into tests/fixtures/ — a directory that is not covered by the .gitignore guidance, so production personal data can easily be committed to a repo. No scrubbing/anonymization step is included.
  • •The OAuth access token is persisted in plaintext to .token-cache on disk (mitigated by mode 0o600 and a gitignore instruction, but still a recoverable bearer credential on the workstation).
  • •Request logging writes a 200-char response_preview of every API response to logs/navan-api.log; client_secret in URLs is redacted, but response bodies (PII) are not, and the log tail command outputs them to the terminal.
  • •Navan has no sandbox, so the workflow's 'live' scripts send real credentials and traffic to the production API; the skill does document this and offers an offline-fixture mode as mitigation.
How this analysis is made

Examples

Set up Navan local dev environment
Set up a local Navan API dev environment in this repository with token caching, request logging, and a .env.example file.
Scaffold Navan project and token cache
Create a .env.example, update .gitignore, scaffold the Navan integration project structure, and add the token cache implementation.
Debug Navan calls with fixtures
Help me debug Navan API calls locally using cached OAuth tokens and fixtures so I do not hit production on every run.

name: navan-local-dev-loop description: 'Set up a local development environment for Navan API integrations with token caching and request logging.

Use when starting a new Navan project or debugging API issues locally.

Trigger with "navan local dev", "navan dev setup", "navan local dev loop", "navan dev environment".

' allowed-tools: Read, Write, Edit, Bash(npm:*), Grep version: 1.8.0 license: MIT author: Jeremy Longshore jeremy@intentsolutions.io tags:

  • saas
  • navan
  • travel compatibility: Designed for Claude Code

Navan Local Dev Loop

Overview

Configure a local development environment for Navan API integrations with token caching, request logging, and mock fixtures. Navan has no sandbox — all API calls hit production, making a structured local setup essential.

Purpose: Establish a safe local dev workflow that minimizes production API calls during iteration.

Prerequisites

  • Completed navan-install-auth with working OAuth 2.0 credentials
  • Node.js 18+ with tsx for TypeScript execution
  • .env file with NAVAN_CLIENT_ID, NAVAN_CLIENT_SECRET, NAVAN_BASE_URL

Instructions

Step 1: Project Structure

Set up a clean project layout that separates concerns:

my-navan-integration/
├── .env                    # Credentials (NEVER commit)
├── .env.example            # Template for teammates
├── .gitignore              # Must include .env, .token-cache, logs/
├── src/
│   ├── navan-client.ts     # API wrapper (from navan-sdk-patterns)
│   ├── navan-types.ts      # Response interfaces
│   └── index.ts            # Entry point
├── tests/
│   ├── fixtures/           # Recorded API responses for offline dev
│   │   ├── bookings.json
│   │   └── users.json
│   └── navan-client.test.ts
├── logs/                   # Request/response logs (gitignored)
├── .token-cache            # Cached OAuth token (gitignored)
├── package.json
└── tsconfig.json

Step 2: Environment Configuration

Create .env.example as a safe template and enforce .gitignore:

# .env.example — commit this file, NOT .env
NAVAN_CLIENT_ID="your-client-id"
NAVAN_CLIENT_SECRET="your-client-secret"
NAVAN_BASE_URL="https://api.navan.com"
NAVAN_LOG_REQUESTS="true"
NAVAN_USE_FIXTURES="false"
# .gitignore additions for Navan projects
echo ".env" >> .gitignore
echo ".token-cache" >> .gitignore
echo "logs/" >> .gitignore

Step 3: Token Cache Implementation

Persist tokens to disk to avoid hitting /ta-auth/oauth/token on every script run:

// src/token-cache.ts
import { readFileSync, writeFileSync, existsSync } from 'fs';

interface CachedToken {
  access_token: string;
  expires_at: number; // Unix timestamp in ms
}

const CACHE_FILE = '.token-cache';

export function getCachedToken(): string | null {
  if (!existsSync(CACHE_FILE)) return null;
  try {
    const cached: CachedToken = JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));
    if (Date.now() < cached.expires_at - 60_000) {
      return cached.access_token;
    }
  } catch { /* corrupt cache, re-auth */ }
  return null;
}

export function setCachedToken(token: string, expiresIn: number): void {
  const cached: CachedToken = {
    access_token: token,
    expires_at: Date.now() + expiresIn * 1000,
  };
  writeFileSync(CACHE_FILE, JSON.stringify(cached), { mode: 0o600 });
}

Integrate with authentication:

import { getCachedToken, setCachedToken } from './token-cache';

async function getNavanToken(): Promise<string> {
  const cached = getCachedToken();
  if (cached) return cached;

  const response = 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!,
    }),
  });
  if (!response.ok) throw new Error(`Auth failed: ${response.status}`);

  const data = await response.json();
  setCachedToken(data.access_token, data.expires_in);
  return data.access_token;
}

Step 4: Request Logger

Log all API requests and responses for debugging without exposing secrets:

// src/request-logger.ts
import { appendFileSync, mkdirSync, existsSync } from 'fs';

const LOG_DIR = 'logs';
const LOG_FILE = `${LOG_DIR}/navan-api.log`;

export function logRequest(method: string, url: string, status: number, durationMs: number, body?: string): void {
  if (process.env.NAVAN_LOG_REQUESTS !== 'true') return;
  if (!existsSync(LOG_DIR)) mkdirSync(LOG_DIR, { recursive: true });

  const entry = {
    timestamp: new Date().toISOString(),
    method,
    url: url.replace(/client_secret=[^&"]+/g, 'client_secret=***'),
    status,
    duration_ms: durationMs,
    response_preview: body ? body.substring(0, 200) : undefined,
  };
  appendFileSync(LOG_FILE, JSON.stringify(entry) + '\n');
}

// Usage in API wrapper
const start = Date.now();
const response = await fetch(url, options);
const body = await response.text();
logRequest('GET', url, response.status, Date.now() - start, body);

Step 5: Mock Fixtures for Offline Development

Record real API responses and replay them during development:

// tests/fixtures/bookings.json
{
  "data": [
    {
      "uuid": "trip-001-abc-def",
      "traveler_name": "Jane Smith",
      "origin": "SFO",
      "destination": "JFK",
      "departure_date": "2026-04-01",
      "return_date": "2026-04-05",
      "booking_status": "confirmed",
      "booking_type": "flight"
    }
  ]
}

Use fixtures when NAVAN_USE_FIXTURES is set:

// src/fixture-loader.ts
import { readFileSync } from 'fs';
import { join } from 'path';

const FIXTURE_DIR = join(__dirname, '..', 'tests', 'fixtures');

export function loadFixture<T>(endpoint: string): T | null {
  if (process.env.NAVAN_USE_FIXTURES !== 'true') return null;
  const filename = endpoint.replace(/^\//, '').replace(/\//g, '_') + '.json';
  try {
    return JSON.parse(readFileSync(join(FIXTURE_DIR, filename), 'utf-8'));
  } catch {
    return null;
  }
}

// In the API wrapper, check fixtures first
async function request<T>(endpoint: string): Promise<T> {
  const fixture = loadFixture<T>(endpoint);
  if (fixture) return fixture;
  // ... real API call
}

Step 6: Dev Scripts

Configure package.json for a fast iteration loop:

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "dev:offline": "NAVAN_USE_FIXTURES=true tsx watch src/index.ts",
    "test": "vitest",
    "test:live": "NAVAN_USE_FIXTURES=false vitest",
    "record": "NAVAN_LOG_REQUESTS=true tsx src/record-fixtures.ts",
    "logs": "tail -f logs/navan-api.log | python3 -m json.tool"
  }
}

Step 7: Recording Fixtures from Production

Create a one-time script to capture real responses for offline use:

// src/record-fixtures.ts
import { writeFileSync, mkdirSync } from 'fs';

const FIXTURE_DIR = 'tests/fixtures';
mkdirSync(FIXTURE_DIR, { recursive: true });

const token = await getNavanToken();
const endpoints = ['/v1/bookings?page=0&size=50', '/v1/users'];

for (const endpoint of endpoints) {
  const response = await fetch(`${process.env.NAVAN_BASE_URL}${endpoint}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (response.ok) {
    const data = await response.json();
    const filename = endpoint.replace(/^\//, '') + '.json';
    writeFileSync(`${FIXTURE_DIR}/${filename}`, JSON.stringify(data, null, 2));
    console.log(`Recorded ${endpoint} -> ${filename}`);
  } else {
    console.error(`Failed to record ${endpoint}: ${response.status}`);
  }
}

Output

Successful setup produces:

  • A project scaffold with proper secret isolation (.env, .gitignore)
  • Token caching that avoids redundant auth calls to production
  • Request/response logging for debugging with secret redaction
  • Mock fixtures for offline development without production API calls
  • Dev scripts for live, offline, and recording modes

Error Handling

| Error | Code | Cause | Solution | |-------|------|-------|----------| | Unauthorized | 401 | Cached token expired | Delete .token-cache and re-authenticate | | Forbidden | 403 | Credentials lack required scope | Regenerate credentials with proper permissions | | Not found | 404 | Fixture file missing for endpoint | Record fixtures with npm run record | | Rate limited | 429 | Too many dev iterations hitting production | Switch to npm run dev:offline | | Server error | 500 | Navan service issue | Use fixtures; retry later | | Maintenance | 503 | Navan downtime | Use NAVAN_USE_FIXTURES=true for offline mode |

Examples

Typical dev workflow:

# 1. First time: record fixtures from production
npm run record

# 2. Daily development: use offline mode
npm run dev:offline

# 3. Integration testing: hit production
npm run test:live

# 4. Debug a failure: check logs
npm run logs

Resources

Next Steps

With your local dev environment running, see navan-sdk-patterns for production-grade wrapper patterns, or navan-common-errors when you encounter API failures during development.

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/navan-local-dev-environment-mts7ikop/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/navan-local-dev-environment-mts7ikop' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/navan-local-dev-environment-mts7ikop/download' --output '.skill-downloads/navan-local-dev-environment-mts7ikop/SKILL.md.part' && mv '.skill-downloads/navan-local-dev-environment-mts7ikop/SKILL.md.part' '.skill-downloads/navan-local-dev-environment-mts7ikop/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

Environment declared by the author: Designed for Claude Code

Tools declared by the author (check availability in your agent): Read, Write, Edit, Bash(npm:*), Grep

Original excerpt: Prerequisites
  • Completed navan-install-auth with working OAuth 2.0 credentials
  • Node.js 18+ with tsx for TypeScript execution
  • .env file with NAVAN_CLIENT_ID, NAVAN_CLIENT_SECRET, NAVAN_BASE_URL

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.

Claude Code
test -f '.skill-downloads/navan-local-dev-environment-mts7ikop/SKILL.md' && test -s '.skill-downloads/navan-local-dev-environment-mts7ikop/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/navan-local-dev-loop' && cp -R '.skill-downloads/navan-local-dev-environment-mts7ikop' '.claude/skills/navan-local-dev-loop'

OpenSkills

OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.

Official OpenSkills installation
openskills install './.skill-downloads/navan-local-dev-environment-mts7ikop'

openskills read 'navan-local-dev-environment-mts7ikop'

Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.

For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.

openskills sync

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/navan-local-dev-environment-mts7ikop/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills