> ## Documentation Index
> Fetch the complete documentation index at: https://docs.voyantcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Get product

> GET /v1/products/:idOrSlug - Retrieve detailed product information by ID or slug

## Endpoint

```
GET https://api.voyantcloud.com/v1/products/:idOrSlug
```

Retrieve complete details about a specific travel product including descriptions, media, pricing models, and availability information.

## Authentication

<ParamField header="Authorization" type="string" required>
  Bearer token (e.g. <code>Authorization: Bearer YOUR\_API\_KEY</code>)
</ParamField>

## Query parameters

<ParamField query="lang" type="string">
  Optional language code (e.g., <code>ro</code>, <code>fr</code>) to return localized fields. Alias: <code>locale</code>.
</ParamField>

## Path parameters

<ParamField path="idOrSlug" type="string" required>
  Product ID ([TypeID](/concepts/identifiers) with `prod_` prefix) or workspace slug. Translation slugs are also supported when you pass the matching <code>lang</code>/<code>locale</code> value.
</ParamField>

## Request example

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.voyantcloud.com/v1/products/prod_01h8z3y4x2w1v0u9t8s7r6q5p4 \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```bash cURL (slug) theme={null}
  curl "https://api.voyantcloud.com/v1/products/paris-city-break?lang=fr" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const productId = "prod_01h8z3y4x2w1v0u9t8s7r6q5p4"

  const response = await fetch(`https://api.voyantcloud.com/v1/products/${productId}`, {
    headers: { Authorization: `Bearer ${process.env.VOYANT_API_KEY}` },
  })

  const product = await response.json()
  console.log(product)
  ```

  ```python Python theme={null}
  import requests
  import os

  product_id = "prod_01h8z3y4x2w1v0u9t8s7r6q5p4"
  response = requests.get(
    f'https://api.voyantcloud.com/v1/products/{product_id}',
    headers={'Authorization': f"Bearer {os.environ['VOYANT_API_KEY']}"}
  )

  product = response.json()
  print(product)
  ```
</CodeGroup>

## Response

<ResponseField name="id" type="string" required>
  Unique product identifier
</ResponseField>

<ResponseField name="title" type="string" required>
  Product name/title. When <code>lang</code> is provided and a translation exists, this is localized.
</ResponseField>

<ResponseField name="description" type="string">
  Full product description (may contain HTML). Localized when a matching translation exists.
</ResponseField>

<ResponseField name="summary" type="string">
  Short summary for listings
</ResponseField>

<ResponseField name="slug" type="string">
  URL-friendly identifier
</ResponseField>

<ResponseField name="status" type="string" required>
  Product status: `draft`, `active`, `archived`
</ResponseField>

<ResponseField name="type" type="string">
  Product type: `tour`, `experience`, `hotel`, `package`, `transfer`, `car_rental`, `flight`
</ResponseField>

<ResponseField name="duration_days" type="integer">
  Duration in days for multi-day products
</ResponseField>

<ResponseField name="currency" type="string">
  3-letter ISO currency code
</ResponseField>

<ResponseField name="location" type="object">
  Product location information
</ResponseField>

<ResponseField name="media" type="array">
  Array of product images and videos

  <Expandable title="Media item properties">
    <ResponseField name="url" type="string">
      Media URL
    </ResponseField>

    <ResponseField name="type" type="string">
      Media type: `image`, `video`
    </ResponseField>

    <ResponseField name="alt" type="string">
      Alt text for images
    </ResponseField>

    <ResponseField name="sort" type="integer">
      Display order (lower numbers first)
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="inclusions" type="array">
  List of what's included in the product
</ResponseField>

<ResponseField name="exclusions" type="array">
  List of what's not included
</ResponseField>

<ResponseField name="inclusionsHtml" type="string">
  HTML content describing what's included in the product. Localized when a matching translation exists.
</ResponseField>

<ResponseField name="exclusionsHtml" type="string">
  HTML content describing what's not included. Localized when a matching translation exists.
</ResponseField>

<ResponseField name="termsHtml" type="string">
  HTML content with product-specific terms and conditions. Localized when a matching translation exists.
</ResponseField>

<ResponseField name="cancellationPolicy" type="object">
  The cancellation policy attached to this product, if any.

  <Expandable title="Cancellation policy properties">
    <ResponseField name="id" type="string">Unique policy identifier</ResponseField>
    <ResponseField name="kind" type="string">Policy kind: `cancellation`, `terms`, `other`</ResponseField>
    <ResponseField name="scope" type="string">Policy scope: `generic`, `product`</ResponseField>
    <ResponseField name="status" type="string">Policy status: `draft`, `active`</ResponseField>
    <ResponseField name="title" type="string">Policy title. Localized when a matching translation exists.</ResponseField>
    <ResponseField name="descriptionHtml" type="string">Full policy description in HTML. Localized when a matching translation exists.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="highlights" type="array">
  Key highlights and features
</ResponseField>

<ResponseField name="created_at" type="timestamp">
  ISO 8601 timestamp when created
</ResponseField>

<ResponseField name="updated_at" type="timestamp">
  ISO 8601 timestamp when last updated
</ResponseField>

<ResponseField name="translation" type="object">
  Translation record for the requested <code>lang</code> (or <code>null</code> if none found).

  <Expandable title="Translation properties">
    <ResponseField name="locale" type="string">Requested locale code</ResponseField>
    <ResponseField name="title" type="string">Localized title</ResponseField>
    <ResponseField name="description" type="string">Localized description (HTML supported)</ResponseField>
    <ResponseField name="slug" type="string">Localized slug</ResponseField>
    <ResponseField name="inclusionsHtml" type="string">Localized inclusions HTML</ResponseField>
    <ResponseField name="exclusionsHtml" type="string">Localized exclusions HTML</ResponseField>
    <ResponseField name="termsHtml" type="string">Localized terms HTML</ResponseField>
    <ResponseField name="attributes" type="object">Localized attributes payload</ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "id": "prod_01h8z3y4x2w1v0u9t8s7r6q5p4",
    "title": "Paris City Tour",
    "description": "<p>Discover the magic of Paris with our comprehensive city tour...</p>",
    "summary": "Full-day guided tour of Paris's iconic landmarks",
    "slug": "paris-city-tour",
    "status": "active",
    "type": "tour",
    "duration_days": 1,
    "currency": "EUR",
    "location": {
      "city": "Paris",
      "country": "FR",
      "coordinates": {
        "lat": 48.8566,
        "lng": 2.3522
      }
    },
    "media": [
      {
        "url": "https://cdn.voyantcloud.com/products/paris-tour-1.jpg",
        "type": "image",
        "alt": "Eiffel Tower view",
        "sort": 0
      },
      {
        "url": "https://cdn.voyantcloud.com/products/paris-tour-2.jpg",
        "type": "image",
        "alt": "Louvre Museum",
        "sort": 1
      }
    ],
    "inclusions": [
      "Professional guide",
      "Hotel pickup and drop-off",
      "Skip-the-line tickets",
      "Lunch at local restaurant"
    ],
    "exclusions": [
      "Gratuities",
      "Personal expenses",
      "Travel insurance"
    ],
    "inclusionsHtml": "<ul><li>Professional guide</li><li>Hotel pickup and drop-off</li><li>Skip-the-line tickets</li><li>Lunch at local restaurant</li></ul>",
    "exclusionsHtml": "<ul><li>Gratuities</li><li>Personal expenses</li><li>Travel insurance</li></ul>",
    "termsHtml": "<p>Cancellation must be made 48 hours in advance for a full refund. Children under 5 travel free.</p>",
    "cancellationPolicy": {
      "id": "pol_01h9a2b3c4d5e6f7g8h9i0j1k2",
      "kind": "cancellation",
      "scope": "product",
      "status": "active",
      "title": "Flexible Cancellation Policy",
      "descriptionHtml": "<p>Free cancellation up to 48 hours before the experience starts. After that, a 50% fee applies. No-shows are non-refundable.</p>"
    },
    "highlights": [
      "Visit the Eiffel Tower",
      "Explore the Louvre Museum",
      "Seine River cruise",
      "Notre-Dame Cathedral"
    ],
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-15T10:00:00Z",
    "translation": null
  }
  ```

  ```json 404 Not Found theme={null}
  {
    "error": "Not found"
  }
  ```

  ```json 500 Server Error theme={null}
  {
    "error": "Failed to fetch product"
  }
  ```
