Heppu AI

Sources

Create, update, list, and delete knowledge base sources

Sources

Manage individual sources in your knowledge base. Sources are identified by an externalId that you control, enabling upsert semantics.

Create or Update Source

Create a new source or update an existing one. If a source with the same externalId exists, it will be updated.

Endpoint

POST /api/v1/kb/sources

Authentication

# API Key
x-api-key: YOUR_API_KEY

# Bearer Token
Authorization: Bearer YOUR_API_KEY

Request Body

FieldTypeRequiredDescription
collectionIdstringYesID of the knowledge collection
externalIdstringYesYour unique identifier for this source (max 255 chars)
titlestringYesDisplay title for the source (max 500 chars)
contentstringYesMarkdown content (max 500KB)
metadataobjectNoAdditional metadata to store with the source

Response

{
  "data": {
    "documentId": "uuid",
    "externalId": "your-external-id",
    "status": "processing" | "unchanged",
    "message": "string"
  },
  "meta": {
    "timestamp": "ISO 8601"
  }
}

Status values:

  • processing - Content queued for processing (new or updated)
  • unchanged - Content hash matches existing source, no processing needed

Examples

Create a New Source

curl -X POST https://v2.heppu.ai/api/v1/kb/sources \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionId": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "car-12345",
    "title": "2024 BMW X5 xDrive40i",
    "content": "# 2024 BMW X5 xDrive40i\n\n## Overview\nThe BMW X5 is a luxury mid-size SUV...\n\n## Specifications\n- Engine: 3.0L TwinPower Turbo inline-6\n- Horsepower: 335 hp\n- Torque: 331 lb-ft\n\n## Pricing\nMSRP: $65,200"
  }'
const response = await fetch('https://v2.heppu.ai/api/v1/kb/sources', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    collectionId: '550e8400-e29b-41d4-a716-446655440000',
    externalId: 'car-12345',
    title: '2024 BMW X5 xDrive40i',
    content: `# 2024 BMW X5 xDrive40i

## Overview
The BMW X5 is a luxury mid-size SUV...

## Specifications
- Engine: 3.0L TwinPower Turbo inline-6
- Horsepower: 335 hp
- Torque: 331 lb-ft

## Pricing
MSRP: $65,200`
  })
});

const data = await response.json();
console.log('Source created:', data.data);
import requests

response = requests.post(
    'https://v2.heppu.ai/api/v1/kb/sources',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'collectionId': '550e8400-e29b-41d4-a716-446655440000',
        'externalId': 'car-12345',
        'title': '2024 BMW X5 xDrive40i',
        'content': '''# 2024 BMW X5 xDrive40i

## Overview
The BMW X5 is a luxury mid-size SUV...

## Specifications
- Engine: 3.0L TwinPower Turbo inline-6
- Horsepower: 335 hp
- Torque: 331 lb-ft

## Pricing
MSRP: $65,200'''
    }
)

data = response.json()
print(f"Source created: {data['data']}")

Update an Existing Source

When you POST with an existing externalId, the source is updated and reprocessed:

curl -X POST https://v2.heppu.ai/api/v1/kb/sources \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionId": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "car-12345",
    "title": "2024 BMW X5 xDrive40i - SOLD",
    "content": "# 2024 BMW X5 xDrive40i\n\n**Status: SOLD**\n\nThis vehicle has been sold."
  }'
// Update existing source (same externalId)
const response = await fetch('https://v2.heppu.ai/api/v1/kb/sources', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    collectionId: '550e8400-e29b-41d4-a716-446655440000',
    externalId: 'car-12345',  // Same externalId = update
    title: '2024 BMW X5 xDrive40i - SOLD',
    content: '# 2024 BMW X5 xDrive40i\n\n**Status: SOLD**\n\nThis vehicle has been sold.'
  })
});

const data = await response.json();
console.log('Source updated:', data.data.status);  // "processing"
# Update existing source (same externalId)
response = requests.post(
    'https://v2.heppu.ai/api/v1/kb/sources',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'collectionId': '550e8400-e29b-41d4-a716-446655440000',
        'externalId': 'car-12345',  # Same externalId = update
        'title': '2024 BMW X5 xDrive40i - SOLD',
        'content': '# 2024 BMW X5 xDrive40i\n\n**Status: SOLD**\n\nThis vehicle has been sold.'
    }
)

