Skip to main content

ApiClient

The ApiClient class is a lightweight HTTP client for interacting with the YouVersion Platform API using the native fetch API. It provides convenient methods for GET, POST, and DELETE requests with typed responses, automatic timeout handling, and error management. All specialized clients (BibleClient, LanguagesClient, HighlightsClient) use this client internally.

Constructor

import { ApiClient } from '@youversion/platform-core';

const client = new ApiClient(config);

Parameters

config
ApiConfig
required
Configuration object for the API client

Example

const apiClient = new ApiClient({
  appKey: 'your-app-key-here',
  timeout: 15000, // 15 seconds
  installationId: 'my-custom-id',
});

Properties

config
ApiConfig
The configuration object passed to the constructor. Publicly accessible.

Methods

get()

Sends a GET request to the specified API path with optional query parameters.
async get<T>(path: string, params?: QueryParams, headers?: RequestHeaders): Promise<T>
path
string
required
The API endpoint path relative to the base URL (e.g., /v1/bibles).
params
QueryParams
Optional query parameters to include in the request.Type: Record<string, string | number | boolean | Array<string | number | boolean>>
headers
RequestHeaders
Optional additional headers to include in the request.Type: Record<string, string>
Returns
Promise<T>
Promise resolving to the typed response data.

Example

import type { Collection, BibleVersion } from '@youversion/platform-core';

// Simple GET request
const versions = await apiClient.get<Collection<BibleVersion>>(
  '/v1/bibles',
  { 'language_ranges[]': ['en'] }
);

// With custom headers
const data = await apiClient.get('/v1/languages', 
  { page_size: 10 },
  { 'X-Custom-Header': 'value' }
);

post()

Sends a POST request to the specified API path with optional data and query parameters.
async post<T>(path: string, data?: RequestData, params?: QueryParams): Promise<T>
path
string
required
The API endpoint path relative to the base URL.
data
RequestData
Optional request body data to send as JSON.Type: Record<string, string | number | boolean | object>
params
QueryParams
Optional query parameters to include in the request URL.
Returns
Promise<T>
Promise resolving to the typed response data.

Example

import type { Highlight } from '@youversion/platform-core';

// Create a highlight
const highlight = await apiClient.post<Highlight>(
  '/v1/highlights',
  {
    version_id: 1,
    passage_id: 'JHN.3.16',
    color: 'ffff00'
  },
  { lat: 'access-token-here' }
);

delete()

Sends a DELETE request to the specified API path with optional query parameters.
async delete<T>(path: string, params?: QueryParams): Promise<T>
path
string
required
The API endpoint path relative to the base URL.
params
QueryParams
Optional query parameters to include in the request.
Returns
Promise<T>
Promise resolving to the response data (may be void for 204 responses).

Example

// Delete a highlight
await apiClient.delete<void>(
  '/v1/highlights/JHN.3.16',
  { 
    version_id: 1,
    lat: 'access-token-here' 
  }
);

Request Features

Automatic Headers

The client automatically includes these headers in every request:
  • Content-Type: application/json
  • X-YVP-App-Key: <your-app-key>
  • X-YVP-Installation-Id: <installation-id>

Timeout Handling

Requests automatically timeout after the configured duration (default 10 seconds):
const client = new ApiClient({
  appKey: 'key',
  timeout: 5000, // 5 seconds
});

try {
  const data = await client.get('/v1/bibles');
} catch (error) {
  // "Request timeout after 5000ms"
}

Error Handling

HTTP errors are thrown as Error objects with additional properties:
try {
  const data = await apiClient.get('/v1/invalid-endpoint');
} catch (error) {
  if (error instanceof Error) {
    console.error(error.message);
    
    // Access HTTP status
    const httpError = error as Error & { 
      status?: number; 
      statusText?: string;
    };
    
    if (httpError.status === 404) {
      console.log('Endpoint not found');
    }
  }
}
In development mode (NODE_ENV=development), error messages include detailed server responses. In production, error messages are sanitized.

Query Parameter Arrays

Array values in query parameters are automatically expanded:
// This request:
await apiClient.get('/v1/bibles', {
  'language_ranges[]': ['en', 'es', 'fr']
});

// Becomes: /v1/bibles?language_ranges[]=en&language_ranges[]=es&language_ranges[]=fr

Type Definitions

ApiConfig

client.ts:1
export interface ApiConfig {
  apiHost?: string;
  appKey: string;
  timeout?: number;
  installationId?: string;
}

QueryParams

client.ts:4
type QueryParams = Record<
  string,
  string | number | boolean | (string | number | boolean)[]
>;

RequestData

client.ts:5
type RequestData = Record<string, string | number | boolean | object>;

RequestHeaders

client.ts:6
type RequestHeaders = Record<string, string>;

Complete Example

import { ApiClient } from '@youversion/platform-core';
import type { Collection, Language } from '@youversion/platform-core';

// Initialize client
const apiClient = new ApiClient({
  appKey: process.env.YOUVERSION_APP_KEY!,
  timeout: 15000,
});

// GET request with query params
const languages = await apiClient.get<Collection<Language>>(
  '/v1/languages',
  { 
    page_size: 50,
    country: 'US' 
  }
);

console.log(`Found ${languages.data.length} languages`);

// Handle pagination
let nextToken = languages.next_page_token;
while (nextToken) {
  const nextPage = await apiClient.get<Collection<Language>>(
    '/v1/languages',
    { 
      page_size: 50,
      page_token: nextToken 
    }
  );
  
  console.log(`Next page: ${nextPage.data.length} languages`);
  nextToken = nextPage.next_page_token;
}

Best Practices

Create a single ApiClient instance and reuse it throughout your application to avoid unnecessary overhead:
// apiClient.ts
export const apiClient = new ApiClient({
  appKey: process.env.YOUVERSION_APP_KEY!,
});

// In other files
import { apiClient } from './apiClient';
const data = await apiClient.get('/v1/languages');
Instead of calling ApiClient methods directly, use the specialized clients that provide type-safe method signatures:
// ❌ Not recommended
const data = await apiClient.get('/v1/bibles/1');

// ✅ Recommended
const bibleClient = new BibleClient(apiClient);
const version = await bibleClient.getVersion(1);
Always wrap API calls in try-catch blocks and handle different error scenarios:
try {
  const data = await apiClient.get('/v1/endpoint');
} catch (error) {
  if (error instanceof Error) {
    const httpError = error as Error & { status?: number };
    
    if (httpError.status === 404) {
      // Handle not found
    } else if (httpError.status === 429) {
      // Handle rate limit
    } else if (error.message.includes('timeout')) {
      // Handle timeout
    } else {
      // Handle other errors
    }
  }
}
Adjust timeout based on expected response times:
// Fast endpoints
const quickClient = new ApiClient({
  appKey: 'key',
  timeout: 5000, // 5 seconds
});

// Slower endpoints or poor network conditions
const slowClient = new ApiClient({
  appKey: 'key',
  timeout: 30000, // 30 seconds
});

Build docs developers (and LLMs) love