</ResponseExample>

## Error handling

<AccordionGroup>
  <Accordion title="404 Not Found">
    The product doesn't exist or you don't have access to it.

    **Possible causes:**

    * Invalid product ID
    * Product belongs to different workspace
    * Product was deleted

    **Solution:** Verify the product ID and ensure it belongs to your workspace.
  </Accordion>

  <Accordion title="401 Unauthorized">
    Missing or invalid API key.

    **Solution:** Check your `Authorization: Bearer` header is set correctly.
  </Accordion>
</AccordionGroup>

## Use cases

### Product detail page

Display comprehensive product information:

```javascript theme={null}
async function loadProductDetails(productId, locale = "en") {
  const response = await fetch(
    `https://api.voyantcloud.com/v1/products/${productId}?lang=${locale}`,
    {
      headers: { Authorization: `Bearer ${process.env.VOYANT_API_KEY}` },
    }
  )

  if (!response.ok) {
    throw new Error("Product not found")
  }

  const product = await response.json()

  return {
    id: product.id,
    title: product.title,
    description: product.description,
    images: product.media?.filter((m) => m.type === "image") || [],
    included: product.inclusions || [],
    excluded: product.exclusions || [],
    // Use HTML content for rich formatting
    inclusionsHtml: product.inclusionsHtml,
    exclusionsHtml: product.exclusionsHtml,
    termsHtml: product.termsHtml,
    // Cancellation policy details
    cancellationPolicy: product.cancellationPolicy,
    highlights: product.highlights || [],
    location: product.location,
    duration: product.duration_days,
  }
}
```

### Generate product schema

Create structured data for SEO:

```javascript theme={null}
function generateProductSchema(product) {
  return {
    "@context": "https://schema.org",
    "@type": "Product",
    name: product.title,
    description: product.summary || product.description,
    image: product.media?.[0]?.url,
    offers: {
      "@type": "Offer",
      priceCurrency: product.currency,
      availability: "https://schema.org/InStock",
    },
  }
}
```

## Related endpoints

<CardGroup cols={2}>
  <Card title="List products" icon="list" href="/api-reference/products/list-products">
    Browse all products in your catalog
  </Card>

  <Card title="Pricing options" icon="tag" href="/api-reference/products/pricing-options">
    Get pricing options for this product
  </Card>

  <Card title="Extensions" icon="puzzle-piece" href="/api-reference/products/extensions">
    Retrieve available add-ons
  </Card>

  <Card title="Calculate price" icon="calculator" href="/api-reference/pricing/calculate-price">
    Calculate real-time pricing
  </Card>
</CardGroup>
