# Branches
Source: https://docs.hellocobi.com/api-reference/data/endpoint/branches
POST /v1/data/branches
Upload branch records to the Cobi Data API.
**Important**: Branch records have a dependency on shops. The system validates
the `shop_id` field against existing shops in the database. You should upload
shop records before uploading branch records to ensure proper data validation.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of branch records (up to 5000)
Unique identifier for the branch
ID of the shop this branch belongs to (must reference an existing shop)
Name of the branch
Geographic region (2-letter country code, e.g., "CA" for Canada)
Latitude coordinate of the branch location
Longitude coordinate of the branch location
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "branches", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "branches", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ----------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "branches") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/branches \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"branch_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Downtown Branch",
"region": "CA",
"latitude": 43.651070,
"longitude": -79.347015,
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Upload Order**: Upload shops before branches to ensure shop\_id references exist
2. **Batch Size**: Upload up to 5000 records at a time for optimal performance
3. **Data Validation**: Ensure all required fields are present and properly formatted
4. **Error Handling**: Implement proper error handling for failed requests
5. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Categories
Source: https://docs.hellocobi.com/api-reference/data/endpoint/categories
POST /v1/data/categories
Upload category records to the Cobi Data API.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of category records (up to 5000)
Unique identifier for the category
Name of the category
Geographic region (2-letter country code, e.g., "CA" for Canada)
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "categories", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "categories", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ------------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "categories") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/categories \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"category_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Electronics",
"region": "CA",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Batch Size**: Upload up to 5000 records at a time for optimal performance
2. **Data Validation**: Ensure all required fields are present and properly formatted
3. **Error Handling**: Implement proper error handling for failed requests
4. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Courses
Source: https://docs.hellocobi.com/api-reference/data/endpoint/courses
POST /v1/data/courses
Upload course records to the Cobi Data API.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of course records (up to 1000)
Unique identifier for the course
Name of the course
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string",
"type": "courses",
"batches": [
{
"batch": 1,
"status": 200,
"data": {
"status": "success",
"type": "courses",
"message": "Records uploaded successfully",
"request_id": "string",
"batch_id": "string",
"error": ""
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ---------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "courses") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/courses \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"course_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Introduction to Computer Science",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Batch Size**: Upload up to 5000 records at a time for optimal performance
2. **Data Validation**: Ensure all required fields are present and properly formatted
3. **Error Handling**: Implement proper error handling for failed requests
4. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Customers
Source: https://docs.hellocobi.com/api-reference/data/endpoint/customers
POST /v1/data/customers
Upload customer records to the Cobi Data API.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of customer records (up to 5000)
Unique identifier for the customer
Unique identifier for the user
Geographic region (2-letter country code, e.g., "CA" for Canada)
Customer's date of birth
ID of the educational institute
ID of the course
Customer's gender
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "customers", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "customers", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ------------------------------------------------ |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "customers") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/customers \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "550e8400-e29b-41d4-a716-446655440001",
"region": "CA",
"birthdate": "1990-01-01",
"gender": "MALE",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Batch Size**: Upload up to 5000 records at a time for optimal performance
2. **Data Validation**: Ensure all required fields are present and properly formatted
3. **Error Handling**: Implement proper error handling for failed requests
4. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Institutes
Source: https://docs.hellocobi.com/api-reference/data/endpoint/institutes
POST /v1/data/institutes
Upload institute records to the Cobi Data API.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of institute records (up to 5000)
Unique identifier for the institute
Name of the institute
Geographic region (2-letter country code, e.g., "CA" for Canada)
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "institutes", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "institutes", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ------------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "institutes") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/institutes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"institute_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "University of Toronto",
"region": "CA",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Batch Size**: Upload up to 5000 records at a time for optimal performance
2. **Data Validation**: Ensure all required fields are present and properly formatted
3. **Error Handling**: Implement proper error handling for failed requests
4. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Keywords
Source: https://docs.hellocobi.com/api-reference/data/endpoint/keywords
POST /v1/data/keywords
Upload keyword records to the Cobi Data API.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of keyword records (up to 5000)
Unique identifier for the keyword
Name of the keyword
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "keywords", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "keywords", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ----------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "keywords") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/keywords \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"keyword_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Student Discount",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Batch Size**: Upload up to 5000 records at a time for optimal performance
2. **Data Validation**: Ensure all required fields are present and properly formatted
3. **Error Handling**: Implement proper error handling for failed requests
4. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Offers
Source: https://docs.hellocobi.com/api-reference/data/endpoint/offers
POST /v1/data/offers
Upload offer records to the Cobi Data API
**Important**: Offer records have a dependency on shops. The system validates
the `shop_id` field against existing records in the database. You should
upload shops before uploading offers to ensure proper data validation.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of offer records (up to 5000)
Unique identifier for the offer
ID of the shop offering the discount (must reference an existing shop)
Name of the offer
Type of offer (e.g., "percentage", "fixed")
Value of the offer (must be a positive number)
Geographic region (2-letter country code, e.g., "CA" for Canada)
Start date and time of the offer
End date and time of the offer
Comma-separated list of branch IDs where the offer is valid
Comma-separated list of days when the offer is valid
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
Flag indicating if the offer is currently enabled
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "offers", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "offers", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | --------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "offers") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/offers \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"offer_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Student Discount",
"type": "percentage",
"value": 10.0,
"region": "CA",
"allowed_from": "2024-01-01T00:00:00Z",
"allowed_to": "2024-12-31T23:59:59Z",
"allowed_branches": "branch1,branch2",
"allowed_days": "monday,tuesday,wednesday",
"is_enabled": true
}
]
}'
```
## Best Practices
1. **Upload Order**: Upload shops before offers to ensure references exist
2. **Batch Size**: Upload up to 5000 records at a time for optimal performance
3. **Data Validation**: Ensure all required fields are present and properly formatted
4. **Error Handling**: Implement proper error handling for failed requests
5. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Shop Categories
Source: https://docs.hellocobi.com/api-reference/data/endpoint/shop-categories
POST /v1/data/shop-categories
Upload shop category mappings to the Cobi Data API
**Important**: Shop category records have dependencies on both shops and
categories. The system validates both the `shop_id` and `category_id` fields
against existing records in the database. You should upload shops and
categories before uploading shop category mappings to ensure proper data
validation.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of shop category mapping records (up to 5000)
Unique identifier for the shop category mapping
ID of the shop (must reference an existing shop)
ID of the category (must reference an existing category)
Geographic region (2-letter country code, e.g., "CA" for Canada)
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "shop-categories", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "shop-categories", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ------------------------------------------------------ |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "shop-categories") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/shop-categories \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"shop_category_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"category_id": "550e8400-e29b-41d4-a716-446655440002",
"region": "CA",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Upload Order**: Upload shops and categories before shop-categories to ensure references exist
2. **Batch Size**: Upload up to 5000 records at a time for optimal performance
3. **Data Validation**: Ensure all required fields are present and properly formatted
4. **Error Handling**: Implement proper error handling for failed requests
5. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Shop Keywords
Source: https://docs.hellocobi.com/api-reference/data/endpoint/shop-keywords
POST /v1/data/shop-keywords
Upload shop keyword mappings to the Cobi Data API
**Important**: Shop keyword records have dependencies on both shops and
keywords. The system validates both the `shop_id` and `keyword_id` fields
against existing records in the database. You should upload shops and keywords
before uploading shop keyword mappings to ensure proper data validation.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of shop keyword mapping records (up to 5000)
Unique identifier for the shop keyword mapping
ID of the shop (must reference an existing shop)
ID of the keyword (must reference an existing keyword)
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "shop-keywords", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "shop-keywords", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ---------------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "shop-keywords") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/shop-keywords \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"shop_keyword_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"keyword_id": "550e8400-e29b-41d4-a716-446655440002",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Upload Order**: Upload shops and keywords before shop-keywords to ensure references exist
2. **Batch Size**: Upload up to 5000 records at a time for optimal performance
3. **Data Validation**: Ensure all required fields are present and properly formatted
4. **Error Handling**: Implement proper error handling for failed requests
5. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Shop Tags
Source: https://docs.hellocobi.com/api-reference/data/endpoint/shop-tags
POST /v1/data/shop-tags
Upload shop tag mappings to the Cobi Data API
**Important**: Shop tag records have dependencies on both shops and tags. The
system validates both the `shop_id` and `tag_id` fields against existing
records in the database. You should upload shops and tags before uploading
shop tag mappings to ensure proper data validation.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of shop tag mapping records (up to 5000)
Unique identifier for the shop tag mapping
ID of the shop (must reference an existing shop)
ID of the tag (must reference an existing tag)
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "shop-tags", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "shop-tags", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ------------------------------------------------ |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "shop-tags") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/shop-tags \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"shop_tag_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"tag_id": "550e8400-e29b-41d4-a716-446655440002",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Upload Order**: Upload shops and tags before shop-tags to ensure references exist
2. **Batch Size**: Upload up to 5000 records at a time for optimal performance
3. **Data Validation**: Ensure all required fields are present and properly formatted
4. **Error Handling**: Implement proper error handling for failed requests
5. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Shops
Source: https://docs.hellocobi.com/api-reference/data/endpoint/shops
POST /v1/data/shops
Upload shop records to the Cobi Data API.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of shop records (up to 5000)
Unique identifier for the shop
Name of the shop
Geographic region (2-letter country code, e.g., "CA" for Canada)
Flag indicating if the shop is public
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "shops", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "shops", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | -------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "shops") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/shops \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"shop_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Campus Bookstore",
"region": "CA",
"is_public": true,
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Batch Size**: Upload up to 5000 records at a time for optimal performance
2. **Data Validation**: Ensure all required fields are present and properly formatted
3. **Error Handling**: Implement proper error handling for failed requests
4. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Student Favourites
Source: https://docs.hellocobi.com/api-reference/data/endpoint/student-favourites
POST /v1/data/student-favourites
Upload student favourite mappings to the Cobi Data API.
**Important**: Student favourite records have dependencies on both
customers/students and shops. The system validates both the `student_id` and
`shop_id` fields against existing records in the database. You should upload
customer/student and shop data before uploading student favourite mappings to
ensure proper data validation.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of student favourite mapping records (up to 5000)
Unique identifier for the favourite mapping
ID of the student (must reference an existing customer/student)
ID of the shop (must reference an existing shop)
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "student-favourites", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "student-favourites", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | --------------------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "student-favourites") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/student-favourites \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"favourite_id": "550e8400-e29b-41d4-a716-446655440000",
"student_id": "550e8400-e29b-41d4-a716-446655440001",
"shop_id": "550e8400-e29b-41d4-a716-446655440002",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Upload Order**: Upload customers/students and shops before student-favourites to ensure references exist
2. **Batch Size**: Upload up to 5000 records at a time for optimal performance
3. **Data Validation**: Ensure all required fields are present and properly formatted
4. **Error Handling**: Implement proper error handling for failed requests
5. **Rate Limiting**: Be mindful of API rate limits
6. **Soft Deletion**: Use the is\_deleted flag instead of permanently deleting records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Tags
Source: https://docs.hellocobi.com/api-reference/data/endpoint/tags
POST /v1/data/tags
Upload tag records to the Cobi Data API.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of tag records (up to 5000)
Unique identifier for the tag
Name of the tag
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "tags", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "tags", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "tags") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/tags \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"tag_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Student Discount",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
]
}'
```
## Best Practices
1. **Batch Size**: Upload up to 5000 records at a time for optimal performance
2. **Data Validation**: Ensure all required fields are present and properly formatted
3. **Error Handling**: Implement proper error handling for failed requests
4. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Transactions
Source: https://docs.hellocobi.com/api-reference/data/endpoint/transactions
POST /v1/data/transactions
Upload transaction records to the Cobi Data API.
**Important**: Transaction records have a dependency on shops. The system
validates the `shop_id` field against existing records in the database. You
should upload shops before uploading transactions to ensure proper data
validation.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Data API
This endpoint is part of the Cobi Data API (`/v1/data/:type`), which provides a consistent interface for uploading different types of structured data.
## Request Body
An array of transaction records (up to 5000)
Unique identifier for the transaction
ID of the shop where the transaction occurred (must reference an existing
shop)
Geographic region (2-letter country code, e.g., "CA" for Canada)
Amount saved in the transaction (must be a non-negative number)
Total transaction amount (must be a positive number)
ID of the student making the purchase
ID of the offer used
ID of the branch location
Source of the transaction (e.g., "online", "in-store")
Timestamp when the record was created
Timestamp when the record was last updated
Flag indicating if the record is marked as deleted
Name of the discount applied
Type of discount (e.g., "percentage", "fixed")
Value of the discount
## Response
The API returns a JSON response with the following fields:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "transactions", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "transactions", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | --------------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "transactions") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Error Codes
| Status Code | Description | data.status value |
| ----------- | ----------------------------------------- | ----------------- |
| 200 | All records processed successfully | success |
| 207 | Some records failed, others succeeded | partial |
| 400 | Validation or dependency error | error |
| 500 | Internal or unexpected server error | error |
| 401 | Unauthorized - Invalid or missing API key | error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/transactions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440002",
"region": "CA",
"savings": 10.50,
"amount": 100.00,
"source": "online",
"student_id": "550e8400-e29b-41d4-a716-446655440001",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"discount_name": "Student Discount",
"discount_type": "percentage",
"discount_value": 10
}
]
}'
```
## Best Practices
1. **Upload Order**: Upload shops before transactions to ensure references exist
2. **Batch Size**: Upload up to 5000 records at a time for optimal performance
3. **Data Validation**: Ensure all required fields are present and properly formatted
4. **Error Handling**: Implement proper error handling for failed requests
5. **Rate Limiting**: Be mindful of API rate limits
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Interactions
Source: https://docs.hellocobi.com/api-reference/data/endpoint/user-interactions
POST /user-interactions
Record and retrieve user interactions with items (TAG, OFFER, SHOP, KEYWORD).
**Important**: User interaction records have dependencies on students. The
system validates the `student_id` against existing records in the database.
You should upload student data before uploading user interaction records to
ensure proper data validation. The `item_id` field must reference the correct
item type as specified by `item_type`.
## Authentication Required
This endpoint may require API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## About the Endpoint
This endpoint allows you to record and retrieve user interactions with items (TAG, OFFER, SHOP, KEYWORD).
### Source Field for Recommendation Feedback
The `source` field is crucial for tracking recommendation API effectiveness:
* **`null`**: Normal user browsing interactions (not from recommendations)
* **`"COBI"`**: Interactions that originated from recommendation API responses
This enables the recommendation system to:
* Track which recommendations led to user engagement
* Measure recommendation algorithm effectiveness
* Improve future recommendations based on user behavior patterns
## Request Body
Type of interaction. One of: FILTER\_CLICK, SHARE, PAGE\_VIEW, DISCOUNT\_CODE
Psuedo ID of the user
UUID of the student
One of: TAG, OFFER, SHOP
UUID of the item (TAG, OFFER, KEYWORD, or SHOP) as determined by `item_type`
City where the interaction occurred
Country where the interaction occurred
One of: ANDROID, IOS, WEB, OTHER
Event timestamp (positive integer or ISO datetime string)
Source of interaction. Set to 'COBI' when from recommendation API, null
otherwise
Recommendation ID of cobi recommended interaction, null otherwise
## Response
The API returns a JSON response with the created interaction object on success.
* `201 Created` on success
* `400 Bad Request` if validation fails
## Error Codes
| Status Code | Description |
| ----------- | -------------------------------- |
| 201 | Interaction created successfully |
| 400 | Validation error |
| 401 | Unauthorized - Invalid API key |
| 500 | Internal server error |
## Example
```bash theme={null}
curl -X POST https://api.hellocobi.com/user-interactions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"interaction_type": "FILTER_CLICK",
"user_id": "75962b04e5caa7e582e2bc2949587ed9",
"student_id": "b1a2c3d4-e5f6-7890-abcd-1234567890ef",
"item_type": "TAG",
"item_id": "a1b2c3d4-e5f6-7890-abcd-1234567890ab",
"city": "Berlin",
"country": "Germany",
"platform": "WEB",
"event_timestamp": 1718035200,
"source": "COBI",
"recommendation_id": "6fc7eb1d-a516-4a9b-8dcc-313976461ae4"
}'
```
# Branch Model
Source: https://docs.hellocobi.com/api-reference/data/models/branch
The category data model represents information about shop categories in the Cobi system.
**Important**: The branch model has a dependency on the shop model. The
`shop_id` field must reference an existing shop in the system. It's
recommended to upload shop data before uploading branch data.
## Schema
```json theme={null}
{
"type": "object",
"required": ["branch_id", "shop_id", "name", "region"],
"properties": {
"branch_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the branch"
},
"shop_id": {
"type": "string",
"format": "uuid",
"description": "ID of the shop this branch belongs to (must reference an existing shop)"
},
"name": {
"type": "string",
"description": "Name of the branch"
},
"region": {
"type": "string",
"description": "Geographic region (2-letter country code, ISO 3166-1 alpha-2)"
},
"latitude": {
"type": "number",
"nullable": true,
"description": "Latitude coordinate of the branch location"
},
"longitude": {
"type": "number",
"nullable": true,
"description": "Longitude coordinate of the branch location"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ----------- | ------ | ------ | ------------------------------------------------------------------- |
| `branch_id` | string | UUID | Unique identifier for the branch |
| `shop_id` | string | UUID | ID of the shop this branch belongs to (must reference a valid shop) |
| `name` | string | - | Name of the branch |
| `region` | string | - | Geographic region (2-letter country code, e.g., "CA" for Canada) |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `latitude` | number | - | Latitude coordinate of the branch location |
| `longitude` | number | - | Longitude coordinate of the branch location |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"branch_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Downtown Branch",
"region": "CA",
"latitude": 43.65107,
"longitude": -79.347015,
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for branch\_id and shop\_id
2. **Data Dependencies**: Ensure shop\_id references an existing shop
3. **Region Codes**: Use ISO 3166-1 alpha-2 country codes (e.g., "CA" for Canada, "US" for United States)
4. **Coordinates**: When providing latitude and longitude, ensure they are valid geographic coordinates
5. **Data Consistency**: Ensure consistent data across all branch records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Category Model
Source: https://docs.hellocobi.com/api-reference/data/models/category
The category data model represents information about shop categories in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["category_id", "name", "region"],
"properties": {
"category_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the category"
},
"name": {
"type": "string",
"description": "Name of the category"
},
"region": {
"type": "string",
"description": "Geographic region (2-letter country code, ISO 3166-1 alpha-2)"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ------------- | ------ | ------ | ---------------------------------------------------------------- |
| `category_id` | string | UUID | Unique identifier for the category |
| `name` | string | - | Name of the category |
| `region` | string | - | Geographic region (2-letter country code, e.g., "CA" for Canada) |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"category_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Electronics",
"region": "CA",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for category\_id
2. **Region Codes**: Use ISO 3166-1 alpha-2 country codes (e.g., "CA" for Canada, "US" for United States)
3. **Data Consistency**: Ensure consistent data across all category records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Course Model
Source: https://docs.hellocobi.com/api-reference/data/models/course
The course data model represents information about educational courses in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["course_id", "name"],
"properties": {
"course_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the course"
},
"name": {
"type": "string",
"description": "Name of the course"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ----------- | ------ | ------ | -------------------------------- |
| `course_id` | string | UUID | Unique identifier for the course |
| `name` | string | - | Name of the course |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"course_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Introduction to Computer Science",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for course\_id
2. **Data Consistency**: Ensure consistent data across all course records
3. **Naming**: Use the official course name as it appears in the institution's catalog
4. **Soft Deletion**: Use the is\_deleted flag instead of permanently removing records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Customer Model
Source: https://docs.hellocobi.com/api-reference/data/models/customer
The customer data model represents information about your customers in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["customer_id", "user_id", "region"],
"properties": {
"customer_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the customer"
},
"user_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the user"
},
"birthdate": {
"type": "string",
"format": "date",
"nullable": true,
"description": "Customer's date of birth"
},
"institute_id": {
"type": "string",
"format": "uuid",
"nullable": true,
"description": "ID of the educational institute"
},
"course_id": {
"type": "string",
"format": "uuid",
"nullable": true,
"description": "ID of the course"
},
"region": {
"type": "string",
"description": "Geographic region of the customer (2-letter country code, ISO 3166-1 alpha-2)"
},
"gender": {
"type": "string",
"nullable": true,
"description": "Customer's gender"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ------------- | ------ | ------ | ---------------------------------------------------------------- |
| `customer_id` | string | UUID | Unique identifier for the customer |
| `user_id` | string | UUID | Unique identifier for the user |
| `region` | string | - | Geographic region (2-letter country code, e.g., "CA" for Canada) |
### Optional Fields
| Field | Type | Format | Description |
| -------------- | ------- | --------- | -------------------------------------------------- |
| `birthdate` | string | date | Customer's date of birth (YYYY-MM-DD) |
| `institute_id` | string | UUID | ID of the educational institute |
| `course_id` | string | UUID | ID of the course |
| `gender` | string | - | Customer's gender |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "550e8400-e29b-41d4-a716-446655440001",
"birthdate": "1990-01-01",
"institute_id": "550e8400-e29b-41d4-a716-446655440002",
"course_id": "550e8400-e29b-41d4-a716-446655440003",
"region": "CA",
"gender": "MALE",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for all ID fields
2. **Date Format**: Use ISO 8601 date format (YYYY-MM-DD) for birthdate
3. **Region Codes**: Use ISO 3166-1 alpha-2 country codes (e.g., "CA" for Canada, "US" for United States)
4. **Data Consistency**: Ensure consistent data across all customer records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Institute Model
Source: https://docs.hellocobi.com/api-reference/data/models/institute
The institute data model represents information about educational institutions in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["institute_id", "name", "region"],
"properties": {
"institute_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the institute"
},
"name": {
"type": "string",
"description": "Name of the institute"
},
"region": {
"type": "string",
"description": "Geographic region (2-letter country code, ISO 3166-1 alpha-2)"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| -------------- | ------ | ------ | ---------------------------------------------------------------- |
| `institute_id` | string | UUID | Unique identifier for the institute |
| `name` | string | - | Name of the institute |
| `region` | string | - | Geographic region (2-letter country code, e.g., "CA" for Canada) |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"institute_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "University of Toronto",
"region": "CA",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for institute\_id
2. **Region Codes**: Use ISO 3166-1 alpha-2 country codes (e.g., "CA" for Canada, "US" for United States)
3. **Data Consistency**: Ensure consistent data across all institute records
4. **Naming**: Use the official name of the educational institution
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Keyword Model
Source: https://docs.hellocobi.com/api-reference/data/models/keyword
The keyword data model represents information about keywords in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["keyword_id", "name"],
"properties": {
"keyword_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the keyword"
},
"name": {
"type": "string",
"description": "Name of the keyword"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ------------ | ------ | ------ | --------------------------------- |
| `keyword_id` | string | UUID | Unique identifier for the keyword |
| `name` | string | - | Name of the keyword |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"keyword_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Student Discount",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for keyword\_id
2. **Data Consistency**: Ensure consistent data across all keyword records
3. **Naming**: Use clear and descriptive names for keywords
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Offer Model
Source: https://docs.hellocobi.com/api-reference/data/models/offer
The offer data model represents discounts and promotions in the Cobi system.
**Important**: The offer model has a dependency on the shop model. The
`shop_id` field must reference an existing shop. It's recommended to upload
shop data before uploading offers.
## Schema
```json theme={null}
{
"type": "object",
"required": ["offer_id", "shop_id", "name", "type", "value", "region"],
"properties": {
"offer_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the offer"
},
"shop_id": {
"type": "string",
"format": "uuid",
"description": "ID of the shop offering the discount (must reference an existing shop)"
},
"name": {
"type": "string",
"description": "Name of the offer"
},
"type": {
"type": "string",
"description": "Type of offer (e.g., 'percentage', 'fixed')"
},
"value": {
"type": "number",
"format": "float",
"description": "Value of the offer (must be a positive number)"
},
"region": {
"type": "string",
"description": "Geographic region where the offer is valid (2-letter country code, ISO 3166-1 alpha-2)"
},
"allowed_from": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Start date and time of the offer"
},
"allowed_to": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "End date and time of the offer"
},
"allowed_branches": {
"type": "string",
"nullable": true,
"description": "Comma-separated list of branch IDs where the offer is valid"
},
"allowed_days": {
"type": "string",
"nullable": true,
"description": "Comma-separated list of days when the offer is valid"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
},
"is_enabled": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the offer is currently enabled"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ---------- | ------ | ------ | ------------------------------------------------------------------- |
| `offer_id` | string | UUID | Unique identifier for the offer |
| `shop_id` | string | UUID | ID of the shop offering the discount (must reference existing shop) |
| `name` | string | - | Name of the offer |
| `type` | string | - | Type of offer (e.g., "percentage", "fixed") |
| `value` | number | float | Value of the offer (must be a positive number) |
| `region` | string | - | Geographic region (2-letter country code, e.g., "CA" for Canada) |
### Optional Fields
| Field | Type | Format | Description |
| ------------------ | ------- | --------- | ----------------------------------------------------- |
| `allowed_from` | string | date-time | Start date and time of the offer (ISO 8601) |
| `allowed_to` | string | date-time | End date and time of the offer (ISO 8601) |
| `allowed_branches` | string | - | Comma-separated list of branch IDs |
| `allowed_days` | string | - | Comma-separated list of days (e.g., "monday,tuesday") |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
| `is_enabled` | boolean | - | Flag indicating if the offer is currently enabled |
## Example
```json theme={null}
{
"offer_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Student Discount",
"type": "percentage",
"value": 10.0,
"region": "CA",
"allowed_from": "2024-01-01T00:00:00Z",
"allowed_to": "2024-12-31T23:59:59Z",
"allowed_branches": "branch1,branch2",
"allowed_days": "monday,tuesday,wednesday",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false,
"is_enabled": true
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for all ID fields
2. **Date/Time Format**: Use ISO 8601 format for date-time fields
3. **Region Codes**: Use ISO 3166-1 alpha-2 country codes (e.g., "CA" for Canada, "US" for United States)
4. **Offer Types**: Use consistent offer types across your system (e.g., "percentage", "fixed")
5. **Value Format**: Use decimal numbers for monetary values
6. **List Format**: Use comma-separated values for allowed\_branches and allowed\_days
7. **Data Dependencies**: Ensure shop\_id references an existing shop
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Shop Model
Source: https://docs.hellocobi.com/api-reference/data/models/shop
The shop data model represents information about shops in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["shop_id", "name", "region"],
"properties": {
"shop_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the shop"
},
"name": {
"type": "string",
"description": "Name of the shop"
},
"region": {
"type": "string",
"description": "Geographic region (2-letter country code, ISO 3166-1 alpha-2)"
},
"is_public": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the shop is public"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| --------- | ------ | ------ | ---------------------------------------------------------------- |
| `shop_id` | string | UUID | Unique identifier for the shop |
| `name` | string | - | Name of the shop |
| `region` | string | - | Geographic region (2-letter country code, e.g., "CA" for Canada) |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `is_public` | boolean | - | Flag indicating if the shop is public |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"shop_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Campus Bookstore",
"region": "CA",
"is_public": true,
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for shop\_id
2. **Region Codes**: Use ISO 3166-1 alpha-2 country codes (e.g., "CA" for Canada, "US" for United States)
3. **Data Consistency**: Ensure consistent data across all shop records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Shop Category Model
Source: https://docs.hellocobi.com/api-reference/data/models/shop-category
The shop category data model represents the mappings between shops and categories in the Cobi system.
**Important**: The shop category model has dependencies on both the shop and
category models. The `shop_id` field must reference an existing shop and the
`category_id` field must reference an existing category. It's recommended to
upload shop and category data before uploading shop category mappings.
## Schema
```json theme={null}
{
"type": "object",
"required": ["shop_category_id", "shop_id", "category_id", "region"],
"properties": {
"shop_category_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the shop category mapping"
},
"shop_id": {
"type": "string",
"format": "uuid",
"description": "ID of the shop (must reference an existing shop)"
},
"category_id": {
"type": "string",
"format": "uuid",
"description": "ID of the category (must reference an existing category)"
},
"region": {
"type": "string",
"description": "Geographic region (2-letter country code, ISO 3166-1 alpha-2)"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ------------------ | ------ | ------ | ---------------------------------------------------------------- |
| `shop_category_id` | string | UUID | Unique identifier for the shop category mapping |
| `shop_id` | string | UUID | ID of the shop (must reference an existing shop) |
| `category_id` | string | UUID | ID of the category (must reference an existing category) |
| `region` | string | - | Geographic region (2-letter country code, e.g., "CA" for Canada) |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"shop_category_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"category_id": "550e8400-e29b-41d4-a716-446655440002",
"region": "CA",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for all ID fields
2. **Data Dependencies**: Ensure shop\_id references an existing shop and category\_id references an existing category
3. **Region Codes**: Use ISO 3166-1 alpha-2 country codes (e.g., "CA" for Canada, "US" for United States)
4. **Data Consistency**: Ensure consistent data across all shop category mapping records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Shop Keyword Model
Source: https://docs.hellocobi.com/api-reference/data/models/shop-keyword
The shop keyword data model represents the mappings between shops and keywords in the Cobi system.
**Important**: The shop keyword model has dependencies on both the shop and
keyword models. The `shop_id` field must reference an existing shop and the
`keyword_id` field must reference an existing keyword. It's recommended to
upload shop and keyword data before uploading shop keyword mappings.
## Schema
```json theme={null}
{
"type": "object",
"required": ["shop_keyword_id", "shop_id", "keyword_id"],
"properties": {
"shop_keyword_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the shop keyword mapping"
},
"shop_id": {
"type": "string",
"format": "uuid",
"description": "ID of the shop (must reference an existing shop)"
},
"keyword_id": {
"type": "string",
"format": "uuid",
"description": "ID of the keyword (must reference an existing keyword)"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ----------------- | ------ | ------ | ------------------------------------------------------ |
| `shop_keyword_id` | string | UUID | Unique identifier for the shop keyword mapping |
| `shop_id` | string | UUID | ID of the shop (must reference an existing shop) |
| `keyword_id` | string | UUID | ID of the keyword (must reference an existing keyword) |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"shop_keyword_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"keyword_id": "550e8400-e29b-41d4-a716-446655440002",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for all ID fields
2. **Data Dependencies**: Ensure shop\_id references an existing shop and keyword\_id references an existing keyword
3. **Data Consistency**: Ensure consistent data across all shop keyword mapping records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Shop Tag Model
Source: https://docs.hellocobi.com/api-reference/data/models/shop-tag
The shop tag data model represents the mappings between shops and tags in the Cobi system.
**Important**: The shop tag model has dependencies on both the shop and tag
models. The `shop_id` field must reference an existing shop and the `tag_id`
field must reference an existing tag. It's recommended to upload shop and tag
data before uploading shop tag mappings.
## Schema
```json theme={null}
{
"type": "object",
"required": ["shop_tag_id", "shop_id", "tag_id"],
"properties": {
"shop_tag_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the shop tag mapping"
},
"shop_id": {
"type": "string",
"format": "uuid",
"description": "ID of the shop (must reference an existing shop)"
},
"tag_id": {
"type": "string",
"format": "uuid",
"description": "ID of the tag (must reference an existing tag)"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ------------- | ------ | ------ | ------------------------------------------------ |
| `shop_tag_id` | string | UUID | Unique identifier for the shop tag mapping |
| `shop_id` | string | UUID | ID of the shop (must reference an existing shop) |
| `tag_id` | string | UUID | ID of the tag (must reference an existing tag) |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"shop_tag_id": "550e8400-e29b-41d4-a716-446655440000",
"shop_id": "550e8400-e29b-41d4-a716-446655440001",
"tag_id": "550e8400-e29b-41d4-a716-446655440002",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for all ID fields
2. **Data Dependencies**: Ensure shop\_id references an existing shop and tag\_id references an existing tag
3. **Data Consistency**: Ensure consistent data across all shop tag mapping records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Student Favourite Model
Source: https://docs.hellocobi.com/api-reference/data/models/student-favourite
The student favourite data model represents the mappings between students and their favourite shops in the Cobi system.
**Important**: The student favourite model has dependencies on both the
customer/student and shop models. The `student_id` field must reference an
existing customer/student and the `shop_id` field must reference an existing
shop. It's recommended to upload customer/student and shop data before
uploading student favourite mappings.
## Schema
```json theme={null}
{
"type": "object",
"required": ["student_id", "shop_id"],
"properties": {
"favourite_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the favourite mapping"
},
"student_id": {
"type": "string",
"format": "uuid",
"description": "ID of the student (must reference an existing customer/student)"
},
"shop_id": {
"type": "string",
"format": "uuid",
"description": "ID of the shop (must reference an existing shop)"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ------------ | ------ | ------ | --------------------------------------------------------------- |
| `student_id` | string | UUID | ID of the student (must reference an existing customer/student) |
| `shop_id` | string | UUID | ID of the shop (must reference an existing shop) |
### Optional Fields
| Field | Type | Format | Description |
| -------------- | ------- | --------- | -------------------------------------------------- |
| `favourite_id` | string | UUID | Unique identifier for the favourite mapping |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"favourite_id": "550e8400-e29b-41d4-a716-446655440000",
"student_id": "550e8400-e29b-41d4-a716-446655440001",
"shop_id": "550e8400-e29b-41d4-a716-446655440002",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for all ID fields
2. **Data Dependencies**: Ensure student\_id references an existing customer/student and shop\_id references an existing shop
3. **Data Consistency**: Ensure consistent data across all student favourite mapping records
4. **Soft Deletion**: Use the is\_deleted flag instead of permanently deleting records
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Tag Model
Source: https://docs.hellocobi.com/api-reference/data/models/tag
The tag data model represents information about tags in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["tag_id", "name"],
"properties": {
"tag_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the tag"
},
"name": {
"type": "string",
"description": "Name of the tag"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| -------- | ------ | ------ | ----------------------------- |
| `tag_id` | string | UUID | Unique identifier for the tag |
| `name` | string | - | Name of the tag |
### Optional Fields
| Field | Type | Format | Description |
| ------------ | ------- | --------- | -------------------------------------------------- |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
## Example
```json theme={null}
{
"tag_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Student Discount",
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for tag\_id
2. **Data Consistency**: Ensure consistent data across all tag records
3. **Naming**: Use clear and descriptive names for tags
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Transaction Model
Source: https://docs.hellocobi.com/api-reference/data/models/transaction
The transaction data model represents purchase records in the Cobi system, including savings and discount information.
**Important**: The transaction model has a dependency on the shop model. The
`shop_id` field must reference an existing shop. It's recommended to upload
shop data before uploading transactions.
## Schema
```json theme={null}
{
"type": "object",
"required": ["transaction_id", "shop_id", "region", "savings", "amount"],
"properties": {
"transaction_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the transaction"
},
"student_id": {
"type": "string",
"format": "uuid",
"nullable": true,
"description": "ID of the student making the purchase"
},
"shop_id": {
"type": "string",
"format": "uuid",
"description": "ID of the shop where the transaction occurred (must reference an existing shop)"
},
"offer_id": {
"type": "string",
"format": "uuid",
"nullable": true,
"description": "ID of the offer used"
},
"branch_id": {
"type": "string",
"format": "uuid",
"nullable": true,
"description": "ID of the branch location"
},
"region": {
"type": "string",
"description": "Geographic region of the transaction (2-letter country code, ISO 3166-1 alpha-2)"
},
"source": {
"type": "string",
"nullable": true,
"description": "Source of the transaction (e.g., 'online', 'in-store')"
},
"savings": {
"type": "number",
"format": "float",
"description": "Amount saved in the transaction (must be a non-negative number)"
},
"amount": {
"type": "number",
"format": "float",
"description": "Total transaction amount (must be a positive number)"
},
"created": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was created"
},
"updated": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Timestamp when the record was last updated"
},
"is_deleted": {
"type": "boolean",
"nullable": true,
"description": "Flag indicating if the record is marked as deleted"
},
"discount_name": {
"type": "string",
"nullable": true,
"description": "Name of the discount applied"
},
"discount_type": {
"type": "string",
"nullable": true,
"description": "Type of discount (e.g., 'percentage', 'fixed')"
},
"discount_value": {
"type": "number",
"format": "float",
"nullable": true,
"description": "Value of the discount"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ---------------- | ------ | ------ | ---------------------------------------------------------------------------- |
| `transaction_id` | string | UUID | Unique identifier for the transaction |
| `shop_id` | string | UUID | ID of the shop where the transaction occurred (must reference existing shop) |
| `region` | string | - | Geographic region (2-letter country code, e.g., "CA" for Canada) |
| `savings` | number | float | Amount saved in the transaction (must be a non-negative number) |
| `amount` | number | float | Total transaction amount (must be a positive number) |
### Optional Fields
| Field | Type | Format | Description |
| ---------------- | ------- | --------- | ------------------------------------------------------ |
| `student_id` | string | UUID | ID of the student making the purchase |
| `offer_id` | string | UUID | ID of the offer used |
| `branch_id` | string | UUID | ID of the branch location |
| `source` | string | - | Source of the transaction (e.g., "online", "in-store") |
| `created` | string | date-time | Timestamp when the record was created |
| `updated` | string | date-time | Timestamp when the record was last updated |
| `is_deleted` | boolean | - | Flag indicating if the record is marked as deleted |
| `discount_name` | string | - | Name of the discount applied |
| `discount_type` | string | - | Type of discount (e.g., "percentage", "fixed") |
| `discount_value` | number | float | Value of the discount |
## Example
```json theme={null}
{
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"student_id": "550e8400-e29b-41d4-a716-446655440001",
"shop_id": "550e8400-e29b-41d4-a716-446655440002",
"offer_id": "550e8400-e29b-41d4-a716-446655440003",
"branch_id": "550e8400-e29b-41d4-a716-446655440004",
"region": "CA",
"source": "online",
"savings": 10.5,
"amount": 100.0,
"created": "2023-01-15T14:30:00Z",
"updated": "2023-06-22T09:15:30Z",
"is_deleted": false,
"discount_name": "Student Discount",
"discount_type": "percentage",
"discount_value": 10
}
```
## Best Practices
1. **UUID Format**: Use valid UUID v4 format for all ID fields
2. **Numeric Values**: Use decimal numbers for monetary values
3. **Region Codes**: Use ISO 3166-1 alpha-2 country codes (e.g., "CA" for Canada, "US" for United States)
4. **Data Consistency**: Ensure consistent data across all transaction records
5. **Discount Information**: Provide complete discount details when available
6. **Data Dependencies**: Ensure shop\_id references an existing shop
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Interaction Model
Source: https://docs.hellocobi.com/api-reference/data/models/user-interaction
The user interaction data model represents a user's interaction with an item (TAG, OFFER, KEYWORD, or SHOP) in the Cobi system.
**Important**: User interaction records have dependencies students. The system
validates the `student_id` against existing records in the database. You
should upload student data before uploading user interaction records to ensure
proper data validation. The `item_id` field must reference the correct item
type as specified by `item_type`.
## Schema
```json theme={null}
{
"type": "object",
"required": [
"interaction_type",
"student_id",
"item_type",
"event_timestamp"
],
"properties": {
"interaction_type": {
"type": "string",
"description": "Type of interaction. One of: FILTER_CLICK, SHARE, PAGE_VIEW, DISCOUNT_CODE"
},
"user_id": {
"type": "string",
"format": "uuid",
"description": "UUID of the student"
},
"student_id": {
"type": "string",
"format": "uuid",
"nullable": true,
"description": "UUID of the student"
},
"item_type": {
"type": "string",
"description": "One of: TAG, OFFER, SHOP, KEYWORD"
},
"item_id": {
"type": "string",
"format": "uuid",
"description": "UUID of the item (TAG, OFFER, KEYWORD or SHOP) as determined by item_type"
},
"city": {
"type": "string",
"description": "City where the interaction occurred"
},
"country": {
"type": "string",
"description": "Country where the interaction occurred"
},
"platform": {
"type": "string",
"description": "One of: ANDROID, IOS, WEB, OTHER"
},
"event_timestamp": {
"type": ["integer", "string"],
"description": "Event timestamp (positive integer or ISO datetime string)"
}
"source": {
"type": "string",
"nullable": true,
"description": "Source of interaction. Set to 'COBI' when the interaction is from a recommendation API response, null otherwise"
},
"recommendation_id": {
"type": "string",
"nullable": true,
"description": "Recommendation ID of cobi recommended interaction, null otherwise"
},
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Description |
| ------------------ | -------------- | ----------------------------------------------------------------------------- |
| `interaction_type` | string | Type of interaction. One of: FILTER\_CLICK, SHARE, PAGE\_VIEW, DISCOUNT\_CODE |
| `item_type` | string | One of: TAG, OFFER, SHOP |
| `item_id` | string (UUID) | UUID of the item (TAG, OFFER, KEYWORD or SHOP) as determined by `item_type` |
| `event_timestamp` | int/ISO string | Event timestamp (positive integer or ISO datetime string) |
### Optional Fields
| Field | Type | Description |
| ------------------- | ------------- | --------------------------------------------------------------------------------- |
| `user_id` | string | Psuedo Id of the student |
| `student_id` | string (UUID) | UUID of the student |
| `city` | string | City where the interaction occurred |
| `country` | string | Country where the interaction occurred |
| `platform` | string | One of: ANDROID, IOS, WEB, OTHER |
| `source` | string | Source of interaction. Set to 'COBI' when from recommendation API, null otherwise |
| `recommendation_id` | string (UUID) | Source of interaction. Set to 'COBI' when from recommendation API, null otherwise |
## Example
```json theme={null}
{
"interaction_type": "FILTER_CLICK",
"user_id": "75962b04e5caa7e582e2bc2949587ed9"
"student_id": null,
"item_type": "TAG",
"item_id": "a1b2c3d4-e5f6-7890-abcd-1234567890ab",
"city": "Berlin",
"country": "Germany",
"platform": "WEB",
"event_timestamp": 1718035200,
"source": "COBI",
"recommendation_id": "6fc7eb1d-a516-4a9b-8dcc-313976461ae4"
}
```
## Source Field for Recommendation Feedback
The `source` field is used to track whether user interactions originated from recommendation API responses:
* **`null`**: The interaction occurred through normal user browsing (not from a recommendation)
* **`"COBI"`**: The interaction occurred when a user clicked on or interacted with an item that was recommended by the Cobi recommendation API
This field is crucial for:
* **Feedback Loop**: Helps the recommendation system understand which recommendations led to user engagement
* **Analytics**: Track the effectiveness of recommendation algorithms
* **Model Training**: Improve future recommendations based on user interaction patterns
### Example Usage
When a user receives recommendations from the API and clicks on a recommended offer:
```json theme={null}
{
"interaction_type": "PAGE_VIEW",
"student_id": "00000000-9779-438e-ae0d-b52099f6be51",
"item_type": "OFFER",
"item_id": "00000000-aaca-4844-9cb0-54af8f897322",
"event_timestamp": 1718035200,
"source": "COBI"
}
```
## Best Practices
1. **Data Validation**: Ensure all required fields are present and properly formatted.
2. **Timestamps**: Use a valid positive integer or ISO 8601 string for `event_timestamp`.
3. **Platform**: Use one of the allowed values for `platform`.
4. **Source Tracking**: Always set `source` to "COBI" when tracking interactions from recommendation API responses.
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Overview
Source: https://docs.hellocobi.com/api-reference/data/overview
The Cobi Data API provides a consistent interface for uploading different types of structured data into the Cobi system for analytics and segmentation.
The Cobi Data API is the foundation for all data integration with the Cobi
platform.
## API Structure
All Data API endpoints follow the same URL pattern:
```http URL Pattern theme={null}
POST /v1/data/:type
```
```http Customers Example theme={null}
POST /v1/data/customers
```
```http Transactions Example theme={null}
POST /v1/data/transactions
```
```http Offers Example theme={null}
POST /v1/data/offers
```
```http Shops Example theme={null}
POST /v1/data/shops
```
```http Branches Example theme={null}
POST /v1/data/branches
```
```http Categories Example theme={null}
POST /v1/data/categories
```
```http Shop Categories Example theme={null}
POST /v1/data/shop-categories
```
```http Institutes Example theme={null}
POST /v1/data/institutes
```
```http Courses Example theme={null}
POST /v1/data/courses
```
```http Interactions Example theme={null}
POST /user-interactions
```
Where `:type` is one of:
Upload customer profile information
Upload transaction records with savings and discounts
Upload offer details for promotions and discounts
Upload shop information
Upload branch information (requires shops to exist first)
Upload category information for product classification
Upload shop-category associations (requires shops and categories to exist
first)
Upload educational institution information
Upload course information
Upload tag information
Upload shop-tag associations (requires shops and tags to exist first)
Upload keyword information
Upload shop-keyword associations (requires shops and keywords to exist
first)
Upload student-favourites associations (requires customer and shops to exist
first)
Record user interactions with items (TAG, OFFER, SHOP) for analytics
**Data Dependencies**: Some data types have dependencies on other types. For
example, branches require valid shop\_id references. It's important to upload
data in the correct order: 1. Upload shops first 2. Then upload branches that
reference those shops
## Common Features
Each endpoint accepts a batch of records (up to 5000) in a single request:
```json theme={null}
{
"records": [
{ /* record 1 */ },
{ /* record 2 */ },
// ...up to 5000 records
]
}
```
This batch processing capability allows for efficient data uploads and reduces the number of API calls needed.
All endpoints require API key authentication via the Authorization header:
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
Contact our support team to obtain your API key.
All endpoints return a consistent response format:
```json theme={null}
{
"request_id": "string", // Unique ID for the overall request
"type": "branches", // Type of data being processed
"batches": [
{
"batch": 1, // Sequential batch number
"status": 200, // HTTP-like status code (200, 207, 400, 500)
"data": {
"status": "success", // "success", "partial", or "error"
"type": "branches", // Matches the top-level type
"message": "Records uploaded successfully", // Summary message
"request_id": "string",
"batch_id": "string",
"error": "" // Optional — empty or null on success; contains error details otherwise
}
}
]
}
```
| Field | Type | Description |
| --------------------------- | ------ | ----------------------------------------------- |
| `request_id` | string | Unique ID for the overall request |
| `type` | string | Type of data being processed (e.g., "branches") |
| `batches` | array | Array of batch results |
| `batches[].batch` | int | Sequential batch number |
| `batches[].status` | int | HTTP-like status code (200, 207, 400, 500) |
| `batches[].data` | object | Batch result details |
| `batches[].data.status` | string | "success", "partial", or "error" |
| `batches[].data.type` | string | Matches the top-level type |
| `batches[].data.message` | string | Summary message of the batch result |
| `batches[].data.request_id` | string | Unique request ID for the batch |
| `batches[].data.batch_id` | string | Unique batch ID |
| `batches[].data.error` | string | Error details (empty or null on success) |
## Available Endpoints
Upload customer profile records with demographic and identification details
Upload transaction records with savings, discounts, and purchase information
Upload offer details including validity periods and restrictions
Upload shop information including location and visibility settings
Upload branch information with location coordinates and shop association
Upload category information for product classification and organization
Upload shop-category associations to organize shops by category
Upload educational institution information including region and status
Upload course information with official names and status
Record user interactions with items (TAG, OFFER, SHOP) for analytics
## Best Practices
* Upload up to 5000 records at a time for optimal performance - Consider
scheduling regular batch uploads rather than individual API calls -
Implement retry logic with exponential backoff for failed requests
* Ensure all required fields are present and properly formatted - Validate
UUIDs and other formatted fields before sending - Use consistent region
codes across all data types
* Store your API key securely - Rotate API keys periodically - Use HTTPS for
all API requests
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Customer Recommendations
Source: https://docs.hellocobi.com/api-reference/recommendations/endpoint/customers
GET /v1/recommendations/customers/{customer_id}
Get personalized recommendations for a specific customer based on their preferences and behavior.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## Path Parameters
Unique identifier for the customer to get recommendations for
## Query Parameters
Whether to include segment-based recommendations in the response. Defaults to
false.
Filter recommendations by region (e.g., "BH" for Bahrain). Comma-separated
values for multiple regions.
## Response
The API returns a JSON array of recommendation objects:
```json theme={null}
[
{
"recommendation_id": "00000000-1234-5678-9abc-def012345678",
"name": "Recommended For you",
"student_id": "00000000-9779-438e-ae0d-b52099f6be51",
"offers": [
"00000000-e9c5-4b43-9d46-4cd02baabc6a",
"00000002-c1f0-44d4-af13-32028f522f31",
"00000001-4d21-4206-8ced-ff2a11162bf0"
],
"type": "SELF"
}
]
```
| Field | Type | Description |
| ------------------- | ------ | ------------------------------------------------- |
| `recommendation_id` | string | Unique identifier for the recommendation |
| `name` | string | Human-readable name for the recommendation set |
| `student_id` | string | UUID of the customer (matches the path parameter) |
| `offers` | array | Array of offer UUIDs that are recommended |
| `type` | string | Type of recommendation (SELF or SEGMENT) |
## Error Codes
| Status Code | Description |
| ----------- | ----------------------------------------- |
| 200 | Recommendations retrieved successfully |
| 400 | Invalid customer\_id format |
| 401 | Unauthorized - Invalid or missing API key |
| 404 | Customer not found |
| 500 | Internal server error |
## Example
```bash theme={null}
curl -X GET "https://api.hellocobi.com/v1/recommendations/customers/00000000-9779-438e-ae0d-b52099f6be51?includeSegments=false®ions=BH" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
## Best Practices
1. **Caching**: Cache recommendations for frequently requested customers to improve performance
2. **Error Handling**: Implement proper error handling for cases where no recommendations are available
3. **Rate Limiting**: Be mindful of API rate limits (1000 requests per minute)
4. **Validation**: Validate the customer\_id format before making the request
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Segment Recommendations
Source: https://docs.hellocobi.com/api-reference/recommendations/endpoint/segments
GET /v1/recommendations/segments/{segment_id}
Get recommendations for a specific customer segment based on collective preferences.
## Authentication Required
This endpoint requires API key authentication via Bearer token in the Authorization header.
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
## Path Parameters
Unique identifier for the customer segment to get recommendations for
## Response
The API returns a JSON object with segment recommendations:
```json theme={null}
{
"recommendation_id": "00000000-1234-5678-9abc-def012345678",
"name": "Early Twenties",
"segment_id": "cmcapwfrl01zk3m2t0axbh05e",
"offers": [
"00000000-aaca-4844-9cb0-54af8f897322",
"00000001-c020-4d37-8d6e-4e4e30ff5671",
"00000002-5fe2-466c-b58a-5ac8db631334"
],
"type": "SEGMENT"
}
```
| Field | Type | Description |
| ------------------- | ------ | ------------------------------------------------------ |
| `recommendation_id` | string | Unique identifier for the recommendation |
| `name` | string | Human-readable name for the segment |
| `segment_id` | string | Identifier of the segment (matches the path parameter) |
| `offers` | array | Array of offer UUIDs that are recommended |
| `type` | string | Type of recommendation (SELF or SEGMENT) |
## Error Codes
| Status Code | Description |
| ----------- | ----------------------------------------- |
| 200 | Recommendations retrieved successfully |
| 400 | Invalid segment\_id format |
| 401 | Unauthorized - Invalid or missing API key |
| 404 | Segment not found |
| 500 | Internal server error |
## Example
```bash theme={null}
curl -X GET https://api.hellocobi.com/v1/recommendations/segments/cmcapwfrl00zk3m2t0axba05e \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
## Best Practices
1. **Caching**: Cache segment recommendations as they change less frequently than individual customer recommendations
2. **Error Handling**: Implement proper error handling for cases where no recommendations are available
3. **Rate Limiting**: Be mindful of API rate limits (1000 requests per minute)
4. **Validation**: Validate the segment\_id format before making the request
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Customer Recommendation Model
Source: https://docs.hellocobi.com/api-reference/recommendations/models/customer-recommendation
The customer recommendation model represents personalized recommendations for a specific customer in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["recommendation_id", "name", "student_id", "offers", "type"],
"properties": {
"recommendation_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the recommendation"
},
"name": {
"type": "string",
"description": "Human-readable name for the recommendation set"
},
"student_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the customer"
},
"offers": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "Array of offer UUIDs that are recommended for the customer"
},
"type": {
"type": "string",
"enum": ["SELF", "SEGMENT"],
"description": "Type of recommendation - SELF for individual customer recommendations, SEGMENT for segment-based recommendations"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ------------------- | ------ | ------ | ---------------------------------------------- |
| `recommendation_id` | string | UUID | Unique identifier for the recommendation |
| `name` | string | - | Human-readable name for the recommendation set |
| `student_id` | string | UUID | Unique identifier for the customer |
| `offers` | array | - | Array of offer UUIDs that are recommended |
| `type` | string | enum | Type of recommendation (SELF or SEGMENT) |
## Example
```json theme={null}
{
"recommendation_id": "00000000-1234-5678-9abc-def012345678",
"name": "Recommended For you",
"student_id": "00000000-9779-438e-ae0d-b52099f6be51",
"offers": [
"00000000-aaca-4844-9cb0-54af8f897322",
"00000001-c020-4d37-8d6e-4e4e30ff5671",
"00000002-5fe2-466c-b58a-5ac8db631334"
],
"type": "SELF"
}
```
## Best Practices
1. **UUID Format**: Ensure all offer IDs are valid UUID v4 format
2. **Data Validation**: Validate that all offer IDs exist in your system before using them
3. **Caching**: Consider caching recommendations to improve performance
4. **Error Handling**: Handle cases where the offers array might be empty
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Segment Recommendation Model
Source: https://docs.hellocobi.com/api-reference/recommendations/models/segment-recommendation
The segment recommendation model represents recommendations for a specific customer segment in the Cobi system.
## Schema
```json theme={null}
{
"type": "object",
"required": ["recommendation_id", "name", "segment_id", "offers", "type"],
"properties": {
"recommendation_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the recommendation"
},
"name": {
"type": "string",
"description": "Human-readable name for the segment"
},
"segment_id": {
"type": "string",
"description": "Unique identifier for the customer segment"
},
"offers": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "Array of offer UUIDs that are recommended for the segment"
},
"type": {
"type": "string",
"enum": ["SELF", "SEGMENT"],
"description": "Type of recommendation - SELF for individual customer recommendations, SEGMENT for segment-based recommendations"
}
}
}
```
## Field Descriptions
### Required Fields
| Field | Type | Format | Description |
| ------------------- | ------ | ------ | ------------------------------------------ |
| `recommendation_id` | string | UUID | Unique identifier for the recommendation |
| `name` | string | - | Human-readable name for the segment |
| `segment_id` | string | - | Unique identifier for the customer segment |
| `offers` | array | - | Array of offer UUIDs that are recommended |
| `type` | string | enum | Type of recommendation (SELF or SEGMENT) |
## Example
```json theme={null}
{
"recommendation_id": "00000000-1234-5678-9abc-def012345678",
"name": "Early Twenties",
"segment_id": "cmcapwfrl01zk3m2t0axbh05e",
"offers": [
"00000000-aaca-4844-9cb0-54af8f897322",
"00000001-c020-4d37-8d6e-4e4e30ff5671",
"00000002-5fe2-466c-b58a-5ac8db631334"
],
"type": "SEGMENT"
}
```
## Best Practices
1. **UUID Format**: Ensure all offer IDs are valid UUID v4 format
2. **Data Validation**: Validate that all offer IDs exist in your system before using them
3. **Caching**: Consider caching segment recommendations as they change less frequently than individual customer recommendations
4. **Error Handling**: Handle cases where the offers array might be empty
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Overview
Source: https://docs.hellocobi.com/api-reference/recommendations/overview
The Cobi Recommendations API provides personalized recommendations for customers and segments based on their preferences and behavior.
The Cobi Recommendations API delivers personalized recommendations to enhance
user engagement and improve conversion rates.
## API Structure
All Recommendations API endpoints follow the same URL pattern:
```http URL Pattern theme={null}
GET /v1/recommendations/:type/:id
```
```http Customers Example theme={null}
GET /v1/recommendations/customers/{customer_id}
```
```http Segments Example theme={null}
GET /v1/recommendations/segments/{segment_id}
```
Where `:type` is one of:
Get personalized recommendations for a specific customer
Get recommendations for a specific customer segment
## Common Features
All endpoints require API key authentication via the Authorization header:
```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```
Contact our support team to obtain your API key.
All endpoints return a consistent response format:
```json theme={null}
[
{
"name": "string",
"student_id": "string (UUID)",
"offers": ["string (UUID)"]
}
]
```
The response is an array of recommendation objects, each containing:
* `name`: Human-readable name for the recommendation set
* `student_id` or `segment_id`: Identifier for the customer or segment
* `offers`: Array of offer UUIDs that are recommended
Recommendations are computationally intensive. Please be mindful of rate limits:
* Maximum 1000 requests per minute per API key
* Consider caching recommendations for frequently requested customers/segments
## Available Endpoints
Get personalized recommendations for a specific customer based on their
preferences and behavior
Get recommendations for a specific customer segment based on collective
preferences
## Best Practices
* Cache recommendations for frequently requested customers/segments -
Implement proper error handling for failed requests - Use appropriate
timeouts for recommendation requests
* Ensure customer\_id and segment\_id are valid UUIDs - Handle cases where no
recommendations are available - Validate offer IDs returned in
recommendations
* Store your API key securely - Rotate API keys periodically - Use HTTPS for
all API requests
Need help? Contact our support team at [support@hellocobi.com](mailto:support@hellocobi.com)
# Authentication
Source: https://docs.hellocobi.com/authentication
Secure your Cobi API requests with Bearer token authentication
## API Keys
All Cobi API requests are authenticated with an API key passed as a Bearer token.
To get an API key:
1. Contact [support@hellocobi.com](mailto:support@hellocobi.com) with your organisation details and intended use case
2. You'll receive a unique key scoped to your organisation
## Making Authenticated Requests
Include your API key in the `Authorization` header on every request:
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/customers \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"records": [...]}'
```
## Security Best Practices
Store your API key in environment variables and never hard-code it in source files or commit it to version control. If you suspect a key has been compromised, contact [support@hellocobi.com](mailto:support@hellocobi.com) to get it rotated immediately. All API traffic must use HTTPS.
## Error Responses
| Status | Meaning | Resolution |
| ------------------ | ---------------------------------------------- | ------------------------------------------------------ |
| `401 Unauthorized` | Key is missing or invalid | Check your `Authorization` header format and key value |
| `403 Forbidden` | Key does not have permission for this resource | Contact support to verify your key's scope |
```json theme={null}
{
"error": "Unauthorized",
"message": "Invalid or missing API key"
}
```
# Backend Service
Source: https://docs.hellocobi.com/deployment/backend
Environment variables, secrets, and Helm values for the Cobi backend service
## Overview
The backend is a Node.js REST API that handles authentication, business logic, data-connector queries, and LLM orchestration. It runs on port `3000` and is exposed through Gateway API or Ingress, usually on its own API hostname.
Set `CONNECT_SERVICE_URL` to the in-cluster Connect service URL when `connect.enabled: true`, for example `http://cobi-dashboard-connect:8000`.
## Required Secrets
Create the backend Kubernetes Secret **before** running `helm install`. The backend will crash-loop if the Secret is missing or incomplete.
```bash theme={null}
kubectl create secret generic cobi-backend-secrets \
--namespace cobi \
# ── Database ─────────────────────────────────────────────────────────
--from-literal=DATABASE_URL="postgres://postgres:@:5432/dashboard_auth?schema=public" \
--from-literal=CORE_DATABASE_URL="postgres://postgres:@:5432/cobi_core?schema=public" \
--from-literal=ENABLE_MIGRATIONS="true" \
# ── Auth ─────────────────────────────────────────────────────────────
--from-literal=BETTER_AUTH_SECRET="" \
--from-literal=BETTER_AUTH_URL="https://api.example.com" \
--from-literal=FRONTEND_URL="https://app.example.com" \
--from-literal=CONNECT_SERVICE_URL="http://cobi-dashboard-connect:8000" \
# ── LLM inference ────────────────────────────────────────────────────
--from-literal=AI_PROVIDER="openai" \
--from-literal=OPENAI_API_KEY="not-used" \
--from-literal=OPENAI_BASE_URL="http://cobi-dashboard-vllmstack-engine:8000/v1" \
--from-literal=DEFAULT_MODEL_ID="QuantTrio/Qwen3.5-9B-AWQ" \
--from-literal=DEFAULT_FAST_MODEL_ID="QuantTrio/Qwen3.5-9B-AWQ" \
--from-literal=DEFAULT_OCR_MODEL_ID="QuantTrio/Qwen3.5-9B-AWQ" \
# ── Vector store (Qdrant) ────────────────────────────────────────────
--from-literal=QDRANT_URL="http://cobi-dashboard-qdrant:6333" \
--from-literal=QDRANT_LOCAL_ONLY="true" \
--from-literal=QDRANT_API_KEY="" \
--from-literal=QDRANT_CLUSTER_KEY="" \
--from-literal=QDRANT_COLLECTION_PREFIX="prod_" \
# ── Object storage (MinIO or AWS S3) ─────────────────────────────────
--from-literal=S3_BUCKET="documents" \
--from-literal=S3_REGION="us-east-1" \
--from-literal=S3_ENDPOINT="http://cobi-dashboard-minio:9000" \
--from-literal=S3_ACCESS_KEY_ID="admin" \
--from-literal=S3_SECRET_ACCESS_KEY="" \
# ── Security ─────────────────────────────────────────────────────────
--from-literal=DATASOURCES_ENCRYPTION_KEY=""
```
After the first successful deployment, change `ENABLE_MIGRATIONS` to `"false"` and re-apply the Secret. Running migrations on every pod restart is not safe in production.
## Environment Variable Reference
### Database
| Variable | Required | Description |
| ------------------- | -------- | ------------------------------------------------------------------------------- |
| `DATABASE_URL` | Yes | PostgreSQL connection string for the `dashboard_auth` database (auth, sessions) |
| `CORE_DATABASE_URL` | Yes | PostgreSQL connection string for the `cobi_core` database (application data) |
| `ENABLE_MIGRATIONS` | Yes | Set `"true"` on first install to run schema migrations; `"false"` after |
Both connection strings follow the format:
```
postgres://:@:/?schema=public
```
For the in-cluster PostgreSQL subchart, the host is `-postgresql` (e.g. `cobi-dashboard-postgresql`).
### Authentication
| Variable | Required | Description |
| -------------------- | -------- | ----------------------------------------------------------------------------- |
| `BETTER_AUTH_SECRET` | Yes | Secret key for signing session tokens. Generate with `openssl rand -hex 32` |
| `BETTER_AUTH_URL` | Yes | Public HTTPS URL of the application — used for auth callbacks and CSRF checks |
| `FRONTEND_URL` | Yes | Public HTTPS URL of the frontend — used for redirect allowlists |
`BETTER_AUTH_URL` must match the public API URL. `FRONTEND_URL` must match the public frontend URL.
### LLM Inference
| Variable | Required | Description |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `AI_PROVIDER` | Yes | Always `"openai"` — the backend uses the OpenAI-compatible API |
| `OPENAI_BASE_URL` | Yes | Base URL of the vLLM inference service (e.g. `http://cobi-dashboard-vllmstack-engine:8000/v1`) |
| `OPENAI_API_KEY` | Yes | API key. Set to any non-empty string for self-hosted vLLM (it ignores the key by default) |
| `DEFAULT_MODEL_ID` | Yes | Model ID passed in inference requests (must match the model loaded in vLLM) |
| `DEFAULT_FAST_MODEL_ID` | No | Model for fast/cheap requests. Defaults to `DEFAULT_MODEL_ID` if not set |
| `DEFAULT_OCR_MODEL_ID` | No | Model for OCR document extraction. Defaults to `DEFAULT_MODEL_ID` if not set |
When using OpenRouter or another external provider instead of vLLM:
```bash theme={null}
--from-literal=OPENAI_BASE_URL="https://openrouter.ai/api/v1" \
--from-literal=OPENAI_API_KEY="sk-or-..." \
--from-literal=DEFAULT_MODEL_ID="qwen/qwen3.5-9b"
```
### Vector Store (Qdrant)
| Variable | Required | Description |
| -------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `QDRANT_URL` | Yes | HTTP URL of the Qdrant service (e.g. `http://cobi-dashboard-qdrant:6333`) |
| `QDRANT_LOCAL_ONLY` | Yes | Set `"true"` for in-cluster or on-prem Qdrant; `"false"` for Qdrant Cloud |
| `QDRANT_COLLECTION_PREFIX` | No | String prefix for all collection names. Useful to namespace multiple environments sharing one Qdrant instance (e.g. `"prod_"`, `"staging_"`) |
| `QDRANT_API_KEY` | Conditional | Required when using Qdrant Cloud. Leave empty for local |
| `QDRANT_CLUSTER_KEY` | Conditional | Required when using Qdrant Cloud. Leave empty for local |
### Object Storage
| Variable | Required | Description |
| ---------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- |
| `S3_BUCKET` | Yes | Bucket name for document storage |
| `S3_REGION` | Yes | Signing region. Set to `"us-east-1"` for MinIO |
| `S3_ENDPOINT` | Yes for S3-compatible storage | Custom S3 endpoint. Set to `"http://cobi-dashboard-minio:9000"` when using in-cluster MinIO |
| `S3_ACCESS_KEY_ID` | Yes for static credentials | S3 access key ID, or MinIO `rootUser` |
| `S3_SECRET_ACCESS_KEY` | Yes for static credentials | S3 secret access key, or MinIO `rootPassword` |
When `backend.s3.existingSecret` is set, the Helm chart maps these generic `S3_*` Secret keys to the backend container's runtime environment variables.
### Security
| Variable | Required | Description |
| ---------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `DATASOURCES_ENCRYPTION_KEY` | Yes | 64-character hex key used to encrypt stored data-source credentials at rest. Generate with `openssl rand -hex 32` |
### Observability
| Variable | Required | Description |
| ------------------------------ | -------- | ------------------------------------------------------------------------------------------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | No | OTLP receiver URL (e.g. `http://cobi-dashboard-otel-lgtm:4318`). Set when otel-lgtm is enabled |
| `OTEL_SERVICE_NAME` | No | Service name shown in traces (e.g. `"cobi-backend"`) |
| `OTEL_SERVICE_VERSION` | No | Service version shown in traces |
| `OTEL_METRIC_EXPORT_INTERVAL` | No | Metric export interval in milliseconds (default `5000`) |
| `POSTHOG_API_KEY` | No | PostHog project API key for product analytics |
| `POSTHOG_HOST` | No | PostHog ingest host (e.g. `https://eu.i.posthog.com`) |
| `POSTHOG_PERSONAL_API_KEY` | No | PostHog personal API key for server-side calls |
| `LANGFUSE_PUBLIC_KEY` | No | Langfuse project public key for LLM tracing |
| `LANGFUSE_SECRET_KEY` | No | Langfuse project secret key |
| `LANGFUSE_HOST` | No | Langfuse host (e.g. `https://cloud.langfuse.com`) |
| `LANGFUSE_TRACING_ENVIRONMENT` | No | Environment label in Langfuse traces (e.g. `"production"`) |
| `LANGFUSE_PROMPT_RUNTIME_MODE` | No | `"local-only"` to load prompts from bundled files; `"langfuse"` to fetch from Langfuse at runtime |
| `LANGFUSE_PROMPT_LABEL` | No | Prompt version label to load from Langfuse (default `"latest"`) |
| `LANGFUSE_PROMPT_BUNDLE_PATH` | No | File path of the bundled prompt file when `LANGFUSE_PROMPT_RUNTIME_MODE=local-only` |
## Helm Values
Reference the Secret in your values file:
```yaml theme={null}
backend:
enabled: true
image:
repository: docker.io/hellocobi/dashboard-backend
tag: "0.2.6"
pullPolicy: IfNotPresent
replicaCount: 1
service:
type: ClusterIP
port: 3000
envFrom:
- secretRef:
name: cobi-backend-secrets
s3:
existingSecret: cobi-backend-secrets
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
```
## Generating Secure Keys
```bash theme={null}
# BETTER_AUTH_SECRET — 32-byte hex string
openssl rand -hex 32
# DATASOURCES_ENCRYPTION_KEY — 32-byte hex string (64 chars)
openssl rand -hex 32
```
# Connect Service
Source: https://docs.hellocobi.com/deployment/dashboard-connect
Environment variables and secrets required to run the connect service for data source ingestion
## Overview
The **connect service** (`hellocobi/dashboard-connect`) is a Python FastAPI service responsible for ingesting data from external data sources. It:
* Validates connector credentials against third-party APIs (GitHub, Slack, Linear, HubSpot)
* Executes ingestion runs that sync data from those sources into MinIO object storage
* Triggers schema mapping syncs requested by the backend
It runs on port `8000` as a `ClusterIP` service. By default it is reachable only within the cluster, but the chart can expose it through `gateway.connect` or `ingress.connect` when a deployment needs a public Connect endpoint.
Set the backend's `CONNECT_SERVICE_URL` to the in-cluster service URL, usually `http://cobi-dashboard-connect:8000`.
## How Authentication Works
Every request from the backend carries the header:
```
x-connect-service-token:
```
The service reads `CONNECT_SERVICE_TOKEN` at startup and validates each incoming request. If the variable is not set, the service **refuses to start**:
```
RuntimeError: CONNECT_SERVICE_TOKEN must be set
```
The same token value must be present on **both** sides — the connect Secret and the backend Secret.
## Required Secrets
### Connect Secret
```bash theme={null}
kubectl create secret generic cobi-connect-secrets \
--namespace cobi \
--from-literal=HOST="0.0.0.0" \
--from-literal=PORT="8000" \
--from-literal=CONNECT_SERVICE_TOKEN=""
```
`HOST` must be `0.0.0.0`. The default `127.0.0.1` binds to loopback only — the Kubernetes Service cannot reach the pod.
Generate the shared token (use the same value in the backend Secret):
```bash theme={null}
openssl rand -hex 32
```
### Backend Secret additions
The backend also needs `CONNECT_SERVICE_TOKEN` and `CONNECT_BUCKET_PREFIX`. See [Backend Service](/deployment/backend) for the full backend secret.
## Environment Variable Reference
### Server
| Variable | Required | Default | Description |
| ----------------------- | -------- | ----------- | ----------------------------------------------------------------- |
| `HOST` | Yes | `127.0.0.1` | Bind address. **Must be `0.0.0.0`** in a container |
| `PORT` | No | `8000` | Port uvicorn listens on. Must match `connect.port` in Helm values |
| `CONNECT_SERVICE_TOKEN` | Yes | — | Shared secret for authenticating requests from the backend |
### MinIO Connection
The connect service writes ingestion artifacts to MinIO. The bucket, prefix, and per-request credentials are passed in the API request body by the backend, but the S3 **endpoint** for MinIO must be set as an environment variable — it is not included in the request body.
| Variable | Required | Description |
| ------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `S3_ENDPOINT` | Yes (on-prem) | MinIO endpoint URL — `http://-minio:9000` for in-cluster. DuckDB httpfs reads this to write storage artifacts. Also enables path-style URL mode automatically |
## Helm Values
```yaml theme={null}
connect:
enabled: true
image:
repository: docker.io/hellocobi/dashboard-connect
tag: "823edd6d96f97aae01425d8c3ddfc9ef8839c65b"
pullPolicy: IfNotPresent
replicaCount: 1
service:
type: ClusterIP
port: 8000
envFrom:
- secretRef:
name: cobi-connect-secrets
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
```
## Verify the Service
```bash theme={null}
# Port-forward
kubectl port-forward -n cobi svc/cobi-dashboard-connect 8000:8000
# Health check — no auth required
curl http://localhost:8000/health
# {"status":"ok"}
# Protected endpoint without token returns 401
curl -X POST http://localhost:8000/internal/mirror-connections/ping
# {"detail":"Invalid connect service token"}
```
## Backend Connection
Add `CONNECT_SERVICE_URL` to the backend Secret or `backend.env`:
```yaml theme={null}
- name: CONNECT_SERVICE_URL
value: "http://cobi-dashboard-connect:8000"
```
Verify it is set:
```bash theme={null}
kubectl exec -n cobi deploy/cobi-dashboard-backend -- env | grep CONNECT_SERVICE_URL
```
# Frontend Service
Source: https://docs.hellocobi.com/deployment/frontend
Environment variables and secrets required to run the Cobi frontend service on-premises
## Overview
The frontend is a Vite/React application served as a Node.js process on port `8080`. It is exposed through Gateway API, Ingress, or an OpenShift Route — separate from the backend API hostname.
The frontend calls the backend API using `VITE_API_BASE_URL`, which must be set to the backend's public URL.
## Required Secrets
```bash theme={null}
kubectl create secret generic cobi-frontend-secrets \
--namespace cobi \
--from-literal=NODE_ENV="production" \
--from-literal=VITE_API_BASE_URL="https://api.example.com" \
--from-literal=VITE_API_SANDBOX_URL="https://api.example.com"
```
`VITE_API_BASE_URL` must match the hostname of the backend Gateway route, Ingress, or Route.
## Environment Variable Reference
| Variable | Required | Description |
| ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `VITE_API_BASE_URL` | Yes | Public base URL of the **backend** API. The frontend constructs all API request URLs from this value |
| `VITE_API_SANDBOX_URL` | No | Base URL for sandbox API calls. Set to the same value as `VITE_API_BASE_URL` if only one environment is deployed |
| `HOST` | No | Server bind address. Defaults to `0.0.0.0` in the container image — only set if you need to override |
## Helm Values
```yaml theme={null}
frontend:
enabled: true
image:
repository: docker.io/hellocobi/dashboard-frontend
tag: "0.1.9"
pullPolicy: IfNotPresent
replicaCount: 1
service:
type: ClusterIP
port: 8080
envFrom:
- secretRef:
name: cobi-frontend-secrets
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
```
## Notes
`VITE_*` variables are injected at **container start time** via a server-side env-injection mechanism in the Cobi image, so Secret changes take effect after a pod restart — no image rebuild is required.
# Helm Chart Setup
Source: https://docs.hellocobi.com/deployment/helm-setup
Step-by-step guide to installing Cobi on Kubernetes or OpenShift using the cobi-dashboard Helm chart
## 1. Choose the chart source
```bash theme={null}
CHART_REF=oci://registry-1.docker.io/hellocobi/cobi-dashboard
CHART_VERSION=0.1.0
```
The Helm chart is published as a public OCI artifact. If you are installing from a local checkout of the chart instead, skip these variables and use the local path in the `helm install` commands below.
## 2. Create the namespace
```bash theme={null}
kubectl create namespace cobi
```
## 3. Create required Secrets
### Docker Hub pull secret
Required if `global.imagePullSecrets` is set (needed for `hellocobi/*` private images).
```bash theme={null}
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username= \
--docker-password= \
--docker-email= \
--namespace cobi
```
### Backend environment secret
The backend reads its configuration from a Kubernetes Secret. Create it with your environment-specific values:
```bash theme={null}
kubectl create secret generic cobi-backend-secrets \
--namespace cobi \
--from-literal=DATABASE_URL="postgres://postgres:@cobi-dashboard-postgresql:5432/dashboard_auth?schema=public" \
--from-literal=CORE_DATABASE_URL="postgres://postgres:@cobi-dashboard-postgresql:5432/cobi_core?schema=public" \
--from-literal=BETTER_AUTH_SECRET="" \
--from-literal=BETTER_AUTH_URL="https://api.example.com" \
--from-literal=FRONTEND_URL="https://app.example.com" \
--from-literal=CONNECT_SERVICE_URL="http://cobi-dashboard-connect:8000" \
--from-literal=QDRANT_URL="http://cobi-dashboard-qdrant:6333" \
--from-literal=S3_BUCKET="documents" \
--from-literal=S3_REGION="us-east-1" \
--from-literal=S3_ENDPOINT="http://cobi-dashboard-minio:9000" \
--from-literal=S3_ACCESS_KEY_ID="admin" \
--from-literal=S3_SECRET_ACCESS_KEY="" \
--from-literal=CONNECT_SERVICE_TOKEN="" \
--from-literal=CONNECT_BUCKET_PREFIX="ingestion"
```
See [Backend Service](/deployment/backend) for the full variable reference.
### Frontend environment secret
```bash theme={null}
kubectl create secret generic cobi-frontend-secrets \
--namespace cobi \
--from-literal=NODE_ENV="production" \
--from-literal=VITE_API_BASE_URL="https://api.example.com" \
--from-literal=VITE_API_SANDBOX_URL="https://api.example.com"
```
`VITE_API_BASE_URL` must match the backend Gateway route, Ingress, or Route hostname.
### Connect service secret
```bash theme={null}
kubectl create secret generic cobi-connect-secrets \
--namespace cobi \
--from-literal=HOST="0.0.0.0" \
--from-literal=PORT="8000" \
--from-literal=CONNECT_SERVICE_TOKEN="" \
--from-literal=S3_ENDPOINT="http://cobi-dashboard-minio:9000"
```
Use the same `CONNECT_SERVICE_TOKEN` value in both the backend and connect secrets.
## 4. Build chart dependencies
If installing from a local chart directory:
```bash theme={null}
cd charts/cobi-dashboard
helm dependency build
```
This downloads the dependency charts (PostgreSQL, Qdrant, vllm-stack, MinIO) into `charts/`.
## 5. Install
Choose a values file that matches your environment, then install:
```bash theme={null}
helm install cobi-dashboard "$CHART_REF" \
--namespace cobi \
--version "$CHART_VERSION" \
--values my-values.yaml \
--wait
```
Or from a local chart:
```bash theme={null}
helm install cobi-dashboard ./charts/cobi-dashboard \
--namespace cobi \
--values my-values.yaml \
--wait
```
## 6. Verify the rollout
```bash theme={null}
kubectl get pods -n cobi
kubectl get gateway,httproute -n cobi # Gateway API
kubectl get ingress -n cobi # Kubernetes
kubectl get routes -n cobi # OpenShift
```
All pods should reach `Running` / `1/1 Ready`. The vLLM pod takes longer (\~5 min) while it downloads model weights.
## Upgrade
```bash theme={null}
helm upgrade cobi-dashboard "$CHART_REF" \
--namespace cobi \
--version "$CHART_VERSION" \
--values my-values.yaml \
--wait
```
## Uninstall
```bash theme={null}
helm uninstall cobi-dashboard --namespace cobi
```
PersistentVolumeClaims are **not** deleted automatically. Remove them manually if you want to purge all data:
```bash theme={null}
kubectl delete pvc -n cobi --all
```
***
## Minimal values file
The following enables all application components with sensible defaults. Substitute your image tags and domain names:
```yaml theme={null}
global:
imagePullSecrets:
- dockerhub-secret
backend:
enabled: true
image:
repository: docker.io/hellocobi/dashboard-backend
tag: "0.2.6"
pullPolicy: IfNotPresent
envFrom:
- secretRef:
name: cobi-backend-secrets
s3:
existingSecret: cobi-backend-secrets
frontend:
enabled: true
image:
repository: docker.io/hellocobi/dashboard-frontend
tag: "0.1.9"
pullPolicy: IfNotPresent
envFrom:
- secretRef:
name: cobi-frontend-secrets
connect:
enabled: true
image:
repository: docker.io/hellocobi/dashboard-connect
tag: "823edd6d96f97aae01425d8c3ddfc9ef8839c65b"
pullPolicy: IfNotPresent
envFrom:
- secretRef:
name: cobi-connect-secrets
ingress:
enabled: true
className: nginx
tls:
- secretName: cobi-dashboard-tls
hosts:
- app.example.com
- api.example.com
- connect.example.com
app:
enabled: true
host: app.example.com
api:
enabled: true
host: api.example.com
connect:
enabled: true
host: connect.example.com
minio:
enabled: false
grafana:
enabled: false
gateway:
enabled: false
postgresql:
enabled: true
auth:
username: postgres
password: ""
database: dashboard_auth
primary:
initdb:
scripts:
create-core-db.sql: |
CREATE DATABASE cobi_core;
persistence:
enabled: true
size: 10Gi
qdrant:
enabled: true
persistence:
enabled: true
size: 10Gi
minio:
enabled: true
auth:
rootUser: admin
rootPassword: ""
defaultBuckets: "documents"
persistence:
enabled: true
size: 100Gi
otelLgtm:
enabled: true
# vLLM disabled by default — see vLLM Setup guide
vllmstack:
enabled: false
```
## OpenShift values override
For OpenShift, disable Kubernetes Ingress and Gateway, then enable the chart's frontend and backend API Routes:
```yaml theme={null}
ingress:
enabled: false
gateway:
enabled: false
# Enable OpenShift Routes
openshift:
route:
enabled: true
host: dashboard.apps.cluster.example.com
api:
enabled: true
host: api.apps.cluster.example.com
```
OpenShift Routes use edge TLS termination by default — TLS is terminated at the router, plain HTTP to the pods. Update the frontend and backend secrets to use the frontend Route and API Route:
```bash theme={null}
# Frontend secret
--from-literal=VITE_API_BASE_URL="https://api.apps.cluster.example.com"
--from-literal=VITE_API_SANDBOX_URL="https://api.apps.cluster.example.com"
# Backend secret
--from-literal=BETTER_AUTH_URL="https://api.apps.cluster.example.com"
--from-literal=FRONTEND_URL="https://dashboard.apps.cluster.example.com"
```
See [Ingress & Routes](/deployment/ingress-routes) for the full platform-specific configuration.
# Gateway, Ingress & Routes
Source: https://docs.hellocobi.com/deployment/ingress-routes
Configure Gateway API, Kubernetes Ingress, or OpenShift Routes for Cobi
## Platform Choice
The chart supports Gateway API, Kubernetes Ingress, and OpenShift Routes for the user-facing app/API path. Enable **one** depending on your platform.
| Platform | Mechanism | Values key |
| --------------------- | ------------------------------ | ------------------------- |
| Kubernetes, preferred | Gateway API with Envoy Gateway | `gateway.enabled` |
| Kubernetes, nginx | `Ingress` | `ingress.enabled` |
| OpenShift | `Route` | `openshift.route.enabled` |
***
## Kubernetes — Gateway API with Envoy Gateway
Gateway API is the preferred option for the kOps v2 rehearsal profile and for self-hosted clusters that already run Envoy Gateway.
### Prerequisites
Install Gateway API CRDs, cert-manager with Gateway support, and Envoy Gateway:
```bash theme={null}
kubectl apply --server-side -f \
https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml
helm upgrade --install cert-manager oci://quay.io/jetstack/charts/cert-manager \
--version v1.20.2 \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true \
--set config.enableGatewayAPI=true
helm upgrade --install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.7.3 \
--namespace envoy-gateway-system \
--create-namespace
```
Create or reuse a `GatewayClass` named `eg`. On AWS kOps, the kOps v2 reference configures Envoy's data-plane Service as an internet-facing NLB.
### Values
```yaml theme={null}
ingress:
enabled: false
gateway:
enabled: true
gatewayClass:
create: false
name: eg
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
enabled: true
app:
secretName: cobi-dashboard-app-tls
api:
secretName: cobi-dashboard-api-tls
connect:
secretName: cobi-dashboard-connect-tls
app:
enabled: true
hostname: app.example.com
api:
enabled: true
hostname: api.example.com
timeouts:
request: 0s
backendRequest: 0s
connect:
enabled: true
hostname: connect.example.com
timeouts:
request: 0s
backendRequest: 0s
minio:
enabled: false
grafana:
enabled: false
```
Set the corresponding application URLs:
```bash theme={null}
# Frontend
--from-literal=VITE_API_BASE_URL="https://api.example.com"
--from-literal=VITE_API_SANDBOX_URL="https://api.example.com"
# Backend
--from-literal=BETTER_AUTH_URL="https://api.example.com"
--from-literal=FRONTEND_URL="https://app.example.com"
--from-literal=CONNECT_SERVICE_URL="http://cobi-dashboard-connect:8000"
```
### Verify
```bash theme={null}
kubectl get gateway -n cobi
kubectl get httproute -n cobi
kubectl get svc -n envoy-gateway-system
```
***
## Kubernetes — Ingress
The frontend and backend each get their own Ingress and their own hostname. The frontend makes API calls to the backend hostname directly — there is no `/api` path proxying.
### Prerequisites
Install `ingress-nginx` before deploying the chart:
```bash theme={null}
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.replicaCount=2
```
Wait for the LoadBalancer IP or hostname:
```bash theme={null}
kubectl get svc -n ingress-nginx ingress-nginx-controller \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```
Create DNS A records for both hostnames pointing at this address.
### Separate-host values
Ingress is configured under the top-level `ingress` block:
```yaml theme={null}
gateway:
enabled: false
ingress:
enabled: true
className: nginx
annotations: {}
tls:
- secretName: cobi-dashboard-tls
hosts:
- app.example.com
- api.example.com
- connect.example.com
app:
enabled: true
host: app.example.com
api:
enabled: true
host: api.example.com
connect:
enabled: true
host: connect.example.com
minio:
enabled: false
grafana:
enabled: false
```
Set the corresponding URLs in the frontend and backend Secrets:
```bash theme={null}
# Frontend — points at the backend hostname
--from-literal=VITE_API_BASE_URL="https://api.example.com"
# Backend — points at itself, the frontend, and the in-cluster Connect service
--from-literal=BETTER_AUTH_URL="https://api.example.com"
--from-literal=FRONTEND_URL="https://app.example.com"
--from-literal=CONNECT_SERVICE_URL="http://cobi-dashboard-connect:8000"
```
### Shared-host values
Shared-host mode routes the frontend at `/` and the backend at `ingress.apiPrefix`:
```yaml theme={null}
ingress:
enabled: true
className: nginx
host: cobi.example.com
apiPrefix: /api
tls:
- secretName: cobi-dashboard-tls
hosts:
- cobi.example.com
```
### TLS — cert-manager (recommended)
```bash theme={null}
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true
```
```yaml theme={null}
# cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
ingressClassName: nginx
```
```bash theme={null}
kubectl apply -f cluster-issuer.yaml
```
Add the annotation under the top-level `ingress` block:
```yaml theme={null}
ingress:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
- secretName: cobi-dashboard-tls
hosts:
- app.example.com
- api.example.com
```
### TLS — Pre-existing certificates
```bash theme={null}
kubectl create secret tls cobi-dashboard-tls --cert=tls.crt --key=tls.key --namespace cobi
```
***
## OpenShift — Frontend and API Routes
OpenShift uses Routes instead of Ingress. The chart creates Routes for the frontend and backend API. Connect stays internal through `CONNECT_SERVICE_URL` unless your deployment needs a public Connect endpoint.
### Values
```yaml theme={null}
ingress:
enabled: false
gateway:
enabled: false
# Enable OpenShift Routes
openshift:
route:
enabled: true
host: dashboard.apps.cluster.example.com
api:
enabled: true
host: api.apps.cluster.example.com
```
The Routes use **edge TLS termination** — TLS is terminated at the OpenShift router, plain HTTP to the pods.
### Generated Route resources
```yaml theme={null}
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: cobi-dashboard-frontend
namespace: cobi
spec:
host: dashboard.apps.cluster.example.com
to:
kind: Service
name: cobi-dashboard-frontend
port:
targetPort: http
tls:
termination: edge
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: cobi-dashboard-backend
namespace: cobi
spec:
host: api.apps.cluster.example.com
to:
kind: Service
name: cobi-dashboard-backend
port:
targetPort: http
tls:
termination: edge
```
Set the corresponding URLs in the frontend and backend Secrets:
```bash theme={null}
# Frontend — points at the backend route hostname
--from-literal=VITE_API_BASE_URL="https://api.apps.cluster.example.com"
# Backend — use the public API URL created for your backend
--from-literal=BETTER_AUTH_URL="https://api.apps.cluster.example.com"
--from-literal=FRONTEND_URL="https://dashboard.apps.cluster.example.com"
```
***
## Observability (Grafana)
Expose the Grafana UI from the `otel-lgtm` component when `otelLgtm.enabled: true`:
```yaml theme={null}
ingress:
enabled: true
grafana:
enabled: true
host: grafana.example.com
```
***
## Internal Service Addresses
When configuring Secrets, use these ClusterIP hostnames (release name `cobi-dashboard`, namespace `cobi`):
| Service | Internal address |
| --------------- | -------------------------------------- |
| Backend | `cobi-dashboard-backend:3000` |
| Frontend | `cobi-dashboard-frontend:8080` |
| Connect | `cobi-dashboard-connect:8000` |
| PostgreSQL | `cobi-dashboard-postgresql:5432` |
| Qdrant | `cobi-dashboard-qdrant:6333` |
| MinIO (API) | `cobi-dashboard-minio:9000` |
| MinIO (Console) | `cobi-dashboard-minio:9001` |
| vLLM | `cobi-dashboard-vllmstack-engine:8000` |
# MinIO Setup
Source: https://docs.hellocobi.com/deployment/minio-setup
Configure MinIO object storage for document uploads and data ingestion artifacts in on-premises environments
## Overview
MinIO provides an S3-compatible API that Cobi uses to store uploaded documents and data ingestion artifacts. Enable it for on-premises or air-gapped clusters.
**Default ports:**
* `9000` — S3 API (used by the backend and connect service)
* `9001` — Web console
## Configuration
```yaml theme={null}
minio:
enabled: true
mode: standalone # "standalone" for single-node; "distributed" for HA
auth:
rootUser: admin
rootPassword: "" # minimum 8 characters
# In production, prefer an existing Secret — see below
defaultBuckets: "documents" # created on first startup
persistence:
enabled: true
storageClass: "" # use cluster default; set "local-path" or "nfs-csi" for on-prem
accessModes:
- ReadWriteOnce
size: 100Gi
service:
type: ClusterIP
ports:
api: 9000
console: 9001
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
```
## On-Premises StorageClass
```yaml theme={null}
minio:
persistence:
storageClass: "local-path"
size: 500Gi
```
`standalone` mode with `ReadWriteOnce` works on most on-prem clusters. For production HA, use `mode: distributed` with a StorageClass that supports concurrent access (NFS, Longhorn, etc.).
## Using an Existing Secret
```bash theme={null}
kubectl create secret generic minio-credentials \
--namespace cobi \
--from-literal=root-user=admin \
--from-literal=root-password=""
```
```yaml theme={null}
minio:
auth:
existingSecret: "minio-credentials"
```
## Exposing the Web Console (optional)
```yaml theme={null}
minio:
consoleIngress:
enabled: true
ingressClassName: nginx
hostname: minio-console.example.com
tls: false
```
Enable TLS before exposing MinIO outside the cluster. Plain HTTP transmits credentials in the clear.
## Verify MinIO
```bash theme={null}
# Port-forward the MinIO API
kubectl port-forward -n cobi svc/cobi-dashboard-minio 9000:9000
# Health check
curl http://localhost:9000/minio/health/live
# List buckets
mc alias set local http://localhost:9000 admin
mc ls local/
```
The `documents` bucket should exist after first startup.
## Connecting the Backend
Set these variables in the backend Secret using the S3 environment variable names:
```bash theme={null}
--from-literal=S3_BUCKET="documents" \
--from-literal=S3_REGION="us-east-1" \
--from-literal=S3_ENDPOINT="http://cobi-dashboard-minio:9000" \
--from-literal=S3_ACCESS_KEY_ID="admin" \
--from-literal=S3_SECRET_ACCESS_KEY=""
```
| Variable | Description |
| ---------------------- | ----------------------------------------------------------------- |
| `S3_BUCKET` | Bucket name — must match `minio.defaultBuckets` |
| `S3_REGION` | Signing region. Use `us-east-1` for MinIO |
| `S3_ENDPOINT` | MinIO API endpoint. For in-cluster: `http://-minio:9000` |
| `S3_ACCESS_KEY_ID` | MinIO `rootUser` |
| `S3_SECRET_ACCESS_KEY` | MinIO `rootPassword` |
Reference the same Secret from `backend.s3.existingSecret` so Helm projects the generic keys into the backend container.
## Connecting the Connect Service
The connect service also writes ingestion artifacts to MinIO. Set the endpoint using the env vars the service reads from (these are the actual variable names the Python process reads):
```bash theme={null}
--from-literal=S3_ENDPOINT="http://cobi-dashboard-minio:9000" \
--from-literal=AWS_ACCESS_KEY_ID="admin" \
--from-literal=AWS_SECRET_ACCESS_KEY="" \
--from-literal=AWS_DEFAULT_REGION="us-east-1"
```
See [Connect Service](/deployment/dashboard-connect) for the full secret.
## Sizing Guide
| Document volume | Recommended PVC size |
| ------------------------ | --------------------------- |
| \< 10 000 documents | 50 Gi |
| 10 000–100 000 documents | 100–500 Gi |
| > 100 000 documents | 500 Gi+ or distributed mode |
# Model Storage
Source: https://docs.hellocobi.com/deployment/model-storage
Host model weights in your own Git repository or MinIO instead of pulling from Hugging Face Hub
## Overview
By default, vLLM downloads model weights from Hugging Face Hub at pod startup. In air-gapped or regulated environments you may prefer to host weights in your own infrastructure and have the cluster pull from there instead.
Two approaches are supported:
| Approach | Best for |
| ----------- | ------------------------------------------------------------------------------------ |
| **Git LFS** | Model versioning alongside code, existing Git infrastructure (GitHub, GitLab, Gitea) |
| **MinIO** | Simplest path — you already have MinIO running in the cluster |
Both approaches use the same pattern:
1. Upload model weights to your storage once, from a machine with internet access.
2. An init container on the vLLM pod copies weights to the PVC before vLLM starts.
3. vLLM loads from the local PVC path — no HuggingFace token required.
***
## Option A — Git LFS
### 1. Set up Git LFS in your repository
Git LFS stores large binary files (model weights) in a separate blob store while keeping pointer files in the repo. It is supported by GitHub, GitLab, Gitea, and Bitbucket.
```bash theme={null}
# Install Git LFS (one-time, on your workstation)
git lfs install
# In your model repository
git lfs track "*.safetensors" "*.bin" "*.pt" "*.gguf"
git add .gitattributes
git commit -m "track model weight files with LFS"
```
### 2. Download and push model weights
On a machine with internet access:
```bash theme={null}
# Download from Hugging Face
pip install huggingface_hub
huggingface-cli download QuantTrio/Qwen3.5-9B-AWQ \
--local-dir ./qwen35-9b-awq
# Push to your Git repository
cd your-model-repo
cp -r ./qwen35-9b-awq ./models/qwen35-9b-awq
git add models/qwen35-9b-awq
git commit -m "add Qwen3.5-9B-AWQ weights"
git push
```
LFS objects (the large weight files) are uploaded to your Git provider's LFS storage automatically.
### 3. Create a pull secret in the cluster
Create a read-only access token in your Git provider, then store it as a Kubernetes Secret:
```bash theme={null}
kubectl create secret generic model-repo-credentials \
--namespace cobi \
--from-literal=GIT_REPO_URL="https://github.com/your-org/your-model-repo.git" \
--from-literal=GIT_USERNAME="" \
--from-literal=GIT_TOKEN=""
```
For GitHub, create a fine-grained PAT with read-only access to the model repository. For GitLab, use a project deploy token with `read_repository` scope. For Gitea, create an application token.
### 4. Configure the vLLM init container
Replace the `busybox` init container with an `alpine/git` image that clones the repo and pulls LFS files. The model weights land on the PVC at `/data/model` before vLLM starts.
```yaml theme={null}
vllmstack:
enabled: true
servingEngineSpec:
enableEngine: true
runtimeClassName: "nvidia"
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
modelSpec:
- name: "qwen35-9b"
repository: "vllm/vllm-openai"
tag: "latest"
modelURL: "/data/model/models/qwen35-9b-awq" # local path — no HF download
replicaCount: 1
requestCPU: 4
requestMemory: "28Gi"
requestGPU: 1
limitCPU: "4"
limitMemory: "28Gi"
shmSize: "2Gi"
pvcStorage: "80Gi"
pvcAccessMode:
- ReadWriteOnce
env:
- name: TMPDIR
value: /data/tmp
- name: GIT_REPO_URL
valueFrom:
secretKeyRef:
name: model-repo-credentials
key: GIT_REPO_URL
- name: GIT_USERNAME
valueFrom:
secretKeyRef:
name: model-repo-credentials
key: GIT_USERNAME
- name: GIT_TOKEN
valueFrom:
secretKeyRef:
name: model-repo-credentials
key: GIT_TOKEN
initContainer:
name: pull-model-weights
image: alpine/git:2.43.0
command: ["sh"]
args:
- -c
- |
set -e
mkdir -p /data/tmp /data/model /data/.cache
chmod -R 777 /data
# Skip clone if weights already exist (pod restart)
if [ -d /data/model/models/qwen35-9b-awq ]; then
echo "Model weights already present, skipping clone."
exit 0
fi
# Install git-lfs
apk add --no-cache git-lfs
git lfs install
# Clone with embedded credentials (shallow, LFS enabled)
REPO_URL="https://${GIT_USERNAME}:${GIT_TOKEN}@$(echo $GIT_REPO_URL | sed 's|https://||')"
git clone --depth=1 "$REPO_URL" /data/model
echo "Model weights pulled successfully."
mountPvcStorage: true
vllmConfig:
dtype: "float16"
tensorParallelSize: 1
maxModelLen: 8192
gpuMemoryUtilization: 0.90
enableChunkedPrefill: true
enablePrefixCaching: false
maxNumSeqs: 16
extraArgs:
- "--trust-remote-code"
- "--quantization"
- "awq"
- "--enforce-eager"
- "--attention-backend"
- "flashinfer"
- "--max-num-batched-tokens"
- "1024"
- "--disable-log-stats"
routerSpec:
enableRouter: false
```
The init container skips cloning if the weights directory already exists. This avoids re-downloading on pod restarts. If you push new weights to the repo, delete the PVC and let the pod reprovision.
***
## Option B — MinIO
Since MinIO is already running in the cluster, you can use it as a model registry. Upload weights once; the init container fetches them on first pod start.
### 1. Upload model weights to MinIO
On a machine with internet access, download the weights and push to MinIO:
```bash theme={null}
# Download from Hugging Face
huggingface-cli download QuantTrio/Qwen3.5-9B-AWQ \
--local-dir ./qwen35-9b-awq
# Configure MinIO client (port-forward first if uploading remotely)
kubectl port-forward -n cobi svc/cobi-dashboard-minio 9000:9000
mc alias set cobi http://localhost:9000 admin
# Create a models bucket and upload
mc mb cobi/models
mc cp --recursive ./qwen35-9b-awq cobi/models/qwen35-9b-awq/
```
Verify:
```bash theme={null}
mc ls cobi/models/qwen35-9b-awq/
```
### 2. Configure the vLLM init container
Replace the `busybox` init container with `minio/mc` to pull weights from the `models` bucket to the PVC:
```yaml theme={null}
vllmstack:
enabled: true
servingEngineSpec:
enableEngine: true
runtimeClassName: "nvidia"
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
modelSpec:
- name: "qwen35-9b"
repository: "vllm/vllm-openai"
tag: "latest"
modelURL: "/data/model" # local path on PVC
replicaCount: 1
requestCPU: 4
requestMemory: "28Gi"
requestGPU: 1
limitCPU: "4"
limitMemory: "28Gi"
shmSize: "2Gi"
pvcStorage: "80Gi"
pvcAccessMode:
- ReadWriteOnce
env:
- name: TMPDIR
value: /data/tmp
- name: MINIO_ENDPOINT
value: "http://cobi-dashboard-minio:9000"
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: root-user
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: root-password
initContainer:
name: pull-model-weights
image: minio/mc:latest
command: ["sh"]
args:
- -c
- |
set -e
mkdir -p /data/tmp /data/.cache
chmod -R 777 /data
# Skip download if weights already present
if [ -f /data/model/config.json ]; then
echo "Model weights already present, skipping download."
exit 0
fi
mc alias set storage "$MINIO_ENDPOINT" "$MINIO_ACCESS_KEY" "$MINIO_SECRET_KEY"
mc cp --recursive storage/models/qwen35-9b-awq/ /data/model/
echo "Model weights downloaded successfully."
mountPvcStorage: true
vllmConfig:
dtype: "float16"
tensorParallelSize: 1
maxModelLen: 8192
gpuMemoryUtilization: 0.90
enableChunkedPrefill: true
enablePrefixCaching: false
maxNumSeqs: 16
extraArgs:
- "--trust-remote-code"
- "--quantization"
- "awq"
- "--enforce-eager"
- "--attention-backend"
- "flashinfer"
- "--max-num-batched-tokens"
- "1024"
- "--disable-log-stats"
routerSpec:
enableRouter: false
```
`minio-credentials` is the same Secret used by the MinIO subchart itself. See [MinIO Setup](/deployment/minio-setup#using-an-existing-secret) for how to create it. The secret keys are `root-user` and `root-password`.
***
## Monitoring the Init Container
```bash theme={null}
# Watch init container logs while the pod starts
kubectl logs -n cobi -l app.kubernetes.io/name=vllmstack \
-c pull-model-weights --follow
# Check pod events if the init container fails
kubectl describe pod -n cobi -l app.kubernetes.io/name=vllmstack
```
A successful pull ends with:
```
Model weights downloaded successfully.
```
After the init container exits, vLLM starts and loads from `/data/model`.
***
## Updating the Model
To deploy a new version of the weights:
```bash theme={null}
# Delete the PVC so the init container re-downloads on next pod start
kubectl delete pvc -n cobi -l app.kubernetes.io/name=vllmstack
# The next helm upgrade or pod restart triggers a fresh pull
helm upgrade cobi-dashboard oci://registry-1.docker.io/hellocobi/cobi-dashboard \
--namespace cobi \
--version 0.1.0 \
--values my-values.yaml \
--wait
```
# On-Premises Deployment Overview
Source: https://docs.hellocobi.com/deployment/overview
Architecture overview for deploying Cobi on Kubernetes or OpenShift using the cobi-dashboard Helm chart
## Architecture
The `cobi-dashboard` Helm chart deploys the full Cobi platform as a set of Kubernetes workloads. All components run in a single namespace and communicate over the cluster network.
```mermaid theme={null}
flowchart TB
Browser[User browser]
subgraph Cluster[Kubernetes / OpenShift namespace]
Edge{Gateway / Ingress / Route}
Frontend[frontend
Vite / React
:8080]
Backend[backend
Node.js API
:3000]
Connect[connect
ingestion service
:8000]
PostgreSQL[(PostgreSQL
:5432)]
Qdrant[(Qdrant
:6333 / :6334)]
MinIO[(MinIO
S3 :9000)]
VLLM[vLLM stack
OpenAI-compatible
:8000]
Observability[otel-lgtm
OTLP :4318
Grafana :3000]
end
Browser -->|HTTPS| Edge
Edge -->|app| Frontend
Edge -->|API| Backend
Backend -->|CONNECT_SERVICE_URL| Connect
Connect -->|S3_ENDPOINT| MinIO
Backend -->|DATABASE_URL| PostgreSQL
Backend -->|QDRANT_URL| Qdrant
Backend -->|S3_ENDPOINT| MinIO
Backend -->|OPENAI_BASE_URL| VLLM
Backend -.->|OTEL_EXPORTER_OTLP_ENDPOINT| Observability
```
## Components
| Component | Image | Port | Purpose |
| -------------- | ------------------------------ | ------------------ | ---------------------------------------------------------------- |
| **frontend** | `hellocobi/dashboard-frontend` | 8080 | Vite/React web application |
| **backend** | `hellocobi/dashboard-backend` | 3000 | REST API, auth, business logic |
| **connect** | `hellocobi/dashboard-connect` | 8000 | Data source ingestion service |
| **postgresql** | Bitnami PostgreSQL | 5432 | Auth database (`dashboard_auth`) and core database (`cobi_core`) |
| **qdrant** | `qdrant/qdrant` | 6333 / 6334 | Vector store for semantic search |
| **vllm-stack** | `vllm/vllm-openai` | 8000 | LLM inference, OpenAI-compatible API |
| **minio** | Bitnami MinIO | 9000 / 9001 | S3-compatible object storage for documents |
| **otel-lgtm** | `grafana/otel-lgtm` | 3000 / 4317 / 9090 | Grafana + Prometheus + Loki + Tempo |
## Connection Map
| From | To | Key environment variable | Port |
| ---------------------------------- | ---------------------------------- | --------------------------------------------------------- | ---- |
| Browser | Gateway / Ingress / frontend Route | — | 443 |
| Gateway / Ingress / frontend Route | frontend | — | 8080 |
| Browser | Gateway / Ingress / API Route | `VITE_API_BASE_URL` | 443 |
| Gateway / Ingress / API Route | backend | — | 3000 |
| Gateway / Ingress | connect | — | 8000 |
| backend | connect | `CONNECT_SERVICE_URL` | 8000 |
| connect | minio | `S3_ENDPOINT` | 9000 |
| backend | postgresql | `DATABASE_URL` | 5432 |
| backend | qdrant | `QDRANT_URL` | 6333 |
| backend | minio | `S3_ENDPOINT`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` | 9000 |
| backend | vllm-stack | `OPENAI_BASE_URL` | 8000 |
| backend | otel-lgtm | `OTEL_EXPORTER_OTLP_ENDPOINT` | 4318 |
## Dependency Chart Sources
| Chart | Repository | Version |
| ------------ | ------------------------------------------------- | -------- |
| `postgresql` | `https://charts.bitnami.com/bitnami` | `16.x.x` |
| `qdrant` | `https://qdrant.github.io/qdrant-helm` | `1.16.3` |
| `vllm-stack` | `https://vllm-project.github.io/production-stack` | `0.1.9` |
| `minio` | `https://charts.bitnami.com/bitnami` | `17.x.x` |
## Exposure Models
The chart supports Gateway API, Kubernetes Ingress, and OpenShift Routes for the user-facing app/API path. Enable **one** model at a time:
* **Gateway API** — uses `Gateway` and `HTTPRoute` resources, tested with Envoy Gateway. This is the preferred kOps v2/self-hosted rehearsal path and is configured under `gateway`.
* **Kubernetes Ingress** — uses top-level `ingress` values, tested with `nginx`. It supports shared-host routing and separate hostnames for app, API, Connect, MinIO, and Grafana.
* **OpenShift Routes** — uses frontend and backend API `Route` resources with edge TLS termination, configured under `openshift.route`. Connect stays internal through `CONNECT_SERVICE_URL` unless your deployment needs to expose it separately.
# PostgreSQL Setup
Source: https://docs.hellocobi.com/deployment/postgresql-setup
Deploy PostgreSQL in-cluster via the Helm subchart, or connect to an existing standalone database instance
## Overview
The backend uses two PostgreSQL databases: `dashboard_auth` for authentication/session data and `cobi_core` for core application data.
Set both connection strings in the backend Secret:
```
DATABASE_URL=postgres://:@:5432/dashboard_auth?schema=public
CORE_DATABASE_URL=postgres://:@:5432/cobi_core?schema=public
```
Choose one of the two setup options below.
***
## Option A — In-cluster PostgreSQL (Bitnami subchart)
The chart bundles the [Bitnami PostgreSQL](https://github.com/bitnami/charts/tree/main/bitnami/postgresql) subchart. Enable it and the chart creates a StatefulSet with a PersistentVolumeClaim.
### Helm values
```yaml theme={null}
postgresql:
enabled: true
image:
tag: "16"
auth:
username: postgres
password: ""
database: dashboard_auth
primary:
initdb:
scripts:
create-core-db.sql: |
CREATE DATABASE cobi_core;
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 2Gi
persistence:
enabled: true
storageClass: "" # use cluster default; set to "local-path" for on-prem
size: 10Gi
```
### Internal service address
Once deployed, PostgreSQL is reachable within the namespace at:
```
-postgresql:5432
```
### Backend connection string
```bash theme={null}
--from-literal=DATABASE_URL="postgres://postgres:@cobi-dashboard-postgresql:5432/dashboard_auth?schema=public" \
--from-literal=CORE_DATABASE_URL="postgres://postgres:@cobi-dashboard-postgresql:5432/cobi_core?schema=public"
```
### On-premises StorageClass
```yaml theme={null}
postgresql:
primary:
persistence:
storageClass: "local-path"
size: 20Gi
```
***
## Option B — External / standalone PostgreSQL
Use this option when you have an existing PostgreSQL instance (on-prem server, managed service, etc.).
### Disable the subchart
```yaml theme={null}
postgresql:
enabled: false
```
### Create the database
```sql theme={null}
CREATE DATABASE dashboard_auth;
CREATE DATABASE cobi_core;
GRANT ALL PRIVILEGES ON DATABASE dashboard_auth TO ;
GRANT ALL PRIVILEGES ON DATABASE cobi_core TO ;
```
### Set the backend connection string
```bash theme={null}
kubectl create secret generic cobi-backend-secrets \
--namespace cobi \
--from-literal=DATABASE_URL="postgres://:@:/dashboard_auth?schema=public" \
--from-literal=CORE_DATABASE_URL="postgres://:@:/cobi_core?schema=public" \
# ... other variables
```
#### Connection string formats
| Scenario | Example |
| ------------------------ | ----------------------------------------------------------------------------------------- |
| On-prem server (no TLS) | `postgres://cobi:secret@192.168.1.50:5432/dashboard_auth?schema=public` |
| On-prem server (TLS) | `postgres://cobi:secret@db.internal:5432/dashboard_auth?schema=public&sslmode=require` |
| External managed service | `postgres://cobi:secret@db.example.com:5432/dashboard_auth?schema=public&sslmode=require` |
#### SSL mode options
| `sslmode` | When to use |
| ------------- | ---------------------------------------------------------------- |
| `disable` | Internal cluster traffic with no TLS requirement |
| `require` | Enforce TLS, skip certificate verification |
| `verify-full` | Full CA verification — compliance or internet-facing connections |
### Connecting to an external database from inside the cluster
**ExternalName Service** (DNS alias):
```yaml theme={null}
apiVersion: v1
kind: Service
metadata:
name: postgres-external
namespace: cobi
spec:
type: ExternalName
externalName: db.internal.example.com
```
Use `postgres-external:5432` as the host in the connection string.
**Endpoints object** (IP-based):
```yaml theme={null}
apiVersion: v1
kind: Service
metadata:
name: postgres-external
namespace: cobi
spec:
ports:
- port: 5432
---
apiVersion: v1
kind: Endpoints
metadata:
name: postgres-external
namespace: cobi
subsets:
- addresses:
- ip: 192.168.1.50
ports:
- port: 5432
```
***
## Verify the Connection
```bash theme={null}
kubectl logs -n cobi deploy/cobi-dashboard-backend | grep -i "database\|migration\|postgres"
```
A successful startup shows migration completion logs and no `ECONNREFUSED` errors.
# Qdrant Setup
Source: https://docs.hellocobi.com/deployment/qdrant-setup
Configure the Qdrant vector database for semantic search and document embeddings
## Overview
Qdrant is the vector database used by Cobi to store and search document embeddings. The `qdrant` dependency chart deploys it as a StatefulSet with persistent storage.
**Default ports:**
* `6333` — HTTP REST API (used by the backend)
* `6334` — gRPC API
The backend connects to Qdrant at `http://-qdrant:6333` via the `QDRANT_URL` environment variable.
## Configuration in values.yaml
```yaml theme={null}
qdrant:
enabled: true
image:
repository: qdrant/qdrant
tag: "v1.16.2" # pin to a specific version — "latest" can break on upgrades
pullPolicy: IfNotPresent
service:
httpPort: 6333
grpcPort: 6334
persistence:
enabled: true
storageClass: "" # use cluster default; set to "local-path" or "nfs-csi" for on-prem
accessModes:
- ReadWriteOnce
size: 10Gi # increase for large document collections
```
## Sizing the PVC
Qdrant stores vectors on disk. The required space depends on:
| Factor | Impact |
| ------------------- | ------------------------------------------------ |
| Number of documents | Each document generates one or more vectors |
| Embedding dimension | Typical embedding models use 384–1536 dimensions |
| Index type | HNSW graph adds \~1.5× overhead over raw vectors |
A rough estimate: **\~1 Gi per 500 000 vectors** at 768 dimensions with HNSW indexing. Start with `10Gi` and expand the PVC when utilization reaches 70%.
## On-Premises StorageClass
For clusters without a cloud storage provisioner, set a local or NFS-backed StorageClass:
```yaml theme={null}
qdrant:
persistence:
storageClass: "local-path" # rancher local-path-provisioner
size: 20Gi
```
## Exposing Qdrant (optional)
Qdrant is a ClusterIP service by default (internal only). To expose it for external tooling (e.g., Qdrant Web UI or admin scripts), add an Ingress:
```yaml theme={null}
qdrant:
ingress:
enabled: true
ingressClassName: nginx
annotations: {}
hosts:
- host: qdrant.example.com
paths:
- path: /
pathType: Prefix
servicePort: 6333
tls: []
```
Do not expose Qdrant publicly without authentication. The Qdrant HTTP API has no built-in auth by default — restrict access to internal networks or use an ingress that enforces authentication.
## Verify Qdrant
```bash theme={null}
# Port-forward the Qdrant HTTP API
kubectl port-forward -n cobi svc/cobi-dashboard-qdrant 6333:6333
# Check cluster health
curl http://localhost:6333/healthz
# List collections
curl http://localhost:6333/collections
```
A healthy response from `/healthz` returns `{"title":"qdrant - vector search engine","version":"..."}`.
## Connecting the Backend
Set the following variables in the backend Secret. The service name follows the pattern `-qdrant` — if your release is named `cobi-dashboard`, the service is `cobi-dashboard-qdrant`.
### In-cluster Qdrant (deployed via this chart)
```bash theme={null}
kubectl create secret generic cobi-backend-secrets \
--namespace cobi \
--from-literal=QDRANT_URL="http://cobi-dashboard-qdrant:6333" \
--from-literal=QDRANT_LOCAL_ONLY="true" \
--from-literal=QDRANT_API_KEY="" \
--from-literal=QDRANT_CLUSTER_KEY="" \
--from-literal=QDRANT_COLLECTION_PREFIX="prod_" \
# ... other keys
```
### External / standalone on-prem Qdrant
If Qdrant is running on a separate server or another cluster:
```bash theme={null}
kubectl create secret generic cobi-backend-secrets \
--namespace cobi \
--from-literal=QDRANT_URL="http://qdrant.internal.example.com:6333" \
--from-literal=QDRANT_LOCAL_ONLY="true" \
--from-literal=QDRANT_API_KEY="" \
--from-literal=QDRANT_CLUSTER_KEY="" \
--from-literal=QDRANT_COLLECTION_PREFIX="prod_" \
# ... other keys
```
Keep `QDRANT_LOCAL_ONLY="true"` for any self-hosted Qdrant regardless of where it runs. Set it to `"false"` only when using [Qdrant Cloud](https://cloud.qdrant.io).
### Qdrant Cloud
```bash theme={null}
kubectl create secret generic cobi-backend-secrets \
--namespace cobi \
--from-literal=QDRANT_URL="https://.us-east4-0.gcp.cloud.qdrant.io:6333" \
--from-literal=QDRANT_LOCAL_ONLY="false" \
--from-literal=QDRANT_API_KEY="" \
--from-literal=QDRANT_CLUSTER_KEY="" \
--from-literal=QDRANT_COLLECTION_PREFIX="prod_" \
# ... other keys
```
### Backend environment variable reference
| Variable | Required | Description |
| -------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `QDRANT_URL` | Yes | Full HTTP(S) URL of the Qdrant REST API, including port |
| `QDRANT_LOCAL_ONLY` | Yes | `"true"` for self-hosted (in-cluster or on-prem); `"false"` for Qdrant Cloud |
| `QDRANT_API_KEY` | Cloud only | API key from the Qdrant Cloud console. Leave empty for local |
| `QDRANT_CLUSTER_KEY` | Cloud only | Cluster key from the Qdrant Cloud console. Leave empty for local |
| `QDRANT_COLLECTION_PREFIX` | No | Prefix prepended to all collection names. Use to namespace multiple environments (e.g. `"prod_"`, `"staging_"`) sharing one Qdrant instance |
## Backup
Qdrant supports snapshot-based backups via its REST API:
```bash theme={null}
# Create a snapshot of a collection
curl -X POST http://localhost:6333/collections//snapshots
# List snapshots
curl http://localhost:6333/collections//snapshots
```
Snapshots are written to the Qdrant PVC. Copy them to external storage using a CronJob or `kubectl cp`.
# Requirements
Source: https://docs.hellocobi.com/deployment/requirements
Hardware, software, and access requirements for deploying Cobi on-premises
## CLI Tools
Install these on the machine you will run Helm from:
| Tool | Minimum version | Install |
| --------- | --------------- | -------------------------------------------------------- |
| `kubectl` | 1.28+ | [kubernetes.io](https://kubernetes.io/docs/tasks/tools/) |
| `helm` | 3.12+ | [helm.sh](https://helm.sh/docs/intro/install/) |
## Cluster Requirements
### Kubernetes
| Property | Requirement |
| ------------------ | --------------------------------------------------- |
| Kubernetes version | 1.28+ |
| CNI | Any (Calico, Cilium, Flannel, etc.) |
| Ingress controller | `ingress-nginx` (IngressClass `nginx`) |
| Storage | A default StorageClass with `ReadWriteOnce` support |
### OpenShift
| Property | Requirement |
| ----------------- | ------------------------------------------------------ |
| OpenShift version | 4.12+ |
| Ingress | OpenShift Router (built-in) |
| Storage | Default StorageClass with `ReadWriteOnce` support |
| SCC | `anyuid` SCC for pods that need it (PostgreSQL, MinIO) |
## Node Sizing
### Without GPU inference (vLLM disabled)
| Component | CPU request | Memory request | Storage |
| ----------------- | ------------ | -------------- | --------------- |
| backend | 250m | 512Mi | — |
| frontend | 100m | 256Mi | — |
| dashboard-connect | 100m | 256Mi | — |
| postgresql | 250m | 512Mi | 10 Gi PVC |
| qdrant | 500m | 1Gi | 10 Gi PVC |
| minio | 250m | 512Mi | 100 Gi PVC |
| otel-lgtm | 500m | 1Gi | 25 Gi PVC total |
| **Total** | **\~2 vCPU** | **\~5 Gi** | **\~145 Gi** |
A single node with **4 vCPU / 8 Gi RAM** and **200 Gi** available disk is sufficient for a minimal deployment.
### With GPU inference (vLLM enabled)
The vLLM pod must be scheduled on a GPU node. The recommended model (Qwen3.5-9B-AWQ) requires:
| Resource | Minimum | Recommended |
| -------------------- | ----------------------------- | ------------------------------------------------------- |
| GPU | 1× NVIDIA GPU with 16 Gi VRAM | 1× A10G 24 Gi (e.g. `g5.2xlarge` or on-prem equivalent) |
| CPU | 4 vCPU | 8 vCPU |
| RAM | 20 Gi | 28 Gi |
| Disk (model weights) | 30 Gi | 80 Gi PVC |
| NVIDIA driver | 525+ | 535+ |
| CUDA | 11.8+ | 12.x |
The GPU node must run the **NVIDIA device plugin** DaemonSet so `nvidia.com/gpu` is visible as a schedulable resource. See [GPU Setup](/deployment/gpu-setup).
## Image Registry Access
All Cobi application images (`hellocobi/*`) are hosted on Docker Hub as private images. You need:
1. Docker Hub credentials with pull access to the `hellocobi` organization.
2. A Kubernetes Secret of type `kubernetes.io/dockerconfigjson` in the target namespace.
```bash theme={null}
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username= \
--docker-password= \
--docker-email= \
--namespace
```
Reference it in your values file:
```yaml theme={null}
global:
imagePullSecrets:
- dockerhub-secret
```
## Hugging Face Token
vLLM downloads model weights from [huggingface.co](https://huggingface.co) at startup. You need a Hugging Face account and an access token with read access to the model repository:
* [Create a token](https://huggingface.co/settings/tokens) with `read` scope.
* Pass it via `vllmstack.servingEngineSpec.modelSpec[0].hf_token` in your values file.
* For air-gapped clusters, pre-download the model weights and serve them from a local cache volume.
## Persistent Storage
All stateful components use `ReadWriteOnce` PersistentVolumeClaims. The default StorageClass is used unless you specify `storageClass` in each component's values.
For on-premises clusters without a cloud storage provisioner, common options are:
| Provisioner | Notes |
| ----------------------- | -------------------------------------------------- |
| `rancher.io/local-path` | Single-node dev/staging; data is local to the node |
| `nfs.csi.k8s.io` | Multi-node HA; requires an NFS server |
| OpenEBS | Block storage for bare-metal clusters |
| Longhorn | Distributed block storage for bare-metal clusters |
Set the storage class globally per component:
```yaml theme={null}
postgresql:
primary:
persistence:
storageClass: "local-path"
qdrant:
persistence:
storageClass: "local-path"
minio:
persistence:
storageClass: "local-path"
```
# vLLM Inference Setup
Source: https://docs.hellocobi.com/deployment/vllm-setup
Deploy a self-hosted LLM inference endpoint using the vllm-stack subchart
## Overview
The `vllmstack` subchart deploys [vLLM](https://github.com/vllm-project/vllm) as an OpenAI-compatible inference server. The backend communicates with it through the `dashboard-connect` service using the standard `/v1/chat/completions` API.
The vLLM pod requires a GPU node and is **disabled by default**. Enable it when you have GPU capacity available.
## GPU Node Prerequisite
Before enabling vLLM, ensure:
1. A GPU node exists in the cluster with NVIDIA drivers installed.
2. The **NVIDIA device plugin** DaemonSet is running so `nvidia.com/gpu` is a schedulable resource.
3. GPU nodes carry the taint `nvidia.com/gpu:NoSchedule` (the vLLM pod tolerates this automatically when configured as shown below).
```bash theme={null}
# Verify GPUs are visible
kubectl get nodes -l karpenter.sh/nodepool=gpu-spot
kubectl describe node | grep "nvidia.com/gpu"
```
For bare-metal or on-prem clusters, apply the device plugin manually:
```bash theme={null}
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml
```
## Recommended Model — Qwen3.5-9B-AWQ
For a single GPU with 16–24 Gi VRAM (e.g., A10G, A100-40G, RTX 4090), the recommended model is **`QuantTrio/Qwen3.5-9B-AWQ`** — an AWQ-quantized 9B parameter model that fits comfortably in 24 Gi VRAM with a 8 192 token context window.
| Property | Value |
| ---------------------- | ------------------------------ |
| Model | `QuantTrio/Qwen3.5-9B-AWQ` |
| Quantization | AWQ (4-bit weights) |
| VRAM usage | \~9–12 Gi (vs. 18 Gi for BF16) |
| Context window | 8 192 tokens |
| GPU | 1× A10G 24 Gi (or equivalent) |
| Disk (weights + cache) | 80 Gi PVC |
### Values — Qwen3.5-9B-AWQ (recommended)
```yaml theme={null}
vllmstack:
enabled: true
servingEngineSpec:
enableEngine: true
runtimeClassName: "nvidia" # required for GPU workloads
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
# Target GPU nodes by node pool label (adjust to your cluster labels)
# nodeSelector:
# karpenter.sh/nodepool: gpu-spot # Karpenter-managed
# # OR for on-prem / bare-metal:
# node-role/gpu: "true"
modelSpec:
- name: "qwen35-9b"
repository: "vllm/vllm-openai"
tag: "latest"
modelURL: "QuantTrio/Qwen3.5-9B-AWQ"
replicaCount: 1
# Resource requests — size for a g5.2xlarge or equivalent on-prem GPU node
requestCPU: 4
requestMemory: "28Gi"
requestGPU: 1
limitCPU: "4"
limitMemory: "28Gi"
shmSize: "2Gi" # shared memory for single-GPU; no NCCL needed
pvcStorage: "80Gi" # model weights (~10 Gi AWQ) + HF cache
pvcAccessMode:
- ReadWriteOnce
# Cache directories on the PVC
env:
- name: HF_HUB_CACHE
value: /data/huggingface/hub
- name: HUGGINGFACE_HUB_CACHE
value: /data/huggingface/hub
- name: TRANSFORMERS_CACHE
value: /data/huggingface/transformers
- name: HF_HUB_DISABLE_XET
value: "1"
- name: XDG_CACHE_HOME
value: /data/.cache
- name: TMPDIR
value: /data/tmp
# Init container creates cache dirs on the PVC before vLLM starts
initContainer:
name: init-cache-dirs
image: busybox:1.36
command: ["sh"]
args:
- -c
- "mkdir -p /data/tmp /data/huggingface/hub /data/huggingface/transformers /data/.cache && chmod -R 777 /data"
mountPvcStorage: true
vllmConfig:
dtype: "float16"
tensorParallelSize: 1 # single GPU
maxModelLen: 8192
gpuMemoryUtilization: 0.90
enableChunkedPrefill: true
enablePrefixCaching: false # disable with AWQ to avoid cache fragmentation
maxNumSeqs: 16
extraArgs:
- "--trust-remote-code"
- "--language-model-only"
- "--download-dir"
- "/data/huggingface"
- "--quantization"
- "awq"
- "--enforce-eager"
- "--attention-backend"
- "flashinfer"
- "--max-num-batched-tokens"
- "1024"
- "--disable-log-stats"
hf_token: ""
routerSpec:
enableRouter: false
```
## Alternative Models
### Qwen3.5-9B (BF16, no quantization)
Requires 1× A10G 24 Gi. Slightly lower throughput than AWQ but avoids quantization artifacts.
```yaml theme={null}
modelSpec:
- name: "qwen35-9b-bf16"
modelURL: "Qwen/Qwen3.5-9B"
requestGPU: 1
requestMemory: "20Gi"
pvcStorage: "30Gi"
vllmConfig:
dtype: "bfloat16"
tensorParallelSize: 1
maxModelLen: 4096 # reduced — BF16 leaves ~4 Gi for KV cache
gpuMemoryUtilization: 0.92
enableChunkedPrefill: true
enablePrefixCaching: true
maxNumSeqs: 16
extraArgs:
- "--trust-remote-code"
```
### Qwen3.5-27B (4× GPU, high quality)
Requires 4× A10G 24 Gi (e.g., `g5.12xlarge` or 4-GPU bare-metal node).
```yaml theme={null}
modelSpec:
- name: "qwen35-27b"
modelURL: "Qwen/Qwen3.5-27B"
requestCPU: 16
requestMemory: "60Gi"
requestGPU: 4
limitCPU: "16"
limitMemory: "60Gi"
shmSize: "20Gi" # NCCL needs large shared memory for 4-GPU comms
pvcStorage: "80Gi" # 27B BF16 weights ≈ 54 Gi
vllmConfig:
dtype: "bfloat16"
tensorParallelSize: 4 # one shard per A10G
maxModelLen: 8192
gpuMemoryUtilization: 0.90
enableChunkedPrefill: true
enablePrefixCaching: true
maxNumSeqs: 32
extraArgs:
- "--trust-remote-code"
```
## Air-Gapped Deployment
In environments without internet access, pre-download the model weights to a shared volume or container registry mirror:
1. Download weights on a machine with internet access:
```bash theme={null}
pip install huggingface_hub
huggingface-cli download QuantTrio/Qwen3.5-9B-AWQ --local-dir ./model-weights
```
2. Serve the weights from a local HTTP server or a pre-populated PVC.
3. In the values file, set `--download-dir` to the local path and remove the `hf_token`.
## Verify the Inference Endpoint
Once the vLLM pod is `Running`:
```bash theme={null}
# Port-forward the vLLM service (it is a ClusterIP service)
kubectl port-forward -n cobi svc/cobi-dashboard-vllmstack-engine 8000:8000
# Test with a chat completion request
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen35-9b",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 64
}'
```
The service name follows the pattern: `-vllmstack-engine`.
## Configure dashboard-connect
Set `LLM_BASE_URL` to point at the vLLM ClusterIP service:
```bash theme={null}
kubectl create secret generic cobi-connect-secrets \
--namespace cobi \
--from-literal=LLM_BASE_URL="http://cobi-dashboard-vllmstack-engine:8000/v1" \
--from-literal=LLM_MODEL="qwen35-9b"
```
Then reference it in your values:
```yaml theme={null}
connect:
envFrom:
- secretRef:
name: cobi-connect-secrets
```
# Introduction
Source: https://docs.hellocobi.com/introduction
What is Cobi and how to get started
## What is Cobi?
Cobi is an AI-powered data analytics platform that lets you ask questions about your data in plain English, no SQL required. Connect your database and the Data Assistant handles the rest.
Connect your database and start chatting with your data instantly
Add the Data Assistant to your own website with a single iframe
Ask Cobi questions, create charts, and share answers inside Slack
Upload customers, transactions, and offers programmatically
Surface personalised offers and segments for your users
## How Cobi Works
There are two ways to get your data into Cobi.
Connecting a database is the fastest way to get started. Point Cobi at an existing PostgreSQL, MySQL, or MongoDB database and the Data Assistant queries it directly in real time.
If you need programmatic control or don't have a directly queryable database, you can upload data via the API instead, pushing structured records like customers, transactions, and offers to Cobi's hosted data platform.
## Getting Started
[Connect a database](/platform/data-connectors/overview) or [upload data via API](/quickstart).
Add [Custom Instructions](/platform/custom-instructions) so the Data Assistant understands your terminology and business rules.
Open the Data Assistant and ask questions about your data in plain English.
[Connect Slack](/platform/channels/slack/overview) or [embed the chat widget](/platform/channels/embed-chat-widget) so your team can explore data where they work.
Need help? Contact us at [support@hellocobi.com](mailto:support@hellocobi.com).
# Data Scopes
Source: https://docs.hellocobi.com/platform/channels/data-scopes
Restrict which tables an embed key can query and enforce row-level isolation for multi-tenant deployments
## Overview
A **data scope** defines the exact slice of your database that an embed key is allowed to access. Instead of granting an embed key unrestricted access to every table in your data source, you create a named scope that specifies:
* Which **tables** the key may query
* Which **row-level filter** should be applied so that each caller only sees their own rows
Scopes are created independently of embed keys and then assigned to a key during key creation. One scope can be shared across many keys.
A `scopeId` URL parameter is required whenever an embed key has a data scope
assigned to it. Requests without a valid `scopeId` will be rejected with a
`403` error.
## Creating a Scope
Navigate to **Developer** > **Channels** and click **Create Scope** (or open the Scopes tab if visible).
Provide a **Name** (required) and an optional **Description**.
| Field | Required | Description |
| --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | Yes | A short, human-readable label used to identify the scope when assigning it to embed keys (e.g. `Customer read-only`, `Outlet dashboard`) |
| **Description** | No | Free-text note that helps team members understand what data this scope exposes |
Click **Next** when done.
This step has two parts: **Row-level scope** and **Table access**.
### Row-level scope
Row-level scoping restricts each caller to only the rows that belong to them. It is required for multi-tenant deployments where different end-users must not see each other's data.
| Setting | Description |
| ---------------- | ------------------------------------------------------------------------ |
| **Scope table** | The table that owns the tenant identifier (e.g. `outlets`) |
| **Scope column** | The primary column in that table that identifies each tenant (e.g. `id`) |
At query time, Cobi automatically filters every query so that only rows matching the caller's `scopeId` value are returned.
If all callers should see the same data (e.g. a public read-only widget),
leave row-level scoping unconfigured and rely on table access restrictions
alone.
### Table access
Select which tables this scope can query. Tables are grouped to help you make the right choices:
| Group | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Related tables** | Tables that contain a foreign key referencing the scope column. These are typically the safest to include because row-level filtering applies to them automatically |
| **Other tables** | Tables without a direct relationship to the scope column. Queries against these tables will not be row-filtered, so include them only when full-table access is intentional |
Use the **Filter tables** search box to find specific tables quickly.
Click **Next** when done.
The **Review scope** screen summarises your configuration:
| Field | Value |
| ------------- | ------------------------------------------------------------------------------------------------- |
| **Name** | The scope name you entered |
| **Tables** | Number of tables included |
| **Row scope** | The `table.column` pair used for filtering, and a note that it is *filtered by caller's scope ID* |
Click **Create scope** to save. The scope is immediately available for assignment to embed keys.
## Assigning a Scope to an Embed Key
When creating or editing an embed key, the **Security** step includes a **Data scope** dropdown:
| Option | Behaviour |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Full access (no scope)** | The key can query all tables enabled on the data source. No `scopeId` parameter is required or used |
| **A named scope** | The key can only query the tables and rows defined in that scope. A `scopeId` must be supplied in every iframe URL |
Selecting a scope on an embed key is permanent for that key. To change the
scope, create a new embed key with the desired scope and revoke the old one.
## Using scopeId in the Iframe URL
When an embed key has a data scope assigned, every iframe `src` must include the `scopeId` parameter. The value should be the identifier of the tenant or entity whose data the current user is allowed to see — typically the primary key value from the scope table.
```html theme={null}
```
Cobi uses `scopeId` to filter all queries at the row level. In the example above, queries against the `outlets` scope table would automatically include `WHERE outlets.id = 'outlet_42'`, and all related table queries would be filtered accordingly.
Render the `scopeId` server-side from your own authentication system. Never
derive it from user-supplied input or client-side storage, as this would allow
a user to spoof another tenant's ID.
### What happens without scopeId
If a request is made to an embed key that has a scope assigned but no `scopeId` is provided, Cobi returns a `403` error and the widget displays an access-denied message. This is intentional — a missing `scopeId` is treated as an unauthorised request.
## Managing Scopes
From **Developer > Channels** (Scopes tab) you can view all scopes along with the number of tables and their row-scope configuration.
| Action | How |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **View** | Click on a scope to see its full configuration |
| **Delete** | Open the scope and click **Delete**. Any embed key currently using this scope will lose its data access restrictions — review key assignments before deleting |
Deleting a scope does not automatically revoke the embed keys assigned to it.
Those keys will revert to having no scope, potentially exposing more data than
intended. Audit your embed keys after any scope deletion.
## Security Considerations
* **Scope isolation** — each embed key can only be assigned one scope. There is no way for a key to escalate beyond its assigned scope at runtime.
* **Row-level enforcement** — Cobi enforces row-level filtering server-side. It cannot be bypassed by modifying the iframe URL or the chat query.
* **scopeId is not a secret** — the `scopeId` value is visible in the iframe `src`. It should only identify *which tenant*, not authenticate the request. Authentication is handled by the embed key itself. Always combine `scopeId` with domain restrictions (allowed origins) in production.
* **Minimal table access** — follow the principle of least privilege: only include the tables your widget actually needs. Excluding sensitive tables from the scope is the safest way to prevent unintended data exposure.
# Chat Widget
Source: https://docs.hellocobi.com/platform/channels/embed-chat-widget
Add Cobi's Data Assistant to your website with a simple iframe
## Overview
The Cobi chat widget can be embedded on any website as an iframe. Visitors interact with your AI assistant directly on your site. Authentication is handled via embed API keys that you create in the Cobi dashboard.
Embed keys are scoped to a single data source and can be restricted to
specific domains for production use.
## Getting Started
Navigate to **Developer** > **Channels** in the Cobi dashboard. Click **Create Embed Key** and fill in:
| Field | Description |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Name** | A label for this key (e.g. "Production website") |
| **Data Source** | The data source the embed will query against |
| **Data Scope** *(optional)* | Restrict which tables the key can query and enforce row-level isolation. See [Data Scopes](/platform/channels/data-scopes) |
| **Allowed Origins** *(optional)* | Domains permitted to load the widget (e.g. `https://yoursite.com`) |
| **Expiry Date** *(optional)* | When the key should expire. Leave blank for no expiry |
| **Chat History** *(optional)* | Allow this key to access conversation history. Must be enabled here before the `chatHistory` URL parameter has any effect |
The full API key is only shown once at creation time. Copy the embed snippet immediately. You won't be able to retrieve the full key later.
After creating the key, you'll see an embed snippet. Copy and paste it into your website's HTML:
```html theme={null}
```
Load your page in a browser. The chat widget should appear and respond to messages using your configured data source.
## Configuration
### URL Parameters
The embed URL supports the following query parameters:
| Parameter | Required | Description |
| ------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key` | Yes | Your embed API key |
| `scopeId` | Conditional | The tenant or entity identifier used for row-level filtering. Required when the embed key has a [data scope](/platform/channels/data-scopes) assigned. Requests without it return `403` |
| `theme` | No | `light`, `dark`, or `system` (default). Matches the widget's appearance to your site |
| `chatHistory` | No | `true` to show the chat history panel. Only takes effect if history was enabled when the embed key was created |
| `userId` | No | An identifier for the current user. See [User Identity](#user-identity) below |
```
https://app.hellocobi.com/embed/chat?key=cobi_embed_...&scopeId=outlet_42&theme=dark&chatHistory=true&userId=user_123
```
### Allowed Origins
When you specify allowed origins during key creation, only those domains can load the widget. Requests from other origins are rejected with a `403` error.
* Leave empty during development to allow any origin
* For production, always specify your exact domains (e.g. `https://yoursite.com`)
* The `Origin` header is required when allowed origins are configured
You can add multiple allowed origins to a single key, for example if your site
is accessible on both `https://example.com` and `https://www.example.com`.
### Theming
The widget respects light and dark mode via the `theme` parameter:
| Value | Behavior |
| -------- | ------------------------------------------ |
| `system` | Matches the user's OS preference (default) |
| `light` | Forces light mode |
| `dark` | Forces dark mode |
### Chat History
Chat history lets users return to previous conversations in the widget. It is controlled by two things:
1. **Key setting:** History must be enabled when creating the embed key in the dashboard. If it wasn't, passing `chatHistory=true` in the URL has no effect.
2. **URL parameter:** Even if the key supports history, you must pass `chatHistory=true` to make the history panel visible in the iframe.
This two-layer control lets you enable history on a key but selectively show it only on certain pages.
### User Identity
By default, the widget generates a unique user ID per browser and stores it in `localStorage`. A returning visitor on the same browser is recognised as the same user and can see their previous chats, but the same user on a different device starts fresh.
To share chat history across browsers, pass your own `userId`:
```html theme={null}
```
Pass a stable, per-user identifier from your own auth system (e.g. a database
user ID). Do not use personally identifiable information such as email
addresses as the `userId`.
### Data Scoping
Data scopes let you restrict an embed key to a specific subset of tables and enforce row-level isolation so each caller only sees their own data. This is the recommended security model for any multi-tenant deployment.
When a scope is assigned to an embed key, you must pass a `scopeId` in every iframe URL. The value is the primary key of the tenant or entity whose data the current user should see:
```html theme={null}
```
Always render `scopeId` server-side from your own authentication system.
Never derive it from user input or client-side storage, as this would allow a
user to access another tenant's data.
See [Data Scopes](/platform/channels/data-scopes) for a full walkthrough on creating scopes and configuring row-level access.
## Available Connections
The embed widget queries whichever data source you select when creating the embed key. All data source types supported by Cobi are available:
| Type | Notes |
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
| **PostgreSQL** | PostgreSQL 12+ |
| **MySQL** | MySQL 8.0+ |
| **MongoDB** | MongoDB Atlas and self-hosted instances. See [MongoDB](/platform/data-connectors/mongodb) for schema conventions |
The data source is locked to the key at creation time and cannot be changed by the client.
## Rate Limiting
Each embed key is rate-limited to **1,000 requests per hour**. When the limit is exceeded:
* The API returns `429 Too Many Requests` with a `Retry-After` header
* The widget displays a user-friendly message asking the visitor to wait
If you need higher limits, contact the Cobi team at [support@hellocobi.com](mailto:support@hellocobi.com).
## Security
Keys are hashed at rest and only the first 18 characters are stored for display. When allowed origins are configured, requests from any other domain are rejected outright. The data source is locked to the key at creation time and cannot be changed by the client. Embed requests have restricted access and cannot view internal documents. The widget does not use cookies or require user authentication.
When a [data scope](/platform/channels/data-scopes) is assigned to a key, table access and row-level filtering are enforced server-side and cannot be bypassed by modifying the iframe URL. The `scopeId` parameter identifies the caller's tenant but does not authenticate the request — authentication is always handled by the embed key itself.
## Managing Keys
From **Developer > Channels** you can view all active embed keys along with their allowed origins and status, and permanently revoke any key. When a key is deleted it takes effect on the next request with no delay or grace period.
## Troubleshooting
| Issue | Cause | Solution |
| ------------------------------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Widget shows "Missing API Key" | No `key` parameter in the URL | Check the iframe `src` includes `?key=cobi_embed_...` |
| `403` error — access denied | Origin not in allowed list, or `scopeId` missing/invalid for a scoped key | Add your domain to the key's allowed origins; if the key has a data scope, ensure `scopeId` is present in the URL and matches a valid tenant ID |
| `401` error | Key is invalid, expired, or deleted | Create a new embed key in Developer > Channels |
| `429` error | Rate limit exceeded | Wait for the rate limit window to reset (1 hour) |
| Widget crashes / white screen | JavaScript error | The widget includes an error boundary. Try refreshing. If it persists, check the browser console |
# Account Linking
Source: https://docs.hellocobi.com/platform/channels/slack/account-linking
Connect your Slack user to your Cobi account
Account linking connects your Slack user to your Cobi user. This improves attribution and makes it clear who asked each question.
## How linking works
A Cobi admin must connect the Slack workspace from **Settings** > **Integrations** first.
In Slack, run `/cobi link`.
Click **Link account** in the private Slack response.
Finish linking while signed in to the Cobi account that belongs to the connected organization.
Run `/cobi status` to confirm your account is linked.
## Linked users
Linked users are attributed to their Cobi user. This is useful for audit trails, answer ownership, and support.
## Unlinked users
Unlinked users can still ask Cobi questions in a connected Slack workspace. Their questions are attributed to their Slack user instead of a Cobi user.
## External guests
External guests cannot use Cobi in Slack. If an external guest mentions Cobi, Cobi rejects the request.
## Common linking issues
| Issue | What to do |
| ---------------------------- | --------------------------------------------------------------- |
| `Slack is not connected` | Ask a Cobi admin to install Slack from Cobi settings |
| Link expired | Run `/cobi link` again |
| Link already used | Run `/cobi link` again to create a new link |
| Wrong Cobi organization | Switch to a Cobi user in the connected organization, then retry |
| `/cobi status` says unlinked | Complete `/cobi link` in Cobi |
You only need to link once per connected Slack workspace.
# Install Slack
Source: https://docs.hellocobi.com/platform/channels/slack/install
Connect a Slack workspace to a Cobi organization
Install Slack from Cobi so people in your workspace can ask the Data Assistant questions without leaving Slack.
## Prerequisites
* You are a Cobi admin.
* You have permission to install apps in Slack.
* Your Cobi organization has at least one connected data source.
A Slack workspace can be connected to one Cobi organization at a time. This keeps Slack questions routed to the correct data sources and permissions.
## Connect Slack
In Cobi, go to **Settings**.
Select the **Integrations** tab.
Find the Slack card and click **Connect**.
Review the Slack authorization screen and approve the install.
After Slack redirects back to Cobi, the Slack card shows the connected workspace.
In Slack, invite Cobi to every channel where people should ask questions.
## Reconnect Slack
Use **Reconnect** from the Slack card if the app was reinstalled in Slack or needs refreshed access. Reconnecting keeps the workspace mapped to the same Cobi organization.
## Disconnect Slack
Use **Disconnect** from the Slack card to remove the workspace connection from Cobi. After disconnecting, Cobi stops answering questions from that Slack workspace until an admin connects it again.
If a workspace is already connected to a different Cobi organization, it cannot be claimed by another organization. Disconnect it from the first organization before connecting it somewhere else.
## What users do next
After installation, users can:
* Mention Cobi in invited channels.
* DM Cobi.
* Run `/cobi help` to see commands.
* Run `/cobi link` to connect their Slack user to their Cobi account.
# Slack
Source: https://docs.hellocobi.com/platform/channels/slack/overview
Use Cobi inside Slack to ask questions, create charts, and share answers with your team
Cobi brings the Data Assistant into Slack so your team can ask questions where work is already happening. Ask in a channel, DM the app, use `/cobi`, or continue a Cobi thread.
Mention Cobi in channels where the app has been invited
Run `/cobi ask`, `/cobi chart`, `/cobi table`, `/cobi link`, and `/cobi status`
Connect your Slack user to your Cobi user for attribution
Fix common setup, linking, and response issues
## The main ways to use Cobi
### Mention Cobi
Mention Cobi in a channel to ask a question:
```text theme={null}
@Cobi how is revenue trending this month?
```
Cobi replies in a thread so the answer stays close to the original question.
### DM Cobi
Send Cobi a direct message for questions that do not need to be visible in a channel.
```text theme={null}
hello cobi
```
### Use `/cobi`
Use slash commands when you want a specific flow:
```text theme={null}
/cobi chart revenue by week
/cobi table top customers
/cobi status
```
Commands like `help`, `status`, and `link` are private to you. Commands that ask for analysis create a public thread in the channel where you ran them.
### Follow up in threads
After Cobi answers in a thread, you can keep replying there without mentioning Cobi again.
```text theme={null}
go ahead
show that by month
explain the rows above
```
Cobi uses the prior thread context so follow-ups stay connected.
## What Cobi can return
* Short answers with the key takeaway.
* Chart images for trend or comparison questions.
* Table or CSV artifacts for ranking, list, or record-level questions.
* An `Open Cobi` button when the full answer is easier to inspect in the web app.
* Feedback tracking when you react to an answer with thumbs up or thumbs down.
## Before you start
Ask a Cobi admin to [connect your Slack workspace](/platform/channels/slack/install).
Invite Cobi to each channel where people should ask questions.
Run [`/cobi link`](/platform/channels/slack/account-linking) if you want Slack activity attributed to your Cobi user.
Cobi is designed to stay quiet in normal channel conversation. It responds when mentioned, DMed, invoked with `/cobi`, or when you reply inside a Cobi-owned thread.
# Slash Commands
Source: https://docs.hellocobi.com/platform/channels/slack/slash-commands
Reference for the /cobi command in Slack
Cobi uses one Slack command: `/cobi`. Use subcommands to link your account, check status, ask a question, create a chart, or create a table.
## Quick reference
| Command | Visibility | What it does |
| ------------------------ | ------------- | --------------------------------------------------------------------------- |
| `/cobi help` | Private | Shows the available commands |
| `/cobi status` | Private | Shows whether the workspace is connected and whether your account is linked |
| `/cobi link` | Private | Creates a one-time account link |
| `/cobi ask ` | Public thread | Starts an answer thread in the current channel |
| `/cobi chart ` | Public thread | Starts an answer thread and asks Cobi for a chart |
| `/cobi table ` | Public thread | Starts an answer thread and asks Cobi for a table or CSV |
## `/cobi help`
Shows a concise command reference in Slack.
```text theme={null}
/cobi help
```
Use this when you forget the command names.
## `/cobi status`
Shows whether the Slack workspace is connected to Cobi and whether your Slack user is linked to your Cobi account.
```text theme={null}
/cobi status
```
The response is private to you.
## `/cobi link`
Creates a one-time link that connects your Slack user to your Cobi user.
```text theme={null}
/cobi link
```
The response is private to you and includes a **Link account** button. The link is valid for 15 minutes.
## `/cobi ask `
Starts a public thread in the current channel and answers your question there.
```text theme={null}
/cobi ask how is revenue trending this month?
```
Use this when you want the channel to see the answer.
## `/cobi chart `
Starts a public thread and asks Cobi to produce a chart when the data supports it.
```text theme={null}
/cobi chart revenue by week
```
Cobi posts the answer and attaches a chart image when a chart is available.
## `/cobi table `
Starts a public thread and asks Cobi to produce a table or CSV when the data supports it.
```text theme={null}
/cobi table top customers by revenue this quarter
```
Cobi posts the answer and attaches a table or CSV when there are rows to show.
`help`, `status`, and `link` are private responses. `ask`, `chart`, and `table` create public Slack threads in the channel where you run them.
# Slack Troubleshooting
Source: https://docs.hellocobi.com/platform/channels/slack/troubleshooting
Fix common Cobi Slack setup and usage issues
Use this guide when Cobi does not answer, linking fails, or an artifact does not appear.
## Cobi does not respond in a channel
Check these first:
* Cobi was invited to the channel.
* Your message mentioned Cobi, used `/cobi`, or was inside a Cobi-owned thread.
* The Slack workspace is connected in Cobi settings.
* You are not using Slack as an external guest.
If Cobi still does not respond, ask a Cobi admin to reconnect Slack from **Settings** > **Integrations**.
## Cobi is silent in normal channel chatter
That is expected. Cobi only responds when:
* You mention Cobi.
* You DM Cobi.
* You run `/cobi`.
* You reply in a thread where Cobi already answered.
This keeps the bot from interrupting unrelated conversations.
## `/cobi link` says Slack is not installed
Slack must be connected from Cobi before account linking works. Ask a Cobi admin to go to **Settings** > **Integrations** > **Slack** and connect the workspace.
## `/cobi status` says your account is unlinked
Run:
```text theme={null}
/cobi link
```
Then click **Link account** and finish the flow in Cobi. Run `/cobi status` again after linking.
## Your link expired
Account links are short-lived. Run `/cobi link` again to get a fresh link.
## Cobi answered but the chart or table is missing
Cobi keeps the answer visible even if an artifact cannot be attached. Try:
* Asking for a narrower date range.
* Asking for fewer rows.
* Asking for a chart or table explicitly.
* Opening the answer in Cobi if the button is available.
If the issue continues, contact [support@hellocobi.com](mailto:support@hellocobi.com) with the channel, approximate time, and question text.
## Cobi asks what records you mean
Reply inside the same Cobi thread where the table or chart appeared. Cobi uses thread context only inside Cobi-owned threads.
## Cobi says it cannot access the workspace
The workspace may not be connected to the Cobi organization, or it may already be connected to a different Cobi organization. Ask a Cobi admin to check the Slack card in **Settings** > **Integrations**.
## Still stuck
Contact [support@hellocobi.com](mailto:support@hellocobi.com). Include:
* The Slack workspace name.
* The channel or DM where the issue happened.
* The command or message you sent.
* The approximate time.
# Using Cobi in Slack
Source: https://docs.hellocobi.com/platform/channels/slack/using-cobi
Ask Cobi questions in channels, DMs, and threads
Once Slack is connected, Cobi works like a teammate in Slack. Ask a clear data question, then continue in the thread if you want to refine the answer.
## Ask in a channel
Mention Cobi in a channel where the app has been invited:
```text theme={null}
@Cobi how did revenue trend over the last 6 months?
```
Cobi replies in the same thread. This keeps the channel readable and keeps follow-ups attached to the original question.
## Ask in a DM
Open a direct message with Cobi and ask normally:
```text theme={null}
which products are driving the most revenue this quarter?
```
DMs are useful for quick exploration before you share a result with a channel.
## Follow up in a Cobi thread
Inside a thread where Cobi has already answered, reply without mentioning Cobi:
```text theme={null}
break that down by month
show the top customers
why did September drop?
```
Cobi uses the thread context, including earlier answers and visible Cobi tables, to understand what you mean.
## Ask for a chart
Use natural language or the chart command:
```text theme={null}
@Cobi chart revenue by week
/cobi chart revenue by week
```
Cobi posts the answer and attaches a chart image when a chart is useful.
## Ask for a table
Use tables for rankings, lists, and record-level results:
```text theme={null}
@Cobi table top customers by revenue this quarter
/cobi table top customers by revenue this quarter
```
Cobi posts a Slack-readable answer and attaches a table or CSV artifact when there are rows to show.
## Open the full answer in Cobi
Some answers include an **Open Cobi** button. Use it when you want a larger view in the web app.
## Give feedback
React to a Cobi answer with thumbs up or thumbs down. This helps your team track answer quality and improve the assistant over time.
## When Cobi responds
Cobi responds when:
* You mention Cobi in a channel.
* You DM Cobi.
* You run a `/cobi` command.
* You reply in a thread where Cobi already answered.
Cobi stays silent in normal channel chatter outside those cases.
# Custom Instructions
Source: https://docs.hellocobi.com/platform/custom-instructions
Add context and business rules to help Cobi understand your data
## Overview
Custom Instructions allow you to provide context, terminology definitions, and business rules that help the Data Assistant understand your specific data model and give more accurate responses.
## Why Use Custom Instructions?
Every organization has unique terminology, data structures, and business logic. Custom Instructions bridge the gap between your internal knowledge and Cobi's understanding of your data.
Map your business terms to database tables and columns
Clarify how tables connect and what joins are appropriate
Define how to calculate metrics like revenue, active users, or churn
Help Cobi write correct queries on the first try
## Adding Custom Instructions
Navigate to **Developer** > **Data Connectors** in the sidebar.
Click on the data source you want to configure.
In the connection modal, click the **Custom Instructions** tab.
Enter your instructions in plain text. Be specific and comprehensive.
Click **Save Changes** to apply your instructions.
## What to Include
### Business Context
Start with a high-level description of what your database contains:
```text theme={null}
This is a sports facility management database for a tennis club.
```
### Key Terminology
Define terms your team uses and map them to database concepts:
```text theme={null}
Key terminology:
- "Members" = users with active package subscriptions
- "Courts" = playing facilities (can have sub-courts)
- "Bookings" = court reservations
- "Lessons" = private coaching sessions (instructor_bookings table)
- "Programs" = group classes, camps, clinics
```
### Revenue Sources
Explain where different metrics come from:
```text theme={null}
Revenue comes from:
1. Court bookings (bookings table)
2. Private lessons (instructor_bookings table)
3. Program enrollments (program_enrollments table)
4. Membership fees (via Stripe subscriptions)
```
### Query Guidance
Provide specific instructions for common questions:
```text theme={null}
When asked about "revenue", sum the `total` field from bookings and instructor_bookings.
When asked about "members", count users with active package_user_mappings.
Payment status 1 = paid, 0 = unpaid.
Booking status 1 = confirmed, 0 = pending, 2 = cancelled.
```
## Example: Complete Custom Instructions
Here's a full example for a sports facility:
```text theme={null}
This is a sports facility management database for a Padel/Tennis club.
Key terminology:
- "Members" = users with active package subscriptions
- "Courts" = playing facilities (can have sub-courts)
- "Bookings" = court reservations
- "Lessons" = private coaching sessions (instructor_bookings table)
- "Programs" = group classes, camps, clinics
Revenue comes from:
1. Court bookings (bookings table)
2. Private lessons (instructor_bookings table)
3. Program enrollments (program_enrollments table)
4. Membership fees (via Stripe subscriptions)
When asked about "revenue", sum the `total` field from bookings and instructor_bookings.
When asked about "members", count users with active package_user_mappings.
Payment status 1 = paid, 0 = unpaid.
Booking status 1 = confirmed, 0 = pending, 2 = cancelled.
```
## Best Practices
### Be Specific
Instead of "customers are in the users table", say "customers are stored in the `users` table, identified by the `id` column, with `user_type = 'customer'` to distinguish from staff accounts."
### Document Status Codes
Many databases use numeric codes for statuses. Always document what they mean:
```text theme={null}
Order status codes:
- 0 = draft
- 1 = pending payment
- 2 = paid
- 3 = shipped
- 4 = delivered
- 5 = cancelled
```
### Explain Calculations
If metrics require specific calculations, be explicit:
```text theme={null}
"Active users" = users who have logged in within the last 30 days (check `last_login` column).
"Monthly revenue" = SUM(amount) from transactions WHERE status = 'completed' AND refunded = false.
```
### Note Table Relationships
Help Cobi understand how to join tables correctly:
```text theme={null}
To get customer orders: JOIN customers ON orders.customer_id = customers.id
To get order items: JOIN order_items ON order_items.order_id = orders.id
Products are linked via order_items.product_id = products.id
```
### Update Regularly
As your data model evolves, update your Custom Instructions to reflect new tables, columns, or business rules.
## Learned Information
In addition to Custom Instructions (which you write), Cobi automatically builds **Learned Information** as you interact with the Data Assistant. This includes:
* Table and column descriptions discovered through queries
* Relationships between tables
* Common query patterns
You can view and edit Learned Information in the **Learned Information** tab of your data connector settings.
## Troubleshooting
### Data Assistant Still Gets It Wrong
If Cobi isn't interpreting your data correctly:
1. Check your Custom Instructions for clarity
2. Add more specific examples
3. Correct any Learned Information that may be inaccurate
4. Try rephrasing your question to include the terminology you defined
### Instructions Not Taking Effect
After saving Custom Instructions:
* Start a new conversation to ensure the context is loaded
* Check that you're using the correct data source in your query
## Next Steps
* [Connect your database](/platform/data-connectors/overview) if you haven't already
* Start asking questions in the Data Assistant to see your instructions in action
# MongoDB
Source: https://docs.hellocobi.com/platform/data-connectors/mongodb
Connect a MongoDB database to Cobi
## Connecting to MongoDB Atlas
MongoDB Atlas uses SRV connection strings. Enter your Atlas cluster hostname (e.g. `cluster0.abc123.mongodb.net`) as the host. Cobi detects the SRV pattern automatically and handles the port for you.
## Connecting to a Self-Hosted MongoDB
Enter your server's hostname or IP address and port (`27017` by default).
## Schema Conventions
Cobi maps MongoDB collections to SQL tables. Keep the following in mind when chatting with the Data Assistant:
| Convention | Detail |
| ----------------- | ----------------------------------------------------------------------------------- |
| **Collections** | Each collection appears as a table |
| **`_id` field** | Present on every document as a unique identifier, used as the primary key |
| **Nested fields** | Flattened with underscores, so `address.city` becomes `address_city` |
| **Arrays** | Array fields are queryable. Ask the Data Assistant to expand or filter them |
| **Relationships** | MongoDB has no foreign keys. Reference related collections by matching field values |
## Create a Read-Only User
**Self-hosted MongoDB:** run the following in `mongosh`:
```js theme={null}
use admin
db.createUser({
user: "cobi_reader",
pwd: "secure_password",
roles: [{ role: "read", db: "your_database" }]
})
```
**MongoDB Atlas:** create the user in the Atlas UI under **Database Access**, with the built-in `read` role scoped to your database.
## Troubleshooting
See the [Troubleshooting](/platform/data-connectors/troubleshooting) page for help with connection issues.
# MySQL
Source: https://docs.hellocobi.com/platform/data-connectors/mysql
Connect a MySQL database to Cobi
## Best Practices
### Create a Read-Only User
We strongly recommend creating a dedicated database user with read-only permissions:
```sql theme={null}
CREATE USER 'cobi_reader'@'%' IDENTIFIED BY 'secure_password';
GRANT SELECT ON your_database.* TO 'cobi_reader'@'%';
FLUSH PRIVILEGES;
```
### Limit Table Access
If you only want Cobi to access specific tables, grant permissions selectively:
```sql theme={null}
-- Grant access to specific tables only
GRANT SELECT ON your_database.customers TO 'cobi_reader'@'%';
GRANT SELECT ON your_database.transactions TO 'cobi_reader'@'%';
FLUSH PRIVILEGES;
```
## Troubleshooting
See the [Troubleshooting](/platform/data-connectors/troubleshooting) page for help with connection issues.
# Overview
Source: https://docs.hellocobi.com/platform/data-connectors/overview
Connect your database to start exploring your data with Cobi
## Overview
Data Connectors allow you to link your existing database directly to Cobi, enabling the Data Assistant to query and analyze your data in real-time.
All database connections are **read-only**. Cobi will never modify your data.
## Supported Databases
Cobi currently supports the following database types:
* [**PostgreSQL**](/platform/data-connectors/postgresql) (versions 12 and above)
* [**MySQL**](/platform/data-connectors/mysql) (version 8.0 and above)
* [**MongoDB**](/platform/data-connectors/mongodb) (Atlas via SRV connection strings and self-hosted instances)
## Connecting Your Database
Navigate to the **Developer** section in the sidebar, then select **Data Connectors**.
Click the **Add Data Source** button to open the connection form.
Fill in your database credentials:
| Field | Description |
| ------------------- | --------------------------------------------------------------------------------- |
| **Connection Name** | A friendly name to identify this data source (e.g., "Production DB", "Analytics") |
| **Host** | Your database server address (e.g., `db.example.com` or an IP address) |
| **Port** | The database port (default: `5432` for PostgreSQL) |
| **Database Name** | The name of the specific database to connect to |
| **Username** | Database user with read access |
| **Password** | Password for the database user |
Click **Test Connection** to verify your credentials. Cobi will attempt to connect and confirm access.
Once the test succeeds, click **Save Changes** to store your connection.
## Network Configuration
### IP Allowlisting
If your database is behind a firewall, you'll need to allowlist Cobi's IP addresses:
```
xx.xx.xxx.xx
xx.xx.xxx.xx
```
Contact [support@hellocobi.com](mailto:support@hellocobi.com) to get the IP addresses for your region.
### Using SSH Tunnels
For databases that aren't publicly accessible, we recommend setting up an SSH tunnel or using a bastion host. Contact our support team for guidance on secure connection options.
## Managing Connections
### Edit a Connection
To update connection details:
1. Go to **Developer** > **Data Connectors**
2. Click on the connection you want to edit
3. Update the fields as needed
4. Click **Test Connection** to verify, then **Save Changes**
### Delete a Connection
To remove a data source:
1. Go to **Developer** > **Data Connectors**
2. Click on the connection
3. Click **Delete Connection**
Deleting a connection will remove all learned information and custom instructions associated with that data source.
## Next Steps
Once your database is connected:
* Add [Custom Instructions](/platform/custom-instructions) to help Cobi understand your data
* Start a conversation with the Data Assistant to explore your data
# PostgreSQL
Source: https://docs.hellocobi.com/platform/data-connectors/postgresql
Connect a PostgreSQL database to Cobi
## Best Practices
### Create a Read-Only User
We strongly recommend creating a dedicated database user with read-only permissions:
```sql theme={null}
CREATE USER cobi_reader WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE your_database TO cobi_reader;
GRANT USAGE ON SCHEMA public TO cobi_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO cobi_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO cobi_reader;
```
### Limit Table Access
If you only want Cobi to access specific tables, grant permissions selectively:
```sql theme={null}
-- Grant access to specific tables only
GRANT SELECT ON customers, transactions, orders TO cobi_reader;
```
## Troubleshooting
See the [Troubleshooting](/platform/data-connectors/troubleshooting) page for help with connection issues.
# Troubleshooting
Source: https://docs.hellocobi.com/platform/data-connectors/troubleshooting
Diagnose and fix common Data Connector issues
## Connection Failed
If your connection test fails, work through the following checks:
1. **Firewall rules.** Make sure Cobi's IP addresses are allowlisted.
2. **Credentials.** Double-check your username and password are correct.
3. **Host and port.** Confirm the host address and port match your database configuration.
4. **Database name.** Check that the database exists and the user has been granted access to it.
5. **SSL requirements.** Some databases require SSL connections.
6. **MongoDB only.** Ensure outbound HTTPS access is permitted from your server. Contact [support@hellocobi.com](mailto:support@hellocobi.com) for on-premises or air-gapped deployments.
## Slow Queries
If the Data Assistant is slow when querying your database:
1. Ensure your database has appropriate indexes on the columns being queried.
2. Consider creating views for commonly queried data.
3. Add [Custom Instructions](/platform/custom-instructions) to help Cobi write more targeted queries.
# Quickstart
Source: https://docs.hellocobi.com/quickstart
Make your first API call to the Cobi Data API
## Prerequisites
* An API key (contact [support@hellocobi.com](mailto:support@hellocobi.com) to request one)
* A tool to make HTTP requests (cURL, Postman, or your preferred language)
## Your First API Call
Upload a customer record to get started:
```bash theme={null}
curl -X POST https://api.hellocobi.com/v1/data/customers \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "550e8400-e29b-41d4-a716-446655440001",
"region": "US",
"birthdate": "1990-01-01",
"gender": "MALE"
}
]
}'
```
A successful response:
```json theme={null}
{
"status": "success",
"message": "Records uploaded successfully",
"count": 1
}
```
## Available Endpoints
Once you're uploading successfully, explore the full set of endpoints:
* [Customers](/api-reference/data/endpoint/customers)
* [Transactions](/api-reference/data/endpoint/transactions)
* [Offers](/api-reference/data/endpoint/offers)
* [Shops, branches, categories, and more](/api-reference/data/overview)
## Best Practices
Send up to 5,000 records per request for the best throughput. Before uploading at scale, check your payload against the [data models](/api-reference/data/overview) to catch schema errors early. Always check the response status on each request and retry with exponential backoff on `429` or `5xx` responses.
## Next Steps
* Read the [Authentication](/authentication) guide for API key security and error codes
* Browse the [Data Models](/api-reference/data/overview) to see required and optional fields for each record type
* Check out the [Recommendations API](/api-reference/recommendations/overview) to surface personalised offers for your users