Catalog AI
Sign In

Product Category Enrichment API

AI-powered enrichment that generates SEO content for every product category — page titles, meta descriptions, keywords, HTML descriptions, URL slugs, and more.

Overview

The Product Category Enrichment API enriches product categories with structured SEO attributes that you define under Settings → Enrichment → Category Attributes. Each job uses your active attributes (or a specific Attribute Set), analyses the products linked to each category, and generates one value per attribute per category.

If no custom attributes are configured, the API falls back to six built-in SEO defaults: page_title, meta_description, meta_keywords, description (HTML), slug, and focus_keywords.

The API accepts rows of category data directly as JSON objects — no CSV file upload is required at the API level.

Endpoints Overview

Method Endpoint Description
POST /product-category-jobs/create Create a batch enrichment job
POST /product-category-jobs/get List enrichment jobs
POST /product-category-jobs/{job_id}/items/get Get a job's enriched results
POST /product-category-jobs/{job_id}/stop Stop a running job
GET /product-category-jobs/{job_id}/download Download job results as CSV

All endpoints are prefixed with /api/{organization}.


Create a Batch Enrichment Job

Start a job to enrich multiple product categories in parallel. The job runs asynchronously — rows are processed concurrently (up to 5 at a time) in the background.

Endpoint: POST /api/{organization}/product-category-jobs/create

Request Body

Field Type Required Default Description
rows object[] Yes — Array of category records. Each row must include a name, category_name, or categoryName key. 1–2,000 rows.
inputColumns string[] Yes — List of column names present in rows
fileName string No — Display name shown in the jobs list (max 255 characters, recommended to end with .csv)
modelType string No budget-v1 AI model to use: budget-v1 or pro-v1
attributeSetId string No — ID of a specific Attribute Set; omit to use all active attributes
useWebsearch boolean No false Allow the AI to search the web for categories that have no linked products
includeExistingAttributes boolean No true Pass the latest completed attribute values for each category to the model. Set false to regenerate from scratch.

Model Types

Value Description
budget-v1 Cost-effective, fast. Recommended for bulk runs.
pro-v1 Higher quality. Best for flagship category pages.

Limits

Limit Value
Max rows per job 2,000
Concurrency 5 categories at a time

Note: Only one enrichment job can run at a time per organisation. If a job is already pending or processing, the request returns an error asking you to wait.

Category Name Column Detection

The category name is detected from the row using the first non-blank match from these keys, in order:

name → category_name → categoryName

Enrichment Modes

Each category is processed in one of two modes chosen automatically at runtime:

Mode Triggered when Behaviour
products Category has ≥ 1 linked product in your catalogue AI uses up to 10 sampled products as grounding; no web search
websearch Category has no linked products and useWebsearch is true AI performs live web searches and fetches category pages

If a category has no linked products and useWebsearch is false, that item fails with the error: "No products are linked to this category. Link products via Enrich Category on a product, or enable "Use Websearch" before enriching."

Example Request

curl -X POST https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/create \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      { "name": "Running Shoes", "slug": "running-shoes" },
      { "name": "Trail Running",  "slug": "trail-running" }
    ],
    "inputColumns": ["name", "slug"],
    "fileName": "categories.csv",
    "modelType": "budget-v1",
    "useWebsearch": false
  }'

Response

{
  "success": true,
  "data": {
    "success": true,
    "message": "Product-category enrichment job created successfully",
    "jobId": "pcej_abc123"
  }
}

List Enrichment Jobs

Retrieve a paginated list of product category enrichment jobs for the organisation.

Endpoint: POST /api/{organization}/product-category-jobs/get

Request Body

Field Type Required Default Description
page number No 1 Page number (min 1)
limit number No 20 Items per page (max 100)

Example Request

curl -X POST https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/get \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"page": 1, "limit": 20}'

Response

{
  "success": true,
  "data": {
    "success": true,
    "jobs": [
      {
        "id": "pcej_abc123",
        "status": "completed",
        "modelType": "budget-v1",
        "fileName": "categories.csv",
        "useWebsearch": false,
        "attributeSetId": null,
        "inputColumns": ["name", "slug"],
        "totalCategories": 120,
        "processedCount": 120,
        "successCount": 118,
        "failedCount": 2,
        "errorSummary": null,
        "startedAt": "2026-05-18T08:00:00Z",
        "completedAt": "2026-05-18T08:04:12Z",
        "createdAt": "2026-05-18T07:59:55Z",
        "createdBy": {
          "name": "Aditya",
          "email": "aditya@example.com"
        }
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 3,
      "totalPages": 1
    }
  }
}

Job Status Values

Status Description
pending Job is queued and will start shortly
processing Categories are being enriched right now
completed All categories finished successfully
completed_with_errors Job finished but some categories failed
failed Job failed before processing
stopped Job was manually stopped

Get Job Items

Retrieve detailed enriched results for a specific job, paginated by row.

Endpoint: POST /api/{organization}/product-category-jobs/{job_id}/items/get

Path Parameters

Parameter Type Required Description
job_id string Yes Job ID

Request Body

Field Type Required Default Description
page number No 1 Page number (min 1)
limit number No 50 Items per page (max 100)

Example Request

curl -X POST https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/pcej_abc123/items/get \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"page": 1, "limit": 50}'

