> 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.

# Using in your app

> A guide to consuming the Destinations Delivery API and Experience Delivery API in your application.

Destinations can be consumed in the following ways:

* During static site generation.
* At render time using the client's path.
* With a hard-coded path or destination node ID.

## Static site generation

To statically generate an entire site for a destination, first retrieve its sitemap:

```bash
curl --request GET \
  --url "https://xdn.contentful.com/spaces/{space_id}/destinations/{destination_id}/sitemap" \
  --header "Authorization: Bearer {access_token}"
```

The response contains one page of resolved paths and experience URNs:

```json
{
  "property": {
    "sys": {
      "type": "Destination"
    }
  },
  "paths": {
    "sys": {
      "type": "Array"
    },
    "limit": 100,
    "items": [
      {
        "sys": {
          "type": "DestinationExperienceNode"
        },
        "path": "/",
        "experienceUrn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/homepage"
      },
      {
        "sys": {
          "type": "DestinationExperienceNode"
        },
        "path": "/products",
        "experienceUrn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/productIndex"
      },
      {
        "sys": {
          "type": "DestinationExperienceNode"
        },
        "path": "/products/shoes",
        "experienceUrn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/shoeIndex"
      },
      {
        "sys": {
          "type": "DestinationExperienceNode"
        },
        "path": "/products/shoes/running",
        "experienceUrn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/runningShoes"
      },
      {
        "sys": {
          "type": "DestinationExperienceNode"
        },
        "path": "/products/shoes/hiking",
        "experienceUrn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/hikingShoes"
      },
      {
        "sys": {
          "type": "DestinationExperienceNode"
        },
        "path": "/products/accessories",
        "experienceUrn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/accessories"
      },
      {
        "sys": {
          "type": "DestinationExperienceNode"
        },
        "path": "/about",
        "experienceUrn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/aboutPage"
      },
      {
        "sys": {
          "type": "DestinationExperienceNode"
        },
        "path": "/contact",
        "experienceUrn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/contactPage"
      }
    ],
    "pages": {}
  }
}
```

From here, there are two ways to retrieve each experience:

* Resolve each path through the Destinations Delivery API.
* Extract the experience ID from `experienceUrn` and request it from the Experience Delivery API.

These approaches differ in how they use the destination context.

### Using the Destinations Delivery API (recommended)

The Destinations Delivery API resolves an experience within its destination context. When an experience is published, its delivery artifacts are updated asynchronously. A destination node tracks which delivery artifacts belong to its linked experience, so resolving through the destination keeps the node and experience artifacts consistent.

Prefer this approach unless measured performance requirements justify bypassing the destination context.

### Using the Experience Delivery API

Resolving an experience directly through the Experience Delivery API avoids the destination tree lookup and may reduce latency, but it bypasses the destination context and its consistency guarantees. Consider this tradeoff on a case-by-case basis, and prefer the Destinations Delivery API when possible.

## Using the client's path

Experiences can be resolved at request time, either server-side or client-side, using the client's path.

For example, a site has the following pages:

```text
/
├── products
│   ├── shoes
│   │   ├── running
│   │   └── hiking
│   └── accessories
├── about
└── contact
```

The Destinations Delivery API can resolve the experience for the current path:

```bash
curl --request POST \
  --url "https://xdn.contentful.com/spaces/{space_id}/destinations/{destination_id}/experiences" \
  --header "Authorization: Bearer {access_token}" \
  --header "Content-Type: application/json" \
  --data '{
    "path": "/products/shoes/running"
  }'
```

In a client-side React application, this could look like:

```jsx
import { useEffect, useState } from "react";
import {
  ClientExperienceRenderer,
  resolveExperience,
} from "@contentful/experiences-react";
import { experienceConfig } from "./experience-config";

const spaceId = "<space_id>";
const destinationId = "<destination_id>";
const accessToken = "<access_token>";

export default function App() {
  const [experience, setExperience] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function loadExperience() {
      const response = await fetch(
        `https://xdn.contentful.com/spaces/${spaceId}/destinations/${destinationId}/experiences`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${accessToken}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ path: window.location.pathname }), // [1]
        },
      );

      if (!response.ok) {
        throw new Error(`Unable to resolve experience: ${response.status}`);
      }

      const result = await response.json();

      if ("redirect" in result) {
        window.location.assign(result.redirect.path); // [2]
        return;
      }

      const destinationExperience = result.experiences[0];

      if (!destinationExperience) {
        throw new Error("The destination did not resolve an experience");
      }

      const resolvedExperience = await resolveExperience(
        destinationExperience.experience,
        experienceConfig,
      );

      setExperience(resolvedExperience);
    }

    loadExperience().catch(setError);
  }, []);

  if (error) {
    return <p>{error.message}</p>;
  }

  if (!experience) {
    return <p>Loading...</p>;
  }

  return (
    <ClientExperienceRenderer
      experience={experience}
      config={experienceConfig}
    />
  );
}
```

The notable parts of this example are:

* The request reads the path from `window.location.pathname` and sends it to the Destinations Delivery API. The pathname should exactly match a resolved destination node path.
* If the response is a redirect, it contains the target path. This example navigates the browser to that path; alternatively, the application can make another Destinations Delivery API request for it.

## Hard-coding a path or destination node ID

The final option is to hard-code a path or destination node ID. A hard-coded path uses the same path-resolution endpoint shown above. A stable destination node ID can be preferable to an experience ID for two reasons:

* The upsert operation lets you choose a meaningful, stable destination node ID.
* The destination node can remain the same when its linked experience changes, so the client doesn't need to be updated when editors replace the experience.

To create a destination experience node with a chosen ID, use the Destinations Management API upsert operation. This operation requires a Contentful management token, which must not be exposed in a client-side application:

```bash
curl --request PUT \
  --url "https://api.contentful.com/spaces/{space_id}/environments/{environment_id}/destinations/{destination_id}/nodes/product-detail-page" \
  --header "Authorization: Bearer {management_token}" \
  --header "Content-Type: application/vnd.contentful.management.v1+json" \
  --data '{
    "properties": [],
    "experience": {
      "sys": {
        "type": "ResourceLink",
        "linkType": "Contentful:Experience",
        "urn": "crn:contentful:::experience:spaces/$self/environments/$self/experiences/{experience_id}"
      }
    }
  }'
```

Publish the destination node before requesting it from the Destinations Delivery API. Use the version returned by the upsert response, and make sure the destination is also published:

```bash
curl --request PUT \
  --url "https://api.contentful.com/spaces/{space_id}/environments/{environment_id}/destinations/{destination_id}/nodes/product-detail-page/published" \
  --header "Authorization: Bearer {management_token}" \
  --header "X-Contentful-Version: {node_version}"
```

The client can then resolve the experience using the hard-coded destination node ID:

```jsx
import { useEffect, useState } from "react";
import {
  ClientExperienceRenderer,
  resolveExperience,
} from "@contentful/experiences-react";
import { experienceConfig } from "./experience-config";

const spaceId = "<space_id>";
const destinationId = "<destination_id>";
const accessToken = "<access_token>";
const nodeId = "product-detail-page";

export default function App() {
  const [experience, setExperience] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function loadExperience() {
      const response = await fetch(
        `https://xdn.contentful.com/spaces/${spaceId}/destinations/${destinationId}/nodes/${nodeId}/experiences`,
        {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        },
      );

      if (!response.ok) {
        throw new Error(`Unable to resolve experience: ${response.status}`);
      }

      const result = await response.json();

      if ("redirect" in result) {
        window.location.assign(result.redirect.path);
        return;
      }

      const destinationExperience = result.experiences[0];

      if (!destinationExperience) {
        throw new Error("The destination node did not resolve an experience");
      }

      const resolvedExperience = await resolveExperience(
        destinationExperience.experience,
        experienceConfig,
      );

      setExperience(resolvedExperience);
    }

    loadExperience().catch(setError);
  }, []);

  if (error) {
    return <p>{error.message}</p>;
  }

  if (!experience) {
    return <p>Loading...</p>;
  }

  return (
    <ClientExperienceRenderer
      experience={experience}
      config={experienceConfig}
    />
  );
}
```