data = response.json()
print(f"Source updated: {data['data']['status']}")  # "processing"

Source with Metadata

curl -X POST https://v2.heppu.ai/api/v1/kb/sources \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionId": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "car-12345",
    "title": "2024 BMW X5 xDrive40i",
    "content": "# 2024 BMW X5 xDrive40i\n\nLuxury SUV with premium features...",
    "metadata": {
      "vin": "5UXCR6C05R9S12345",
      "stock_number": "A12345",
      "price": 65200,
      "mileage": 0,
      "condition": "new",
      "source_url": "https://dealer.com/inventory/12345",
      "last_scraped": "2024-12-19T10:00:00Z"
    }
  }'
const response = await fetch('https://v2.heppu.ai/api/v1/kb/sources', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    collectionId: '550e8400-e29b-41d4-a716-446655440000',
    externalId: 'car-12345',
    title: '2024 BMW X5 xDrive40i',
    content: '# 2024 BMW X5 xDrive40i\n\nLuxury SUV with premium features...',
    metadata: {
      vin: '5UXCR6C05R9S12345',
      stock_number: 'A12345',
      price: 65200,
      mileage: 0,
      condition: 'new',
      source_url: 'https://dealer.com/inventory/12345',
      last_scraped: new Date().toISOString()
    }
  })
});
from datetime import datetime

response = requests.post(
    'https://v2.heppu.ai/api/v1/kb/sources',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'collectionId': '550e8400-e29b-41d4-a716-446655440000',
        'externalId': 'car-12345',
        'title': '2024 BMW X5 xDrive40i',
        'content': '# 2024 BMW X5 xDrive40i\n\nLuxury SUV with premium features...',
        'metadata': {
            'vin': '5UXCR6C05R9S12345',
            'stock_number': 'A12345',
            'price': 65200,
            'mileage': 0,
            'condition': 'new',
            'source_url': 'https://dealer.com/inventory/12345',
            'last_scraped': datetime.utcnow().isoformat() + 'Z'
        }
    }
)

List Sources

Retrieve sources in a collection with optional filtering.

Endpoint

GET /api/v1/kb/sources

Query Parameters

ParameterTypeRequiredDefaultDescription
collectionIdstringYes-ID of the knowledge collection
externalIdstringNo-Filter by specific externalId
statusstringNo-Filter by processing status
limitintegerNo50Results per page (max: 100)
offsetintegerNo0Pagination offset

Status values: pending, processing, completed, failed

Response

{
  "data": {
    "sources": [
      {
        "id": "uuid",
        "externalId": "string",
        "title": "string",
        "status": "string",
        "chunkCount": "integer",
        "fileSize": "integer",
        "error": "string|null",
        "createdAt": "ISO 8601",
        "updatedAt": "ISO 8601",
        "metadata": "object"
      }
    ],
    "pagination": {
      "total": "integer",
      "limit": "integer",
      "offset": "integer",
      "hasMore": "boolean"
    }
  }
}

Examples

List All Sources

curl "https://v2.heppu.ai/api/v1/kb/sources?collectionId=550e8400-e29b-41d4-a716-446655440000" \
  -H "x-api-key: YOUR_API_KEY"
const collectionId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
  `https://v2.heppu.ai/api/v1/kb/sources?collectionId=${collectionId}`,
  {
    headers: { 'x-api-key': 'YOUR_API_KEY' }
  }
);

const data = await response.json();
console.log(`Found ${data.data.pagination.total} sources`);
data.data.sources.forEach(source => {
  console.log(`- ${source.title}: ${source.status} (${source.chunkCount} chunks)`);
});
response = requests.get(
    'https://v2.heppu.ai/api/v1/kb/sources',
    headers={'x-api-key': 'YOUR_API_KEY'},
    params={'collectionId': '550e8400-e29b-41d4-a716-446655440000'}
)

data = response.json()
print(f"Found {data['data']['pagination']['total']} sources")
for source in data['data']['sources']:
    print(f"- {source['title']}: {source['status']} ({source['chunkCount']} chunks)")

Check Processing Status

# Check status of a specific source
curl "https://v2.heppu.ai/api/v1/kb/sources?collectionId=550e8400-e29b-41d4-a716-446655440000&externalId=car-12345" \
  -H "x-api-key: YOUR_API_KEY"