Response

{
  "success": true,
  "data": {
    "success": true,
    "job": {
      "id": "pcej_abc123",
      "modelType": "budget-v1",
      "fileName": "categories.csv",
      "inputColumns": ["name", "slug"],
      "totalCategories": 120,
      "processedCount": 120,
      "successCount": 118,
      "failedCount": 2,
      "status": "completed",
      "errorSummary": null
    },
    "items": [
      {
        "id": "item_001",
        "jobId": "pcej_abc123",
        "rowIndex": 0,
        "categoryName": "Running Shoes",
        "inputData": {
          "name": "Running Shoes",
          "slug": "running-shoes"
        },
        "enrichedAttributes": {
          "page_title": {
            "attributeId": "default_page_title",
            "attributeName": "Page Title",
            "attributeType": "text",
            "value": "Running Shoes — Shop Road & Trail Runners"
          },
          "meta_description": {
            "attributeId": "default_meta_description",
            "attributeName": "Meta Description",
            "attributeType": "text",
            "value": "Browse our full range of running shoes for road, trail, and track. Find the perfect fit for your stride. Free shipping on orders over £50."
          },
          "meta_keywords": {
            "attributeId": "default_meta_keywords",
            "attributeName": "Meta Keywords",
            "attributeType": "text",
            "value": "running shoes, road running shoes, trail running shoes, mens running shoes, womens running shoes"
          },
          "description": {
            "attributeId": "default_description",
            "attributeName": "Description",
            "attributeType": "html",
            "value": "<div><p>Whether you're training for a marathon...</p></div>"
          },
          "slug": {
            "attributeId": "default_slug",
            "attributeName": "URL Slug",
            "attributeType": "text",
            "value": "running-shoes"
          },
          "focus_keywords": {
            "attributeId": "default_focus_keywords",
            "attributeName": "Focus Keywords",
            "attributeType": "text",
            "value": "running shoes"
          }
        },
        "referenceData": {
          "mode": "products",
          "productIds": ["prod_001", "prod_002", "prod_003"]
        },
        "status": "completed",
        "cost": 0.0031,
        "startedAt": "2026-05-18T08:00:05Z",
        "completedAt": "2026-05-18T08:00:11Z"
      },
      {
        "id": "item_002",
        "jobId": "pcej_abc123",
        "rowIndex": 1,
        "categoryName": "Trail Running",
        "inputData": {
          "name": "Trail Running",
          "slug": "trail-running"
        },
        "referenceData": null,
        "status": "failed",
        "errorMessage": "No products are linked to this category. Link products via Enrich Category on a product, or enable \"Use Websearch\" before enriching."
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 50,
      "total": 120,
      "totalPages": 3
    }
  }
}

enrichedAttributes Object

Each key in enrichedAttributes is the attribute slug (e.g. page_title, meta_description). The value object has the following fields:

Field Type Description
attributeId string Internal attribute ID
attributeName string Human-readable attribute name
attributeType string One of text, html, number, boolean, select, multiselect
value string | number | boolean | string[] | null The enriched value

referenceData Object

Field Type Description
mode string "products" or "websearch"
productIds string[] (products mode only) IDs of the sampled products used as context
urls string[] (websearch mode only) URLs fetched during web research

Item Status Values

Status Description
pending Waiting to be processed
processing Currently being enriched
completed Successfully enriched
failed Failed (see errorMessage)

Stop a Job

Stop a pending or processing job. Categories already enriched keep their results. All remaining pending and processing items are immediately marked as failed with the message "Job was stopped by user".

Endpoint: POST /api/{organization}/product-category-jobs/{job_id}/stop

Path Parameters

Parameter Type Required Description
job_id string Yes ID of the job to stop

Example Request

curl -X POST https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/pcej_abc123/stop \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json"

Response

{
  "success": true,
  "message": "Product-category enrichment job stopped successfully",
  "data": {
    "jobId": "pcej_abc123",
    "status": "stopped"
  }
}

Error Responses

If the job does not exist:

{
  "success": false,
  "message": "Job not found"
}

If the job is already completed, failed, or stopped:

{
  "success": false,
  "message": "Job cannot be stopped because it is not in progress"
}

Notes

  • Only jobs with status pending or processing can be stopped
  • All pending and processing items are immediately marked failed with error "Job was stopped by user"

Download Job Results as CSV

Download all enriched rows as a CSV file. The file preserves your original input columns and appends enrichment status and one column per configured attribute.

Endpoint: GET /api/{organization}/product-category-jobs/{job_id}/download

Path Parameters

Parameter Type Required Description
job_id string Yes Job ID

Example Request

curl -X GET https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/pcej_abc123/download \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o enriched-categories.csv

Response

A text/csv file with Content-Disposition: attachment. The filename format is:

{original_filename_without_extension}_enriched_{YYYY-MM-DD}.csv

Falls back to categories_{first8charsOfJobId}_enriched_{YYYY-MM-DD}.csv when no file name is set.

The header row is structured as:

<your input columns>, Enrichment Status, Enrichment Error, <one column per enriched attribute>

Attribute column notes:

  • html type attributes are written as raw HTML; any other attribute whose value contains HTML tags is automatically stripped to plain text
  • multiselect values are joined with ;
  • boolean values are written as Yes / No
  • All other types are written as plain strings
  • null values are written as empty strings

Default SEO Attributes

If your organisation has no custom enrichment attributes configured, the API automatically uses these six built-in defaults:

Slug Name Type Constraint
page_title Page Title text ≤60 characters, Title Case, keyword near the front
meta_description Meta Description text ≤160 characters, benefit-led, includes keyword
meta_keywords Meta Keywords text 5–10 comma-separated lowercase keywords
description Description html 150–350 words, wrapped in <div>, uses <h2> and <ul>
slug URL Slug text kebab-case, 3–5 words, ≤75 characters
focus_keywords Focus Keywords text 1–3 word primary keyword, lowercase

Workflow Example

1. Prepare Category Rows

const categories = [
  { name: 'Running Shoes', slug: 'running-shoes' },
  { name: 'Trail Running', slug: 'trail-running' },
  { name: 'Road Running', slug: 'road-running' }
]

2. Create the Job

const createRes = await fetch(
  'https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/create',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      rows: categories,
      inputColumns: ['name', 'slug'],
      modelType: 'budget-v1',
      useWebsearch: false
    })
  }
)

