{"id":15566,"library":"castle-api-client","title":"Ghost Server API Client","description":"The `castle-api-client` package functions as the official JavaScript/TypeScript client for interacting with the `ghost-server` API. It implements a custom JSON RPC-like protocol, making HTTP POST requests to a single endpoint (`/api`) with a JSON body containing `method` and `args` fields. This design enables a unified and simplified communication interface. The client is highly versatile, supporting a broad spectrum of JavaScript environments, including Node.js, modern web browsers, Electron applications, and React Native projects. Its primary function is centered around user identity and authentication, relying on deep integration with Expo user accounts. This strategic reliance streamlines authentication workflows for Expo-centric applications. A key differentiator for `castle-api-client` is its architectural independence: it was specifically developed to decouple `ghost-server` from larger, more monolithic web stacks, allowing for faster development cycles and greater agility in evolving its features. This client is currently at stable version 7.0.0. While no specific release cadence is publicly detailed, its design emphasizes responsiveness to evolving needs, offering a lightweight and efficient solution for secure, cross-platform communication within the Expo ecosystem, particularly beneficial for projects needing robust login and identity management without entanglement in complex legacy systems.","status":"active","version":"7.0.0","language":"javascript","source_language":"en","source_url":"https://github.com/expo/ghost-server","tags":["javascript"],"install":[{"cmd":"npm install castle-api-client","lang":"bash","label":"npm"},{"cmd":"yarn add castle-api-client","lang":"bash","label":"yarn"},{"cmd":"pnpm add castle-api-client","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"The primary class for interacting with the Ghost API. As of v7, the package is ESM-first, so CommonJS 'require' should be avoided for optimal compatibility and tooling.","wrong":"const GhostApiClient = require('castle-api-client');","symbol":"GhostApiClient","correct":"import { GhostApiClient } from 'castle-api-client';"},{"note":"A custom error class used for specific API-related issues returned by the client. It is a named export, not a default export, so curly braces are required.","wrong":"import APIClientError from 'castle-api-client';","symbol":"APIClientError","correct":"import { APIClientError } from 'castle-api-client';"},{"note":"TypeScript type definition for the client's configuration object. Use 'import type' for type-only imports to ensure they are stripped during compilation, preventing accidental runtime imports and optimizing bundle size.","wrong":"import { APIClientConfig } from 'castle-api-client';","symbol":"APIClientConfig","correct":"import type { APIClientConfig } from 'castle-api-client';"}],"quickstart":{"code":"import { GhostApiClient } from 'castle-api-client';\n\nasync function runGhostClientExample() {\n  const expoAuthToken = process.env.EXPO_AUTH_TOKEN ?? ''; // Securely retrieve your Expo auth token\n\n  if (!expoAuthToken) {\n    console.warn(\"EXPO_AUTH_TOKEN environment variable is not set. API calls requiring authentication may fail.\");\n  }\n\n  // Initialize the client with the server URL\n  const client = new GhostApiClient({\n    serverUrl: 'https://ghost-server.app.render.com/api',\n    // Further configuration options like custom fetch, timeouts, etc. can be added here\n  });\n\n  try {\n    console.log('Attempting to log in with Expo token...');\n    // The API uses a generic 'call' method with 'method' and 'args' fields\n    const loginResponse = await client.call({\n      method: 'auth.loginWithExpoToken', // Example: a hypothetical login method\n      args: { token: expoAuthToken },\n    });\n\n    if (loginResponse.error) {\n      console.error('Login failed:', loginResponse.error.message, loginResponse.clientError);\n      return;\n    }\n\n    console.log('Login successful. Result:', loginResponse.result);\n    const sessionToken = loginResponse.result?.sessionToken; // Assuming session token is returned\n\n    if (sessionToken) {\n      console.log('Fetching user profile...');\n      // Make another call using the obtained session token\n      const userProfile = await client.call({\n        method: 'user.getProfile', // Example: a hypothetical method to get user profile\n        args: { sessionToken: sessionToken },\n      });\n\n      if (userProfile.error) {\n        console.error('Failed to get user profile:', userProfile.error.message);\n        return;\n      }\n      console.log('User Profile:', userProfile.result);\n    }\n\n  } catch (error) {\n    console.error('An unexpected client error occurred:', error instanceof Error ? error.message : String(error));\n  }\n}\n\nrunGhostClientExample();","lang":"typescript","description":"Demonstrates initializing the Ghost API client, performing a hypothetical login using an Expo token, and subsequently fetching user profile information via the generic `call` method, handling potential errors."},"warnings":[{"fix":"Review the package's GitHub releases or documentation for migration instructions between major versions. Update import paths, method calls, and client configuration as required.","message":"Major version updates to `castle-api-client` (e.g., `v7.0.0`) typically introduce breaking changes to the API surface, configuration options, or internal behaviors. Users upgrading from `v6.x.x` or earlier should meticulously consult the official changelog or migration guides for specific details, as method signatures, constructor parameters, or error handling mechanisms may have changed significantly.","severity":"breaking","affected_versions":">=7.0.0"},{"fix":"Plan authentication strategy to align with Expo user accounts where possible. For alternative identity providers, consider implementing a custom authentication layer that translates into the `ghost-server` protocol or using a different API client.","message":"The client's identity and authentication mechanisms are tightly coupled with Expo user accounts. This means direct integration with other non-Expo identity providers or implementing entirely custom authentication flows might require significant custom implementation efforts or might not be supported out-of-the-box by this client.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Consult the `ghost-server` README or API documentation to understand the precise `method` names and the structure of `args` objects for each available operation before making calls.","message":"This `castle-api-client` uses a specific JSON RPC-like protocol where all requests are HTTP POST to a single endpoint with a JSON body containing `method` and `args`. Developers accustomed to RESTful, GraphQL, or other standard API paradigms should carefully review the `ghost-server` documentation for available methods and their expected argument structures to avoid malformed requests and unexpected errors.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Always use environment variables (e.g., `process.env.GHOST_API_URL`) to configure the `serverUrl` option when initializing `GhostApiClient`. This enables flexible deployment to different environments.","message":"The quickstart and README often provide a hardcoded production URL (`https://ghost-server.app.render.com/api`). For any production application, API endpoints should be stored in environment variables or a secure configuration management system. Hardcoding URLs can lead to deployment errors, security vulnerabilities, and difficulties in managing different environments (development, staging, production).","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Ensure `new GhostApiClient(...)` has been successfully executed before attempting to call any methods on the `client` object. Verify that `client` variable holds the instantiated `GhostApiClient`.","cause":"Attempting to invoke the `call` method on an uninitialized `GhostApiClient` instance, or `client` is not the expected object.","error":"Unhandled promise rejection: TypeError: client.call is not a function"},{"fix":"Verify that `EXPO_AUTH_TOKEN` (or the equivalent credential) is correctly set and passed to the client or the specific authentication method. Renew expired tokens if necessary.","cause":"The API call requires authentication, but a valid Expo authentication token was either missing, expired, or incorrectly provided in the request payload (e.g., via `auth.loginWithExpoToken` method).","error":"HTTP Error 401: Unauthorized"},{"fix":"Double-check the `serverUrl` provided to the `GhostApiClient` constructor. Confirm the `ghost-server` is running correctly and that no network proxies or firewalls are interfering with the JSON response. Inspect the raw HTTP response body if possible.","cause":"The `ghost-server` responded with non-JSON content, often an HTML error page, instead of the expected JSON. This can happen due to server-side errors, incorrect endpoint URLs, or network issues.","error":"SyntaxError: Unexpected token < in JSON at position 0"},{"fix":"Ensure that the first argument to `client.call` is an object that explicitly includes a `method` property with a valid, non-empty string value corresponding to an existing server method (e.g., `{ method: 'user.getProfile', args: {...} }`).","cause":"The object passed to `client.call` did not contain a `method` property, or its value was an empty string or not a string, which is required by the underlying JSON RPC protocol.","error":"Error: Missing or invalid 'method' field in request body"}],"ecosystem":"npm"}