> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://contentful.com/developers/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/developers/docs/_mcp/server.

# Shared SDK

> Work with events and types common to all Ninetailed implementations.

## Shared SDK: The NinetailedApiClient

The Shared SDK exposes Ninetailed data objects, types, and methods applicable across runtimes. The Shared SDK allows you to create an API Client that facilitates constructing and sending [Experience API](/personalization/experience-api) events. It also handles retrying requests and request errors.

Elements of the shared SDK are composed to create the [JavaScript SDK](/personalization/the-ninetailed-instance), which is then composed into Ninetailed's React-based SDKs.

For more information about the code, see the shared SDK in our [open source repository](https://github.com/ninetailed-inc/experience.js/tree/main/packages/sdks/shared/src/lib).

## Instantiation

```typescript
import { NinetailedApiClient, fetchImpl} from "@ninetailed/experience.js-shared"

const ninetailedApiClient = new NinetailedApiClient({ clientId, environment, url, fetchImpl, }: NinetailedApiClientOptions)

type NinetailedApiClientOptions = {
    clientId: string;
    environment?: string;
    url?: string;
    fetchImpl?: FetchImpl;
}
```

| Parameter     | Type Signature          | Description                                                                                                                                                                                                                                                                                                                                                                      |
| ------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clientId`    | `clientId: string`      | The organization ID/API key of a Contentful Personalization account.                                                                                                                                                                                                                                                                                                             |
| `environment` | `environment?: string`  | The environment key of a Contentful Personalization account. Typically either `main` or `development`, depending on the environment in which you have configured your content source connections. Defaults to `main` if unsupplied.                                                                                                                                              |
| `url`         | `url?: string`          | Specify the base URL of the Experience API. This should usually be left unspecified. Defaults to the production Experience API base URL of `https://experience.ninetailed.co` when unspecified.                                                                                                                                                                                  |
| `fetchImpl`   | `fetchImpl?: FetchImpl` | A `NinetailedApiClient` can be used in different JavaScript runtimes including the browser, Node.js, and edge workers. However, an implementation of the `fetch()` method available in the browser may not be available within all of those run times. This option allows you to provide a `fetch()` implementation of your own should the runtime not expose one automatically. |

## Profile methods

A `NinetailedAPIClient` maps one function for each Experience API endpoint. `upsertProfile` is also provided to conveniently switch between create and update functions.

| Method               | Type Signature                                                                                                                                                                                                         | Description                                                                                                                                                                                                 |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `upsertProfile`      | `upsertProfile(&#123; profileId, events &#125;: &#123; profileId?: string; events: Event[];&#125; options?: RequestOptions): Promise&lt;ProfileWithSelectedVariants&gt;`                                               | If a profile ID is not supplied, calls `updateProfile`. Otherwise, calls `createProfile`.                                                                                                                   |
| `createProfile`      | `createProfile(&#123; events &#125;: &#123; events: Event[];&#125;, options?: RequestOptions): Promise&lt;ProfileWithSelectedVariants&gt;`                                                                             | Creates a profile with the specified profile ID. Interfaces with the [create profile endpoint](/personalization/experience-api).                                                                            |
| `updateProfile`      | `createProfile(&#123; profileId, events &#125;: &#123; profileId: string, events: Event[];&#125;, options?: RequestOptions): Promise&lt;ProfileWithSelectedVariants&gt;`                                               | Creates a profile at the specified profile ID. Interfaces with the [update profile endpoint](/personalization/experience-api).                                                                              |
| `getProfile`         | `getProfile(id: string, options?: Omit&lt;RequestOptions, 'preflight' \| 'plaintext'&gt; ): Promise&lt;ProfileWithSelectedVariants&gt;`                                                                                | Get the profile with the specified profile ID. Interfaces with the [get profile endpoint](/personalization/experience-api).                                                                                 |
| `upsertManyProfiles` | `upsertManyProfiles(&#123;events&#125; : &#123;events: Event[], options?: &#123; timeout?: number \| undefined; enabledFeatures?: Feature[] \| undefined; &#125;&#125;): Promise&lt;ProfileWithSelectedVariants[]&gt;` | Supply an array of events to update many profiles at once. Responds with the representation of each upserted profile. Interfaces with the [batch upsert profile endpoint](/personalization/experience-api). |

The returned response of each method is the same as that of the [Experience API](/personalization/experience-api): a data structure indicating the complete representation of the profile(s) and the Ninetailed Experiences and variants that the Experience API has selected for the profile(s).

### Request options

All of profile methods accept request options in addition to the supplied events. These are used to control request timeout & retry behavior, performance, localization, and location resolution.

```typescript
type RequestOptions = {
    /**
     * A timeout after which a request will get cancelled
     */
    timeout?: number;
    /**
     * Return a profile as though events have been submitted without actually storing the profile state
     * Useful in ESR or SSR contexts
     */
    preflight?: boolean;
    /**
     * Determines the locale to which to localize location.city & location.country properties
     */
    locale?: string;
    /**
     * An IP address to override the IP lookup behavior, if using also supplying "ip-enrichment" as an enabled feature
     * Used in ESR or SSR environments to proxy a client's IP instead of using the server's IP
     */
    ip?: string;
    /**
     * Sending requests as plain-text bypasses the need for a CORS preflight request, making for a faster request.
     */
    plainText?: boolean;
    /**
     * The maximum amount of retries for a request.
     * Only 503 errors will be retried. The Ninetailed API is aware of which requests are retryable and send a 503 error.
     *
     * @default 1
     */
    retries?: number;
    /**
     * The maximum amount of time in ms to wait between retries.
     * By default the retry will be sent immediately as the Ninetailed API is serverless and sends a 503 error if it could not recover a request internally.
     *
     * @default 0 (no delay)
     */
    minRetryTimeout?: number;
    /**
     * Activated features which the API should use for this request.
     */
    enabledFeatures?: Feature[];
};

/**
 * "location" = Attempt to resolve the location of the user based on the IP of the request and where it ingresses to the Experience API
 * "ip-enrichment" = Attempt to resolve firmographic data with a connected Albacross API Key. See Setup => Customer Data => Albacross
 */
type Feature = "location" | "ip-enrichment"
```

## Event building functions

Each of the profile methods above accepts an array of events. Event building helper functions facilitate generating the required payload for `page`, `track`, and `identify` events. These builder methods take care of parsing the supplied `ctx.url` and populating `context` properties on events commonly used by Audience rules, including `context.page` and `context.campaign`.

```typescript
function buildPageEvent(data: {
    messageId: string; // A UUID
    timestamp: number; // Typically assigned as Date.now()
    ctx: {
        url: string; // Supply the whole URL, including the protocol, domain, path, and any query parameters
        referrer: string;
        locale: string;
        userAgent: string;
        document?: {
            title: string;
        } | undefined;
    }
    // Optionally supply an object to merge with API-resolved location
    location?: Geolocation;
    properties: Record<string, any>; // JSON
})

function buildTrackEvent(data: {
    messageId: string; // A UUID
    timestamp: number; // Typically assigned as Date.now()
    ctx: {
        url: string; // Supply the whole URL, including the protocol, domain, path, and any query parameters
        referrer: string;
        locale: string;
        userAgent: string;
        document?: {
            title: string;
        } | undefined;
    }
    // Optionally supply an object to merge with API-resolved location
    location?: Geolocation;
    event: string; // the name of the event
    properties: Record<string, any>; // JSON
})

function buildIdentifyEvent(data: {
    messageId: string; // A UUID
    timestamp: number; // Typically assigned as Date.now()
    ctx: {
        url: string; // Supply the whole URL, including the protocol, domain, path, and any query parameters
        referrer: string;
        locale: string;
        userAgent: string;
        document?: {
            title: string;
        } | undefined;
    }
    // Optionally supply an object to merge with API-resolved location
    location?: Geolocation;
    userId: string; // An alias. If you don't want to set one, use an empty string ""
    traits: Record<string, any>; // JSON
})

type Geolocation = {
      coordinates?: {
        latitude: number;
        longitude: number;
      },
      city?: string; // Use proper capitalization of the city name
      postalCode?: string;
      region?: string;
      regionCode?: string; // ISO 3166-2
      country?: string;
      countryCode?: string; // ISO 3166-2
      continent?: string;
      timezone?: string;
}
```

## Example shared SDK usage

This is a short example of using the `buildPageEvent` helper to create a well-formatted event of type `page` and using it to upsert a profile.

```typescript
import {buildPageEvent, buildTrackEvent, buildIdentifyEvent, NinetailedApiClient, NINETAILED_ANONYMOUS_ID_COOKIE } from "@ninetailed/experience.js-shared"

import { v4 as uuidv4 } from 'uuid';

const clientID = "YOUR_NINETAILED_CLIENT_ID" || undefined;
const apiClient = new NinetailedApiClient({ clientId });

const middleware = (req: Request) => {
  buildPageEvent({
    // Hardcoded strings are for example purposes
    // You'd populate these from the incoming Request object
    ctx: { 
      url: 'https://www.example.com/path/?query=test'
      locale: 'en-US',
      referrer: 'https://www.google.com/',
      userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
    }
    messageId: uuidv4(),
    timestamp: Date.now(),
    // Resolve your original request geolocation info and pass it along
    location: {
      city: request.geo?.city,
      region: request.geo?.region,
      country: request.geo?.country,
      continent: request.geo?.continent,
    } 
    properties: {},
  });
  
  const apiResponse = await apiClient.upsertProfile(
    {
      // Use cookies to store and read a Ninetailed profile ID
      // `upsertProfile` takes care of case where this isn't present
      profileId: req.cookies.get(NINETAILED_ANONYMOUS_ID_COOKIE)?.value 
      events: [{ pageEvent }],
    },
    { ip: req.ip } // Pass original IP address
  );
  
  // If no profile ID cookie present, be sure to write it in a response
  return apiResponse;
}
```

Despite the brevity of this example, this is all it takes to get started [working with Ninetailed in edge functions and middleware](https://github.com/ninetailed-inc/ninetailed-examples/blob/main/marketing-contentful-next-app-all-in/middleware.ts#L31).