# List all failed sources
curl "https://v2.heppu.ai/api/v1/kb/sources?collectionId=550e8400-e29b-41d4-a716-446655440000&status=failed" \
  -H "x-api-key: YOUR_API_KEY"
// Poll for processing completion
async function waitForProcessing(collectionId, externalId) {
  while (true) {
    const response = await fetch(
      `https://v2.heppu.ai/api/v1/kb/sources?collectionId=${collectionId}&externalId=${externalId}`,
      { headers: { 'x-api-key': 'YOUR_API_KEY' } }
    );

    const data = await response.json();
    const source = data.data.sources[0];

    if (!source) {
      throw new Error('Source not found');
    }

    if (source.status === 'completed') {
      console.log(`Processing complete: ${source.chunkCount} chunks`);
      return source;
    }

    if (source.status === 'failed') {
      throw new Error(`Processing failed: ${source.error}`);
    }

    console.log(`Status: ${source.status}, waiting...`);
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
}
import time

def wait_for_processing(collection_id, external_id):
    """Poll until processing is complete"""
    while True:
        response = requests.get(
            'https://v2.heppu.ai/api/v1/kb/sources',
            headers={'x-api-key': 'YOUR_API_KEY'},
            params={
                'collectionId': collection_id,
                'externalId': external_id
            }
        )

        data = response.json()
        sources = data['data']['sources']

        if not sources:
            raise Exception('Source not found')

        source = sources[0]

        if source['status'] == 'completed':
            print(f"Processing complete: {source['chunkCount']} chunks")
            return source

        if source['status'] == 'failed':
            raise Exception(f"Processing failed: {source['error']}")

        print(f"Status: {source['status']}, waiting...")
        time.sleep(2)

Delete Sources

Bulk delete sources by their externalIds.

Endpoint

DELETE /api/v1/kb/sources

Request Body

FieldTypeRequiredDescription
collectionIdstringYesID of the knowledge collection
externalIdsstring[]YesArray of externalIds to delete (max: 100)

Response

{
  "data": {
    "deleted": "integer",
    "deletedExternalIds": ["string"],
    "notFound": ["string"],
    "message": "string"
  }
}

Examples

curl -X DELETE https://v2.heppu.ai/api/v1/kb/sources \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionId": "550e8400-e29b-41d4-a716-446655440000",
    "externalIds": ["car-12345", "car-12346", "car-12347"]
  }'
const response = await fetch('https://v2.heppu.ai/api/v1/kb/sources', {
  method: 'DELETE',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    collectionId: '550e8400-e29b-41d4-a716-446655440000',
    externalIds: ['car-12345', 'car-12346', 'car-12347']
  })
});

const data = await response.json();
console.log(`Deleted ${data.data.deleted} sources`);

if (data.data.notFound?.length > 0) {
  console.log('Not found:', data.data.notFound);
}
response = requests.delete(
    'https://v2.heppu.ai/api/v1/kb/sources',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'collectionId': '550e8400-e29b-41d4-a716-446655440000',
        'externalIds': ['car-12345', 'car-12346', 'car-12347']
    }
)

data = response.json()
print(f"Deleted {data['data']['deleted']} sources")

if data['data'].get('notFound'):
    print(f"Not found: {data['data']['notFound']}")

Error Responses

Validation Error

{
  "error": {
    "message": "Validation error: collectionId is required",
    "status": 400,
    "timestamp": "ISO 8601"
  }
}

Collection Not Found

{
  "error": {
    "message": "Collection 550e8400-... not found or does not belong to your organization",
    "status": 404,
    "timestamp": "ISO 8601"
  }
}

Content Too Large

{
  "error": {
    "message": "Validation error: content too long (max 500KB)",
    "status": 400,
    "timestamp": "ISO 8601"
  }
}

Best Practices

  1. Use meaningful externalIds - Use IDs from your source system (e.g., VIN, SKU, document ID)
  2. Structure content with Markdown - Use headings to organize content for better chunking
  3. Include relevant metadata - Store source URLs, timestamps, and other tracking info
  4. Handle unchanged content - Check for unchanged status to avoid unnecessary processing
  5. Poll for completion - Use the GET endpoint to verify processing completed successfully
  6. Delete sold/removed items - Clean up sources that are no longer relevant

On this page