const { data } = await createRes.json()
const jobId = data.jobId

3. Poll for Completion

async function waitForJob(jobId) {
  while (true) {
    const res = await fetch(
      'https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/get',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ page: 1, limit: 100 })
      }
    )

    const { data } = await res.json()
    const job = data.jobs.find(j => j.id === jobId)

    if (
      job &&
      ['completed', 'completed_with_errors', 'failed', 'stopped'].includes(
        job.status
      )
    ) {
      return job
    }

    await new Promise(r => setTimeout(r, 5000))
  }
}

const finalJob = await waitForJob(jobId)

4. Fetch Enriched Items

const itemsRes = await fetch(
  `https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/${jobId}/items/get`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ page: 1, limit: 50 })
  }
)

const { data } = await itemsRes.json()
console.log(data.items)

5. Or Download the Enriched CSV

const csvRes = await fetch(
  `https://catalog-ai.tdcapps.com/api/your-org/product-category-jobs/${jobId}/download`,
  { headers: { Authorization: `Bearer ${API_KEY}` } }
)

const csv = await csvRes.text()
fs.writeFileSync('enriched-categories.csv', csv)

Best Practices

  1. Include a recognised name column — each row must have one of name, category_name, or categoryName.
  2. Include slug as well — if you have it, including the slug gives the AI better context when generating the URL slug attribute.
  3. Link products before enriching — categories with linked products produce richer, more accurate output than web search mode; the AI samples up to 10 products per category.
  4. Enable useWebsearch for unlinked categories — without linked products and with websearch off, those items will fail immediately.
  5. Use attributeSetId — restrict the job to a named Attribute Set when you only need to refresh specific fields; faster and cheaper.
  6. Stay under 2,000 rows per job — split larger lists into multiple jobs.
  7. Poll every 5–10 seconds — enrichment is asynchronous; avoid hammering the endpoint.
  8. Check failedCount and item errorMessage — common failures are missing linked products or credit exhaustion mid-job.

Error Responses

400 Bad Request

Invalid request body or missing required fields:

{
  "success": false,
  "message": "Invalid request body: ..."
}

401 Unauthorized

Missing API key:

{
  "success": false,
  "message": "Missing API Key"
}

403 Forbidden

Invalid or revoked API key, or the key does not have access to the specified organisation:

{
  "success": false,
  "message": "Invalid API Key"
}

409 Conflict — Job Already in Progress

A job is already pending or processing for this organisation:

{
  "success": false,
  "message": "A product-category enrichment job is already in progress. Please wait for it to complete before starting a new one."
}

402 Payment Required — Credit Balance Exhausted

Triggered at job creation if the credit balance is zero:

{
  "success": false,
  "message": "Credit limit reached. Please top up your credits to start a new enrichment job."
}

If credits exhaust mid-job, the job continues processing but remaining items fail with the error "Credit balance limit has been reached." The job's errorSummary is set to "Credit balance limit has been reached." Top up and create a new job to continue.

500 Internal Server Error

{
  "success": false,
  "message": "Internal Server Error"
}