# Log Search Result Click Source: https://developer.thehaystack.ai/api-reference/endpoints/analytics-click POST /logging/search-result-clicks Track when a user clicks a search result # Log Media Playback Event Source: https://developer.thehaystack.ai/api-reference/endpoints/analytics-media-playback POST /logging/media-playback-events Record a play/pause/seek/progress/end event for a media asset. Used by the embed player. # Log Page View Source: https://developer.thehaystack.ai/api-reference/endpoints/analytics-page-view POST /logging/page-views Record a frontend page view. Used by the embed/Connect frontend — most API consumers do not need to call this. # Create Chapter Source: https://developer.thehaystack.ai/api-reference/endpoints/chapters-create POST /media/chapters # Delete Chapter Source: https://developer.thehaystack.ai/api-reference/endpoints/chapters-delete DELETE /media/chapters/{chapterId} # Get Chapter Source: https://developer.thehaystack.ai/api-reference/endpoints/chapters-get GET /media/chapters/{chapterId} # List Chapters Source: https://developer.thehaystack.ai/api-reference/endpoints/chapters-list GET /media/chapters # Update Chapter Source: https://developer.thehaystack.ai/api-reference/endpoints/chapters-update PATCH /media/chapters/{chapterId} # Create Collection Source: https://developer.thehaystack.ai/api-reference/endpoints/collections-create POST /collections/create Create a new collection # Get Collection Source: https://developer.thehaystack.ai/api-reference/endpoints/collections-get GET /collections/{collectionId} Retrieve a single collection by ID # List Collections Source: https://developer.thehaystack.ai/api-reference/endpoints/collections-list GET /collections Retrieve all collections for your account # Update Collection Source: https://developer.thehaystack.ai/api-reference/endpoints/collections-update PATCH /collections/{collectionId} Update a collection's metadata. # Get Frontend Style Source: https://developer.thehaystack.ai/api-reference/endpoints/frontend-style GET /frontend/style Return the frontend style configuration used by the embed/Connect surface. # Get Home Feed Source: https://developer.thehaystack.ai/api-reference/endpoints/home GET /home Return a ready-to-render home feed (recent items, series with their items). Used to populate the default Haystack frontend. # Delete Item Artwork Source: https://developer.thehaystack.ai/api-reference/endpoints/items-artwork-delete POST /items/{itemId}/artwork/delete Remove artwork of a specific variant from an item. # Upload Item Artwork Source: https://developer.thehaystack.ai/api-reference/endpoints/items-artwork-upload POST /items/{itemId}/artwork/upload Upload artwork for an item as multipart/form-data. Supports four variants: `square`, `wide`, `ultraWide`, `vertical`. Max size 15 MB. # Create Item Source: https://developer.thehaystack.ai/api-reference/endpoints/items-create POST /items/create Create a new content item # Delete Item Source: https://developer.thehaystack.ai/api-reference/endpoints/items-delete DELETE /items/{itemId}/delete Permanently delete an item and all its related media assets, resources, scriptures, and analytics events. # Get Item Source: https://developer.thehaystack.ai/api-reference/endpoints/items-get GET /items/{itemId} Retrieve a single item by ID # List Items Source: https://developer.thehaystack.ai/api-reference/endpoints/items-list GET /items Retrieve content items with filtering and pagination # Publish Item Source: https://developer.thehaystack.ai/api-reference/endpoints/items-publish POST /items/{itemId}/publish Publish a ready item. No request body — the item must already be in `ready` or `unpublished` status with an indexed media asset. # List Similar Items Source: https://developer.thehaystack.ai/api-reference/endpoints/items-similar GET /items/{itemId}/similar Return items semantically similar to this one, ranked by vector similarity. Useful for "Related sermons" modules. # Get Item Transcript Source: https://developer.thehaystack.ai/api-reference/endpoints/items-transcript GET /items/{itemId}/transcript Retrieve the full transcript for an item including semantic chunks. Requires the item to be processed. # Unpublish Item Source: https://developer.thehaystack.ai/api-reference/endpoints/items-unpublish POST /items/{itemId}/unpublish Take a published item offline. It remains in the library but is not discoverable via search or the public frontend. # Update Item Source: https://developer.thehaystack.ai/api-reference/endpoints/items-update PATCH /items/{itemId}/update Update an existing item # Create Media Asset Source: https://developer.thehaystack.ai/api-reference/endpoints/media-create POST /media/create Create a new media asset and receive an upload URL. Returns a secure upload URL where you can upload your video/audio file (max 5GB). If using YouTube or Vimeo for playback, provide externalPlatform and externalPlatformId, but you must still upload the original file to the returned uploadUrl for AI processing. # Delete Media Asset Source: https://developer.thehaystack.ai/api-reference/endpoints/media-delete DELETE /media/{mediaAssetId} Delete a media asset and its underlying file. If it was the indexed asset and the item was published, the item is unpublished. # Get Media Asset Source: https://developer.thehaystack.ai/api-reference/endpoints/media-get GET /media/{mediaAssetId} Retrieve media asset details including processing status # List Media Assets Source: https://developer.thehaystack.ai/api-reference/endpoints/media-list GET /media # Update Media Asset Source: https://developer.thehaystack.ai/api-reference/endpoints/media-update PATCH /media/{mediaAssetId}/update Update external platform link for a media asset. Only valid on video assets. # List Media Variant Types Source: https://developer.thehaystack.ai/api-reference/endpoints/media-variant-types GET /media/variantTypes Retrieve all media variant types for your organization. Variant types define the different formats of media that can be uploaded (e.g., original video, sermon audio, full service). # List Resource Types Source: https://developer.thehaystack.ai/api-reference/endpoints/resource-types-list GET /resource-types List the resource type categories configured for your account. Use these to populate a picker when creating resources. # Create Resource Source: https://developer.thehaystack.ai/api-reference/endpoints/resources-create POST /items/{itemId}/resources/create Attach a file, external link, or external video to an item. For `file` resources, the response includes an `uploadUrl` — a presigned S3 URL that you must PUT the file to (with the same `Content-Type` as `fileMimeType`) to complete the upload. `link` and `video` resources have no upload step and return `uploadUrl: null`. # Delete Resource Source: https://developer.thehaystack.ai/api-reference/endpoints/resources-delete DELETE /items/{itemId}/resources/{resourceId} Delete a resource. For `file` resources, the underlying file is also removed from storage. # Get Resource Source: https://developer.thehaystack.ai/api-reference/endpoints/resources-get GET /items/{itemId}/resources/{resourceId} Retrieve a single resource. # List Resources Source: https://developer.thehaystack.ai/api-reference/endpoints/resources-list GET /items/{itemId}/resources List all resources attached to an item. # Update Resource Source: https://developer.thehaystack.ai/api-reference/endpoints/resources-update PATCH /items/{itemId}/resources/{resourceId}/update Update metadata for an existing resource. The `contentType` and underlying file cannot be changed once created — create a new resource if you need a different type. # Get Bible Metadata Source: https://developer.thehaystack.ai/api-reference/endpoints/scriptures-bible-metadata GET /scriptures/bibleMetadata Return the canonical book list, chapter counts per book, and verse counts per chapter. Use to populate book/chapter/verse pickers. # Create Scripture Source: https://developer.thehaystack.ai/api-reference/endpoints/scriptures-create POST /scriptures # Delete Scripture Source: https://developer.thehaystack.ai/api-reference/endpoints/scriptures-delete DELETE /scriptures/{scriptureId} # Get Bible Text Source: https://developer.thehaystack.ai/api-reference/endpoints/scriptures-full-text GET /scriptures/fullText Retrieve Bible text for a book/chapter/verse range in the requested translation. # Get Scripture Source: https://developer.thehaystack.ai/api-reference/endpoints/scriptures-get GET /scriptures/{scriptureId} # Get Items by Scripture Source: https://developer.thehaystack.ai/api-reference/endpoints/scriptures-items GET /scriptures/items Find all items tagged with a specific scripture reference. # List Scriptures Source: https://developer.thehaystack.ai/api-reference/endpoints/scriptures-list GET /scriptures # Update Scripture Source: https://developer.thehaystack.ai/api-reference/endpoints/scriptures-update PATCH /scriptures/{scriptureId} Update metadata on a scripture. Pass `displayOrder` to reorder; subsequent siblings shift automatically. # Search Content Source: https://developer.thehaystack.ai/api-reference/endpoints/search GET /search Semantic search across sermons, scriptures, and series. By default returns all results in a single JSON response. When `stream=true`, returns a text/event-stream with these message types: - `results` — full results payload (same shape as the non-stream response) - `headline` — streaming AI headline (`{headline: string}` appended incrementally) - `overview` — streaming AI overview (`{overview: string}` appended incrementally) - `complete` — final message; stream closes - `error` — error event; stream closes **No Authentication Required**: This endpoint does NOT require authentication. Never include your API token when calling this endpoint, especially from client-side JavaScript, as this would expose your credentials to all users. **Using "Try It Out"**: This endpoint requires a church-specific base URL. To test this endpoint: 1. Click the server dropdown and select "Your church-specific Search API" 2. Replace `{churchShortname}` with your actual church shortname 3. Find your shortname in the [Haystack Dashboard](https://app.thehaystack.ai) under **Developer** → **API** Example: If your Search API URL is `https://gracechurch.thehaystack.ai/api`, your shortname is `gracechurch`. ## Response Format The search endpoint supports two response formats: ### Standard JSON Response (Default) Fast, simple response with search results only. Best for most use cases. ```javascript JavaScript theme={null} const SEARCH_URL = 'https://your-church-name.thehaystack.ai/api'; const response = await fetch( `${SEARCH_URL}/haystack/search?q=${encodeURIComponent('prayer')}` ); const data = await response.json(); // Returns: { query, queryAnalyticsId, items, scriptures, series } console.log('Found', data.items.length, 'items'); ``` ```python Python theme={null} import requests SEARCH_URL = 'https://your-church-name.thehaystack.ai/api' response = requests.get( f'{SEARCH_URL}/haystack/search', params={'q': 'prayer'} ) data = response.json() print(f"Found {len(data['items'])} items") ``` ```bash cURL theme={null} curl "https://your-church-name.thehaystack.ai/api/haystack/search?q=prayer" ``` ### Streaming Response with AI Overview (Advanced) For an AI-generated overview of results, add `?stream=true` to receive a Server-Sent Events stream. This feature is best suited for advanced integrations that need real-time AI summaries. **Event Types:** * `results`: Search results (same structure as JSON response) * `overview`: Chunks of AI-generated summary as they're generated * `complete`: Stream finished successfully * `error`: An error occurred **Example Implementations:** ```javascript JavaScript (Browser) theme={null} const SEARCH_URL = 'https://your-church-name.thehaystack.ai/api'; const eventSource = new EventSource( `${SEARCH_URL}/haystack/search?q=prayer&stream=true` ); let overviewText = ''; eventSource.addEventListener('results', (event) => { const message = JSON.parse(event.data); console.log('Results:', message.data); }); eventSource.addEventListener('overview', (event) => { const message = JSON.parse(event.data); overviewText += message.data.overview; console.log('Overview chunk:', message.data.overview); }); eventSource.addEventListener('complete', () => { console.log('Complete overview:', overviewText); eventSource.close(); }); eventSource.addEventListener('error', (event) => { console.error('Error:', JSON.parse(event.data).error); eventSource.close(); }); ``` ```python Python theme={null} import json import sseclient # pip install sseclient-py import requests SEARCH_URL = 'https://your-church-name.thehaystack.ai/api' response = requests.get( f'{SEARCH_URL}/haystack/search', params={'q': 'prayer', 'stream': 'true'}, stream=True ) client = sseclient.SSEClient(response) overview_text = '' for event in client.events(): message = json.loads(event.data) if message['type'] == 'results': print(f"Found {len(message['data']['items'])} items") elif message['type'] == 'overview': overview_text += message['data']['overview'] print('Overview chunk received') elif message['type'] == 'complete': print(f'Complete overview: {overview_text}') break elif message['type'] == 'error': print(f"Error: {message['error']}") break ``` ```php PHP theme={null} on('message', function($event, $data) use (&$overviewText) { $message = json_decode($data, true); switch ($message['type']) { case 'results': echo "Found " . count($message['data']['items']) . " items\n"; break; case 'overview': $overviewText .= $message['data']['overview']; echo "Overview chunk received\n"; break; case 'complete': echo "Complete overview: {$overviewText}\n"; break; case 'error': echo "Error: {$message['error']}\n"; break; } }); $client->listen(); ?> ``` ```ruby Ruby theme={null} # Using https://github.com/Tonkpils/celluloid-eventsource require 'celluloid/eventsource' require 'json' search_url = 'https://your-church-name.thehaystack.ai/api' url = "#{search_url}/haystack/search?q=prayer&stream=true" overview_text = '' source = Celluloid::EventSource.new(url) source.on_message do |raw_message| message = JSON.parse(raw_message) case message['type'] when 'results' puts "Found #{message['data']['items'].length} items" when 'overview' overview_text += message['data']['overview'] puts 'Overview chunk received' when 'complete' puts "Complete overview: #{overview_text}" source.close when 'error' puts "Error: #{message['error']}" source.close end end ``` ```bash cURL theme={null} # Stream SSE events to console curl -N "https://your-church-name.thehaystack.ai/api/haystack/search?q=prayer&stream=true" ``` **Popular SSE Libraries:** * **Python:** `sseclient-py`, `aiohttp-sse-client` * **PHP:** `mpociot/php-sse-client`, `artax/sse` * **Ruby:** `celluloid-eventsource`, `sse-client` * **Node.js:** `eventsource`, built-in `fetch` with stream handling * **Go:** `r3labs/sse`, standard `http` package # Delete Series Artwork Source: https://developer.thehaystack.ai/api-reference/endpoints/series-artwork-delete POST /series/{seriesId}/artwork/delete # Upload Series Artwork Source: https://developer.thehaystack.ai/api-reference/endpoints/series-artwork-upload POST /series/{seriesId}/artwork/upload Upload artwork for a series as multipart/form-data. Supports the same variant types as item artwork. # Create Series Source: https://developer.thehaystack.ai/api-reference/endpoints/series-create POST /series/create # Get Series Source: https://developer.thehaystack.ai/api-reference/endpoints/series-get GET /series/{seriesId} # List Series Source: https://developer.thehaystack.ai/api-reference/endpoints/series-list GET /series List sermon series. Supports `_expand=items` to include items per series. # Update Series Source: https://developer.thehaystack.ai/api-reference/endpoints/series-update PATCH /series/{seriesId}/update # Create Speaker Source: https://developer.thehaystack.ai/api-reference/endpoints/speakers-create POST /speakers/create Create a new speaker # Delete Speaker Source: https://developer.thehaystack.ai/api-reference/endpoints/speakers-delete DELETE /speakers/{speakerId} # Get Speaker Source: https://developer.thehaystack.ai/api-reference/endpoints/speakers-get GET /speakers/{speakerId} # Delete Speaker Image Source: https://developer.thehaystack.ai/api-reference/endpoints/speakers-image-delete DELETE /speakers/{speakerId}/artwork/delete # Upload Speaker Image Source: https://developer.thehaystack.ai/api-reference/endpoints/speakers-image-upload POST /speakers/{speakerId}/image/upload # List Speakers Source: https://developer.thehaystack.ai/api-reference/endpoints/speakers-list GET /speakers Retrieve all speakers # Update Speaker Source: https://developer.thehaystack.ai/api-reference/endpoints/speakers-update PATCH /speakers/{speakerId} # Click Position Distribution Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-click-position GET /stats/click-position Positions of clicked search results. # Click-Through Metrics Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-click-through GET /stats/click-through Search result click-through rates. # Media Plays Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-media-plays GET /stats/media-plays Media play counts over time. # Page View Metrics Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-page-views GET /stats/page-views Page view counts over time. # Search Media Play Rates Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-search-media-play-rates GET /stats/search-media-play-rates Rate at which searches lead to media plays. # Search Media Plays Duration Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-search-media-plays-duration GET /stats/search-media-plays-duration Duration of media plays initiated from search. # Search Quality Score Distribution Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-search-quality-score-distribution GET /stats/search-quality-score-distribution Histogram of search result scores. # Search Quality Time Series Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-search-quality-time-series GET /stats/search-quality-time-series Aggregated search quality score over time. # Top Items Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-top-items GET /stats/top-items Top-performing items (limit/offset supported). # Top Series Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-top-series GET /stats/top-series Top-performing series. # Trending Topics Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-trending-topics GET /stats/trending-topics Topics with rising engagement over the range. # Underperforming Topics Source: https://developer.thehaystack.ai/api-reference/endpoints/stats-underperforming-topics GET /stats/underperforming-topics Topics with low engagement or poor search quality. # API Reference Source: https://developer.thehaystack.ai/api-reference/introduction Complete endpoint documentation for the Haystack API ## Overview The Haystack API provides programmatic access to all content management, media processing, search, and analytics features. This reference documents all available endpoints with request/response schemas. ## Base URLs Haystack provides two APIs with different base URLs: ### Private API (Authenticated Endpoints) All authenticated endpoints (content management, media, analytics) use: ``` https://api.thehaystack.ai/v2/haystack ``` ### Search API (Public, No Authentication) The search endpoint uses a church-specific base URL: ``` https://{your-church-shortname}.thehaystack.ai/api ``` To find your Search API base URL: 1. Log in to your [Haystack Dashboard](https://app.thehaystack.ai) 2. Navigate to **Developer** → **API** 3. Copy your Search API Base URL from the Search API section ## Authentication Most endpoints require Bearer token authentication. Include your API token in the `Authorization` header: ```bash theme={null} Authorization: Bearer YOUR_API_TOKEN ``` **Important**: The [Search endpoint](/api-reference/endpoints/search) does NOT require authentication and should be called without an API token from public-facing applications. See the [Authentication guide](/authentication#endpoints-that-dont-require-authentication) for details. See the [Authentication guide](/authentication) for complete details on obtaining and managing API tokens. ## API Structure The Haystack API is organized into the following resource groups: ### Content Management Create, read, update, and delete content items Organize items into top-level collections Manage people who deliver content Tag content with topical categories ### Media Upload and manage video/audio files ### Discovery Semantic search across content ### Analytics Track user interactions and events ## Request Format ### HTTP Methods * `GET` - Retrieve resources * `POST` - Create new resources * `PATCH` - Update existing resources * `DELETE` - Remove resources ### Content Type All `POST` and `PATCH` requests should include: ```bash theme={null} Content-Type: application/json ``` Media file uploads use a two-step process: first create a media asset via the API to receive a signed upload URL, then PUT the file directly to that URL. See the [Media Management guide](/concepts/media-management) for details. ### Query Parameters Use query parameters for filtering, sorting, and pagination: ```bash theme={null} GET /items?collectionId=1&status=published&_orderBy[]=date,DESC&_limit=20&_offset=0 ``` **Common parameters:** * `_limit` - Maximum number of records to return (default: 50, max: 100) * `_offset` - Number of records to skip for pagination (default: 0) * `_orderBy` - Sort order as array of strings in format 'fieldName,direction' (e.g., 'title,ASC') * `_expand` - Related entities to include in the response (can specify multiple) ## Response Format ### Success Response Successful requests return a JSON object with the requested data: ```json theme={null} { "item": { "id": 42, "title": "Example Item", "date": "2025-01-15" } } ``` List endpoints include pagination metadata: ```json theme={null} { "items": [...], "limit": 50, "offset": 0, "page": 0, "totalRecords": 150, "totalPages": 3, "hasMore": true } ``` ### Error Response Errors return a standardized format: ```json theme={null} { "result": "error", "error": { "type": "ValidationError", "message": "Title is required" } } ``` ## HTTP Status Codes | Code | Meaning | | ----- | ---------------------------------------- | | `200` | Success | | `201` | Created | | `400` | Bad Request - Invalid parameters | | `401` | Unauthorized - Invalid or missing token | | `403` | Forbidden - Insufficient permissions | | `404` | Not Found - Resource doesn't exist | | `422` | Unprocessable Entity - Validation failed | | `500` | Internal Server Error | ## Pagination List endpoints support pagination using `_limit` and `_offset`: ```javascript theme={null} // Get 50 items starting from offset 50 (second page) const response = await fetch( `${API_URL}/items?_limit=50&_offset=50`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } } ); const data = await response.json(); console.log(`Page ${data.page + 1} of ${data.totalPages}`); console.log(`Showing ${data.items.length} of ${data.totalRecords} total items`); console.log(`Has more: ${data.hasMore}`); ``` ## Expanding Related Data Use `_expand` to include related data in a single request: ```bash theme={null} # Get item with related data GET /items/42?_expand[]=collection&_expand[]=series&_expand[]=speakers&_expand[]=mediaAssets ``` **Available expansions:** * Items: `collection`, `series`, `speakers`, `mediaAssets`, `scriptures` * Series: `collection`, `items` * Collections: `items`, `series` ## Filtering Most list endpoints support filtering by fields: ```bash theme={null} # Filter items by multiple criteria GET /items?collectionId=1&seriesId=5&status=published # Using filter operators GET /items?date.gt=2025-01-01&status.ne=draft ``` **Available filter operators:** * No operator - Exact match (e.g., `collectionId=1`) * `.eq` - Equal to (e.g., `status.eq=published`) * `.ne` - Not equal to (e.g., `status.ne=draft`) * `.in` - In array (e.g., `id.in=1,2,3`) * `.lt` - Less than (e.g., `date.lt=2025-01-01`) * `.le` - Less than or equal * `.gt` - Greater than * `.ge` - Greater than or equal * `.bt` - Between two values (e.g., `date.bt=2025-01-01,2025-12-31`) * `.nl` - Is null (e.g., `seriesId.nl=true`) * `.like` - Pattern matching (e.g., `title.like=%prayer%`) ## Sorting Sort results using `_orderBy`: ```bash theme={null} # Get newest items first GET /items?_orderBy[]=date,DESC # Get topics by display order GET /topics?_orderBy[]=displayOrder,ASC # Multiple sort fields GET /items?_orderBy[]=date,DESC&_orderBy[]=title,ASC ``` # Authentication Source: https://developer.thehaystack.ai/authentication Learn how to authenticate your API requests to Haystack ## Overview The Haystack API uses **Bearer token authentication** to secure API requests. Most API endpoints require a valid authentication token in the `Authorization` header. **Search Endpoint Exception**: The [Search endpoint](/api-reference/endpoints/search) does NOT require authentication. It is designed to be called from public-facing websites and applications. **Never include your API token** when calling the search endpoint, as this would expose your credentials to end users. ## Two Types of API Access Haystack provides two separate APIs for different use cases: ### Search API (Public, No Authentication) * **Purpose**: Search your content library from anywhere, including public websites * **Authentication**: None required * **Base URL**: `https://{your-church-shortname}.thehaystack.ai/api` * **Use from**: Frontend JavaScript, mobile apps, any public-facing application ### Private API (Authenticated) * **Purpose**: Manage your library, access analytics, update settings * **Authentication**: API key required (Bearer token) * **Base URL**: `https://api.thehaystack.ai/v2/haystack` * **Use from**: Backend servers only (never from frontend code) ## Getting Your Search API Base URL The Search API base URL is specific to your organization: 1. Log in to your [Haystack Dashboard](https://app.thehaystack.ai) 2. Navigate to **Developer** → **API** 3. Find your **Search API Base URL** in the Search API section 4. Click **Copy** to copy the URL to your clipboard Your Search API base URL will look like: ``` https://your-church-name.thehaystack.ai/api ``` The Search API does not require authentication. You can use it directly from frontend JavaScript without exposing any credentials. ## Getting Your Private API Token To create a Private API token for authenticated requests: 1. Log in to your [Haystack Dashboard](https://app.thehaystack.ai) 2. Navigate to **Developer** → **API** 3. In the **Private API** section, click **New key** 4. Enter a descriptive name for your key (e.g., "Production Server") 5. Choose whether to enable **Admin privileges** (only if needed for user/billing management) 6. Click **Submit** 7. **Important**: Copy your API key immediately - you won't be able to see it again! **Keep Your API Key Private**: This key provides access to your Haystack account. Never share it, expose it in frontend code, or commit it to version control. Store it securely using environment variables or a secrets manager. ## Making Authenticated Requests (Private API) For Private API endpoints, include your API token in the `Authorization` header using the Bearer authentication scheme: ```bash theme={null} Authorization: Bearer YOUR_API_TOKEN ``` ### Example Private API Request ```bash cURL theme={null} curl https://api.thehaystack.ai/v2/haystack/items \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" ``` ```javascript JavaScript theme={null} const API_URL = 'https://api.thehaystack.ai/v2/haystack'; const API_TOKEN = process.env.HAYSTACK_API_TOKEN; const response = await fetch(`${API_URL}/items`, { headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' } }); const data = await response.json(); ``` ```python Python theme={null} import os import requests API_URL = 'https://api.thehaystack.ai/v2/haystack' API_TOKEN = os.getenv('HAYSTACK_API_TOKEN') headers = { 'Authorization': f'Bearer {API_TOKEN}', 'Content-Type': 'application/json' } response = requests.get(f'{API_URL}/items', headers=headers) data = response.json() ``` ```typescript TypeScript theme={null} const API_URL = 'https://api.thehaystack.ai/v2/haystack'; const API_TOKEN = process.env.HAYSTACK_API_TOKEN; const response = await fetch(`${API_URL}/items`, { headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' } }); const data = await response.json(); ``` ### Example Search API Request (No Authentication) ```bash cURL theme={null} curl "https://your-church-name.thehaystack.ai/api/haystack/search?q=prayer" ``` ```javascript JavaScript theme={null} const SEARCH_API_URL = 'https://your-church-name.thehaystack.ai/api'; const response = await fetch( `${SEARCH_API_URL}/haystack/search?q=${encodeURIComponent('prayer')}`, { headers: { 'Content-Type': 'application/json' } } ); const data = await response.json(); ``` ```python Python theme={null} SEARCH_API_URL = 'https://your-church-name.thehaystack.ai/api' params = {'q': 'prayer'} response = requests.get(f'{SEARCH_API_URL}/haystack/search', params=params) data = response.json() ``` ```typescript TypeScript theme={null} const SEARCH_API_URL = 'https://your-church-name.thehaystack.ai/api'; const response = await fetch( `${SEARCH_API_URL}/haystack/search?q=${encodeURIComponent('prayer')}`, { headers: { 'Content-Type': 'application/json' } } ); const data = await response.json(); ``` ## Which API Should I Use? Choose the appropriate API based on where your code runs: | Scenario | API to Use | Base URL | Authentication | | ----------------------------- | ----------- | --------------------------------------------- | ---------------- | | Search from website | Search API | `https://{your-shortname}.thehaystack.ai/api` | None | | Search from mobile app | Search API | `https://{your-shortname}.thehaystack.ai/api` | None | | Manage content from backend | Private API | `https://api.thehaystack.ai/v2/haystack` | API Key required | | Upload media from backend | Private API | `https://api.thehaystack.ai/v2/haystack` | API Key required | | Access analytics from backend | Private API | `https://api.thehaystack.ai/v2/haystack` | API Key required | **Never use Private API keys in frontend code**. API keys should only be used from backend servers where they can be kept secure. For public-facing search functionality, use the Search API which requires no authentication. ## Authentication Errors The API returns specific error responses for authentication issues: ### 401 Unauthorized Returned when your API token is missing, invalid, or expired. ```json theme={null} { "result": "error", "error": { "type": "Unauthorized", "message": "Invalid or missing authentication token" } } ``` **Resolution**: Verify your token is correct and included in the `Authorization` header. ### 403 Forbidden Returned when your token is valid but you don't have permission to access the requested resource. ```json theme={null} { "result": "error", "error": { "type": "Forbidden", "message": "Insufficient permissions to access this resource" } } ``` **Resolution**: Ensure your API token has the necessary permissions for the resource you're trying to access. ## Next Steps Now that you understand authentication, you're ready to make your first API call: Follow our quickstart tutorial to make your first authenticated API request # Content Model Source: https://developer.thehaystack.ai/concepts/content-model Understanding how content is organized in Haystack ## Overview Haystack uses a hierarchical content model that helps you organize and categorize your media library. Understanding these relationships is key to effectively managing your content. ## Core Entities ### Items **Items** are the fundamental unit of content in Haystack. An item typically represents a single piece of content like a sermon, talk, podcast episode, or video. ```json theme={null} { "id": 42, "title": "Leading with Humility", "subTitle": "Part 2 of Leadership Series", "description": "A deep dive into servant leadership...", "date": "2025-01-15", "status": "published", "collectionId": 1, "seriesId": 5, "urlSlug": "leading-with-humility" } ``` **Key Properties:** * `title` - The main title of the content * `date` - Publication or recording date * `status` - Current state: `draft`, `queued`, `processing`, `ready`, `published`, `unpublished`, or `error` * `collectionId` - Required: which collection this belongs to * `seriesId` - Optional: which series this belongs to **Item Lifecycle:** Item is created but not yet published Item is queued for processing Media is being transcribed and indexed for search Processing complete, ready to be published Item is live and searchable by users Item was published but is now hidden from search Processing failed - check error details ### Collections **Collections** are top-level organizational containers. They represent major categories of content and every item must belong to exactly one collection. ```json theme={null} { "id": 1, "name": "Sunday Sermons", "itemDescriptor": "sermon" } ``` **Use Cases:** * Organizing by content type (Sermons, Podcasts, Conferences) * Separating by venue (Main Campus, Satellite Location) * Dividing by audience (Youth, Adults, Spanish) Collections are required for all items. Plan your collection structure before importing content. ### Series **Series** group related items together in a sequence. Series are optional but highly recommended for organizing multi-part content. ```json theme={null} { "id": 5, "title": "Leadership Principles", "description": "A 4-week series on biblical leadership", "collectionId": 1, "published": true, "colorHex": "#4A90E2", "itemSortDirection": "DESC", "showItemOrderInSeries": true } ``` **Key Features:** * **Ordering**: Control whether items display in ascending or descending order * **Numbering**: Optionally show "Part 1", "Part 2" labels * **Branding**: Each series can have custom artwork and colors * **Discovery**: Users can find related content by browsing series ### Speakers **Speakers** represent the people who deliver your content. Items can have multiple speakers. ```json theme={null} { "id": 10, "name": "Pastor John Smith", "bio": "Senior Pastor since 2015...", "imageFilename": "john-smith-headshot.jpg" } ``` **Relationships:** * Items must have at least one speaker * Items can have multiple speakers * Speakers can be associated with many items * Speaker filtering enables users to find all content by a specific person ## Content Hierarchy Here's how these entities relate to each other: ``` Customer └── Collections (one or more) ├── Series (optional, multiple allowed) │ └── Items (optional, multiple allowed) └── Items (one or more) ├── Speakers (required, at least one) ├── Media Assets (optional, multiple allowed) ├── Scriptures (optional, multiple allowed) └── Artwork (optional, multiple allowed) ``` ## Relationships Explained ### Required Relationships Every item must belong to exactly one collection. This cannot be null. ```javascript theme={null} // ✅ Valid - has collectionId { "title": "My Sermon", "date": "2025-01-15", "collectionId": 1 } // ❌ Invalid - missing collectionId { "title": "My Sermon", "date": "2025-01-15" } ``` Every series must belong to exactly one collection. ```javascript theme={null} { "title": "Leadership Series", "collectionId": 1 // Required } ``` Every item must have at least one speaker. Items can have multiple speakers. ```javascript theme={null} // ✅ Valid - has at least one speaker { "title": "My Sermon", "date": "2025-01-15", "collectionId": 1, "speakers": [{ "id": 10 }] } // ✅ Valid - multiple speakers { "title": "Panel Discussion", "date": "2025-01-15", "collectionId": 1, "speakers": [{ "id": 10 }, { "id": 15 }] } // ❌ Invalid - no speakers { "title": "My Sermon", "date": "2025-01-15", "collectionId": 1, "speakers": [] } ``` ### Optional Relationships Items can optionally belong to a series, but it's not required. ```javascript theme={null} // Standalone item (no series) { "title": "Easter Special", "collectionId": 1, "seriesId": null } // Part of a series { "title": "Leadership Part 1", "collectionId": 1, "seriesId": 5 } ``` ## Working with the Content Model ### Creating a Complete Item Here's a comprehensive example of creating an item with all relationships: ```javascript theme={null} const newItem = { // Basic info title: "Leading with Humility", subTitle: "Part 2 of Leadership Series", description: "A deep dive into servant leadership principles", shortDescription: "Learn to lead like Jesus", date: "2025-01-15", // Required relationships collectionId: 1, speakers: [ { id: 10 } // Pastor John ], // Optional relationships seriesId: 5, // Auto-publishing autoPublish: false // Keep as draft, publish manually when ready }; const response = await fetch(`${API_URL}/items/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(newItem) }); ``` ### Querying with Related Data Use the `_expand` query parameter to include related data: ```bash theme={null} # Get item with all related data curl "$API_URL/items/42?_expand[]=collection&_expand[]=series&_expand[]=speakers" \ -H "Authorization: Bearer $API_TOKEN" ``` Response includes nested objects: ```json theme={null} { "item": { "id": 42, "title": "Leading with Humility", "collection": { "id": 1, "name": "Sermons" }, "series": { "id": 5, "title": "Leadership Principles" }, "speakers": [ { "id": 10, "name": "Pastor John Smith" } ] } } ``` ## Content Organization Best Practices Design your collection structure before importing content. Collections are the foundation of your organization. Group related content into series for better discovery and user engagement. Maintain consistent speaker naming to avoid duplicates (e.g., don't create both "John Smith" and "Pastor John"). Use descriptions and subtitles to provide context and improve searchability. ## URL Slugs Items and series can have custom URL slugs for user-friendly URLs: ```json theme={null} { "title": "Leading with Humility", "urlSlug": "leading-with-humility" } ``` **Slug Rules:** * Lowercase letters, numbers, and hyphens only * Must be unique within your customer account * Auto-generated from title if not provided ## Next Steps Learn how to attach media assets to your items Step-by-step guide to creating and organizing content # Media Management Source: https://developer.thehaystack.ai/concepts/media-management How Haystack handles video and audio assets ## Overview Haystack provides comprehensive media management capabilities including automatic transcription, multi-format encoding, chapter detection, and CDN delivery. Understanding the media pipeline helps you efficiently manage your content library. ## Video Hosting Options Churches can choose how they want to host and play their video content: Use Haystack's custom video player powered by Mux Embed YouTube player in your site Embed Vimeo player in your site **Important:** Regardless of which player you choose, you must upload the original video or audio file to Haystack for AI processing. Even if you host videos on YouTube or Vimeo, Haystack needs the source file for transcription, search indexing, and other AI features. ### Why Upload to Haystack? When you upload media to Haystack (even if hosting elsewhere), we: * **Transcribe** the audio with word-level timestamps * **Index** the content for semantic search * **Extract** topics, speakers, and key moments * **Generate** searchable chapters * **Enable** "jump to moment" functionality The original file stays on YouTube/Vimeo for playback, while Haystack provides the AI-powered search and discovery features. ## Media Assets **Media Assets** represent video or audio files attached to items. Each item can have multiple media assets in different formats or qualities. ```json theme={null} { "id": 100, "itemId": 42, "contentType": "video", "status": "ready", "duration": 2847.5, "muxAssetId": "abc123", "muxPlaybackId": "def456" } ``` ### Content Types Haystack supports two primary content types: Full video content with audio, visual, and subtitle tracks Audio-only content for podcasts and audio messages ### Media Status Lifecycle Media asset record created, awaiting file upload File uploaded, awaiting processing Transcoding to multiple formats and bitrates Available for playback and searching Processing failed - check error details ## Creating Media Assets To upload a video or audio file to Haystack, use a two-step process: ### Step 1: Create Media Asset and Get Upload URL First, create a media asset to get a secure upload URL: ```javascript theme={null} const response = await fetch(`${API_URL}/media/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ itemId: 42, // Optional: If using YouTube/Vimeo for playback externalUrl: 'https://www.youtube.com/watch?v=abc123', externalPlatform: 'youtube' // or 'vimeo' }) }); const { mediaAsset, uploadUrl } = await response.json(); console.log('Upload URL:', uploadUrl); ``` **Parameters:** * `itemId` (required) - The item this media belongs to * `externalUrl` (optional) - YouTube or Vimeo URL for playback only * `externalPlatform` (optional) - `'youtube'` or `'vimeo'` when using external playback **External URLs are for playback only.** Even if you provide a YouTube or Vimeo URL, you must still upload the original video file to Haystack in Step 2 for AI processing (transcription, search, etc.). ### Step 2: Upload File to Upload URL Use the returned `uploadUrl` to upload your video or audio file: ```javascript theme={null} // Upload the file using the upload URL const uploadResponse = await fetch(uploadUrl, { method: 'PUT', headers: { 'Content-Type': 'video/mp4', // or appropriate content type }, body: videoFile // File object or buffer }); if (uploadResponse.ok) { console.log('Upload complete!'); console.log('Media asset ID:', mediaAsset.id); } ``` **File size limit:** 5GB maximum. Files larger than 5GB cannot be uploaded. **Supported formats:** * Video: MP4, MOV, AVI, MKV, WebM * Audio: MP3, WAV, AAC, M4A, FLAC ## Automatic Transcription When media is uploaded, Haystack automatically: 1. **Extracts audio** from video files 2. **Transcribes speech** using AI speech recognition 3. **Timestamps every word** for precise searching 4. **Indexes content** for semantic search ### Transcription Quality Transcription accuracy depends on: * Audio quality (higher quality = better accuracy) * Speaker clarity and accent * Background noise levels * Language (English is best supported) For best transcription results, use high-quality audio recordings with minimal background noise. ## Media Chapters **Chapters** divide media into logical sections with titles and timestamps. They improve navigation and user experience. ```json theme={null} { "id": 50, "mediaAssetId": 100, "title": "Introduction", "startTimeSeconds": 0, "sortOrder": 1 } ``` ### Auto-Generated Chapters Haystack can automatically detect chapter boundaries based on: * Transcript analysis * Silence detection * Scene changes (for video) ### Manual Chapters Create chapters manually for precise control: ```javascript theme={null} const chapters = [ { title: "Introduction", startMs: 0 }, { title: "Main Teaching", startMs: 180000 }, { title: "Q&A", startMs: 1200000 }, { title: "Closing Prayer", startMs: 2400000 } ]; // Create chapters individually for (const chapter of chapters) { await fetch(`${API_URL}/media/chapters`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ itemId: 42, mediaAssetId: 100, title: chapter.title, startMs: chapter.startMs, suggested: false, accepted: true }) }); } ``` ## Media Variants **Variants** allow you to offer different versions of content for the same item. Each collection has predefined variant types that serve different purposes. ### Common Variant Types * **Sermon Video** - The main sermon message (video) * **Sermon Audio** - Audio-only version of the sermon * **Full Service** - Complete worship service including music, announcements, etc. (video) * **Podcast** - Edited audio version optimized for podcast distribution ### How Variants Work Each item can have multiple media assets, each assigned to a different variant type: ```json theme={null} { "item": { "id": 42, "title": "Leading with Humility", "mediaAssets": [ { "id": 100, "variantType": { "id": 1, "name": "Sermon Video", "contentType": "video", "indexable": true } }, { "id": 101, "variantType": { "id": 2, "name": "Sermon Audio", "contentType": "audio", "indexable": true } }, { "id": 102, "variantType": { "id": 3, "name": "Full Service", "contentType": "video", "indexable": false } } ] } } ``` ### Variant Properties * **name** - Descriptive name (e.g., "Sermon Video", "Podcast") * **contentType** - Either "video" or "audio" * **indexable** - Whether this variant can be used for search/transcription * **displayOrder** - Order shown to users when selecting variants * **collectionId** - Variant types are tied to specific collections ### Indexing and Search Only **one** media asset per item is indexed for search and transcription: * If multiple variants are marked as `indexable`, video variants are preferred over audio * The indexed variant is used for transcription, search, and AI features * All other variants are available for playback but not searchable Variant types are predefined per collection and cannot be customized by API consumers. Each collection includes video and audio variant types by default. ## Playback URLs Haystack stores the Mux playback ID, which you use to construct playback URLs: ```json theme={null} { "mediaAsset": { "id": 100, "muxPlaybackId": "abc123def456" } } ``` Construct the HLS playback URL using the `muxPlaybackId`: ```javascript theme={null} const playbackUrl = `https://stream.mux.com/${mediaAsset.muxPlaybackId}.m3u8`; ``` ### HLS Streaming All media is delivered via **HLS (HTTP Live Streaming)** for: * Adaptive bitrate streaming * Fast startup times * Reliable playback across devices ### Thumbnails **For Haystack Custom Player:** When using the Haystack custom player, thumbnails are automatically generated at regular intervals via Mux: ```javascript theme={null} // Get thumbnail at 30 seconds const thumbnailUrl = `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?time=30`; // Get thumbnail at specific width const thumbnail = `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?width=640&time=120`; ``` **For YouTube/Vimeo Players:** When using YouTube or Vimeo for playback, Mux thumbnails are not available. Use the artwork files you uploaded to your items instead. Thumbnail availability depends on your chosen video player. Mux-generated thumbnails are only available when using the Haystack custom player. ## API Reference Key endpoints for media management: * `POST /media/create` - Create media asset and get upload URL * `GET /media/{id}` - Get media asset details * `DELETE /media/{id}` - Delete media asset * `POST /media/chapters` - Create a chapter * `PATCH /media/chapters/{id}` - Update a chapter * `DELETE /media/chapters/{id}` - Delete a chapter ## Next Steps Learn how transcribed content powers semantic search Complete guide to uploading and managing media # Search & Discovery Source: https://developer.thehaystack.ai/concepts/search-discovery How Haystack's semantic search works ## Overview Haystack provides powerful semantic search that goes beyond simple keyword matching. Users can search using natural language questions, and Haystack understands meaning and context to return the most relevant results. ## Search API ### Basic Search **No Authentication Required**: The search endpoint uses your church-specific Search API URL and does not require authentication. Never include your API token when calling search from public-facing applications. ```javascript theme={null} // Your church-specific Search API URL const SEARCH_URL = 'https://your-church-name.thehaystack.ai/api'; const query = "how to pray"; const response = await fetch( `${SEARCH_URL}/haystack/search?q=${encodeURIComponent(query)}`, { headers: { 'Content-Type': 'application/json' } } ); const results = await response.json(); ``` Find your Search API Base URL in the [Haystack Dashboard](https://app.thehaystack.ai) under **Developer** → **API**. **Advanced Feature**: Add `?stream=true` to receive a Server-Sent Events stream with an AI-generated overview of search results. Perfect for creating rich search experiences with AI summaries. [Learn more](/api-reference/endpoints/search#streaming-response-with-ai-overview-advanced) ### Search Response ```json theme={null} { "query": "how to pray", "queryAnalyticsId": "abc123", "items": [ { "item": { "id": 42, "title": "The Power of Prayer", "date": "2025-01-15" }, "score": 0.89, "highlights": [ { "transcript": "So let me share three practical ways to deepen your prayer life...", "startMs": 145000, "endMs": 152000, "thumbnailUrl": "https://image.mux.com/.../thumbnail.jpg?time=145" } ] } ], "scriptures": [ { "book": "matthew", "bookName": "Matthew", "chapter": 6, "numItems": 12 } ], "series": [ { "series": { "id": 5, "title": "Spiritual Disciplines" }, "numItems": 4, "linkedItemUrlSlug": "power-of-prayer" } ] } ``` ## Search Result Types Haystack returns three types of results: ### Item Results Individual content items with highlights showing relevant sections. ```json theme={null} { "item": { /* item details */ }, "score": 0.85, "highlights": [ { "transcript": "exact text from transcript", "startMs": 120000, "endMs": 127000, "score": 0.89 } ] } ``` **Key Fields:** * `score` - Overall relevance (0-1, higher is better) * `highlights` - Most relevant segments with timestamps * `thumbnailUrl` - Visual preview of the segment (only available when using Haystack custom player; use item artwork for YouTube/Vimeo) ### Scripture Results Bible passages related to the search query. ```json theme={null} { "book": "matthew", "bookName": "Matthew", "chapter": 6, "numItems": 12 } ``` Haystack automatically: * Detects biblical references in queries ("Matthew 6") * Finds thematic scripture connections ("forgiveness" → Matthew 6:14-15) * Shows how many items reference each passage ### Series Results Entire series related to the query. ```json theme={null} { "series": { "id": 5, "title": "Spiritual Disciplines", "description": "A 4-week series on prayer, fasting, and worship" }, "numItems": 4, "linkedItemUrlSlug": "power-of-prayer" } ``` Useful when users want comprehensive content on a topic. ## Search Features ### Semantic Understanding Haystack understands meaning, not just keywords: | Query | Matches | | ---------------------- | -------------------------------------------------------------- | | "how to pray" | "prayer life", "talking to God", "spending time with the Lord" | | "dealing with anxiety" | "worry", "fear", "stress", "peace" | | "relationships" | "marriage", "friendship", "dating", "family" | ### Highlights Each result includes 1-3 highlights showing the most relevant sections: ```json theme={null} { "highlights": [ { "transcript": "The actual words spoken in this segment...", "startMs": 145000, // 2:25 into the video "endMs": 152000, // 2:32 "score": 0.89, "thumbnailUrl": "https://..." } ] } ``` Use highlights to: * Show preview text in search results * Enable "jump to moment" functionality * Display thumbnail previews ### Scripture Detection Haystack recognizes Bible references in multiple formats: * Book and chapter: "John 3" * Full reference: "John 3:16" * Natural language: "when Jesus talked to Nicodemus" * Alternative names: "1st John", "First John", "1 John" ## Best Practices Log every search query to understand user intent and identify gaps in your content Use thumbnail URLs from highlights to give users visual context Let users jump directly to relevant timestamps instead of watching from the beginning Track searches that return no results to identify missing content topics ## Next Steps Full search API documentation # Managing Content Source: https://developer.thehaystack.ai/guides/managing-content Complete guide to creating and organizing content in Haystack ## Overview This guide walks you through the complete workflow of managing content in Haystack, from creating your organizational structure to publishing items. ## Prerequisites Before you begin, ensure you have: * A Haystack account with API access * Your API token (see [Authentication](/authentication)) * At least one collection created ## Step 1: Set Up Your Organization ### Create Collections Collections are the top-level categories for your content. Create them first: ```javascript theme={null} const collections = [ { name: 'Sunday Sermons' }, { name: 'Midweek Studies' }, { name: 'Podcasts' } ]; for (const collection of collections) { const response = await fetch(`${API_URL}/collections/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(collection) }); const { collection: created } = await response.json(); console.log(`Created collection: ${created.name} (ID: ${created.id})`); } ``` ### Create Speakers Add the people who deliver your content: ```javascript theme={null} const speakers = [ { name: 'Pastor John Smith', bio: 'Senior Pastor since 2015, passionate about biblical teaching and discipleship.' }, { name: 'Sarah Johnson', bio: 'Youth Pastor and conference speaker focusing on student ministry.' } ]; for (const speaker of speakers) { const response = await fetch(`${API_URL}/speakers/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(speaker) }); const { speaker: created } = await response.json(); console.log(`Created speaker: ${created.name} (ID: ${created.id})`); } ``` ### Create Topics Define the topics/tags you'll use to categorize content: ```javascript theme={null} const topics = [ { name: 'Prayer', displayOrder: 1 }, { name: 'Faith', displayOrder: 2 }, { name: 'Leadership', displayOrder: 3 }, { name: 'Relationships', displayOrder: 4 }, { name: 'Worship', displayOrder: 5 } ]; for (const topic of topics) { const response = await fetch(`${API_URL}/topics/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(topic) }); const { topic: created } = await response.json(); console.log(`Created topic: ${created.name} (ID: ${created.id})`); } ``` ## Step 2: Create a Series (Optional) For multi-part content, create a series: ```javascript theme={null} const seriesData = { title: 'Spiritual Disciplines', subTitle: 'A 4-Week Journey', description: 'Explore the foundational practices of prayer, fasting, worship, and scripture study that deepen your relationship with God.', shortDescription: 'Master the spiritual disciplines', collectionId: 1, // Sunday Sermons colorHex: '#4A90E2', itemSortDirection: 'ASC', // Show oldest first showItemOrderInSeries: true, // Show "Part 1", "Part 2", etc. published: true }; const response = await fetch(`${API_URL}/series/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(seriesData) }); const { series } = await response.json(); console.log(`Created series: ${series.title} (ID: ${series.id})`); ``` ### Upload Series Artwork Add visual branding to your series: ```javascript theme={null} const formData = new FormData(); formData.append('file', seriesArtworkFile); // Your image file formData.append('artworkType', 'square'); // or 'wide' const response = await fetch( `${API_URL}/series/${series.id}/artwork/upload`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}` }, body: formData } ); ``` **Artwork requirements:** * Square: 1:1 aspect ratio, minimum 1000x1000px * Wide: 16:9 aspect ratio, minimum 1920x1080px * Max file size: 15MB * Formats: JPG, PNG ## Step 3: Create an Item Now create your first content item: ```javascript theme={null} const itemData = { // Basic information title: 'The Power of Prayer', subTitle: 'Part 1 of Spiritual Disciplines', description: 'Discover how prayer transforms not just our circumstances, but our hearts. Learn practical ways to develop a consistent prayer life and experience deeper intimacy with God.', shortDescription: 'Transform your prayer life', date: '2025-01-15', // Required: Collection collectionId: 1, // Optional: Series seriesId: series.id, // Optional: Speakers (array of IDs) speakers: [ { id: 1 } // Pastor John Smith ], // Optional: Topics (array of IDs) topics: [ { id: 1 }, // Prayer { id: 2 } // Faith ], // Publishing (initially draft) published: false }; const response = await fetch(`${API_URL}/items/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(itemData) }); const { item } = await response.json(); console.log(`Created item: ${item.title} (ID: ${item.id})`); ``` ## Step 4: Add Item Artwork Upload thumbnail images for the item: ```javascript theme={null} // Upload square artwork (for grids) const squareFormData = new FormData(); squareFormData.append('file', squareArtworkFile); squareFormData.append('artworkType', 'square'); await fetch(`${API_URL}/items/${item.id}/artwork/upload`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}` }, body: squareFormData }); // Upload wide artwork (for hero images) const wideFormData = new FormData(); wideFormData.append('file', wideArtworkFile); wideFormData.append('artworkType', 'wide'); await fetch(`${API_URL}/items/${item.id}/artwork/upload`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}` }, body: wideFormData }); console.log('Artwork uploaded successfully'); ``` ## Step 5: Add Scriptures (Optional) Link Bible passages to your item: ```javascript theme={null} const scriptures = [ { itemId: item.id, book: 'matthew', chapter: 6, verseStart: 9, verseEnd: 13 }, { itemId: item.id, book: '1_thessalonians', chapter: 5, verseStart: 17, verseEnd: 17 } ]; for (const scripture of scriptures) { await fetch(`${API_URL}/scriptures/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(scripture) }); } console.log('Scriptures added'); ``` ## Step 6: Publish the Item Once everything is ready, publish the item: ```javascript theme={null} const response = await fetch(`${API_URL}/items/${item.id}/publish`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}` } }); const { item: publishedItem } = await response.json(); console.log(`Published: ${publishedItem.title}`); ``` Items must have at least one media asset attached before they can be published. See the [Working with Media](/guides/working-with-media) guide to learn how to upload media. ## Updating Content ### Update Item Metadata Modify an existing item: ```javascript theme={null} const updates = { title: 'The Transforming Power of Prayer', // Updated title description: 'Updated description...', topics: [ { id: 1 }, // Prayer { id: 5 } // Worship (added) ] }; const response = await fetch(`${API_URL}/items/${item.id}`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }); const { item: updatedItem } = await response.json(); ``` ### Unpublish an Item Take an item offline: ```javascript theme={null} await fetch(`${API_URL}/items/${item.id}/unpublish`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}` } }); ``` ## Bulk Operations ### Creating Multiple Items When importing a library, create items in batches: ```javascript theme={null} const items = [ { title: 'Item 1', date: '2025-01-01', collectionId: 1 }, { title: 'Item 2', date: '2025-01-08', collectionId: 1 }, { title: 'Item 3', date: '2025-01-15', collectionId: 1 } ]; // Create items sequentially for (const itemData of items) { const response = await fetch(`${API_URL}/items/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(itemData) }); const { item } = await response.json(); console.log(`Created: ${item.title} (ID: ${item.id})`); // Small delay to avoid rate limits await new Promise(resolve => setTimeout(resolve, 100)); } ``` For large bulk imports (>100 items), consider batching your requests and implementing retry logic for failed imports. ## Querying Content ### List Items Retrieve items with filtering and pagination: ```javascript theme={null} // Get published items from a specific series const response = await fetch( `${API_URL}/items?seriesId=5&published=true&sortBy=date&sortDirection=DESC&page=1&pageSize=20`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } } ); const data = await response.json(); console.log(`Found ${data.total} items`); console.log(`Showing page ${data.page} of ${Math.ceil(data.total / data.pageSize)}`); data.items.forEach(item => { console.log(`- ${item.title} (${item.date})`); }); ``` ### Get Item with Related Data Fetch an item with all related data: ```javascript theme={null} const response = await fetch( `${API_URL}/items/42?_expand[]=collection&_expand[]=series&_expand[]=speakers&_expand[]=mediaAssets`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } } ); const { item } = await response.json(); // Now item includes nested objects console.log(item.collection.name); // "Sunday Sermons" console.log(item.series.title); // "Spiritual Disciplines" console.log(item.speakers[0].name); // "Pastor John Smith" ``` ## Common Patterns ### Checking if Item Exists ```javascript theme={null} async function itemExists(title, date, collectionId) { const response = await fetch( `${API_URL}/items?title=${encodeURIComponent(title)}&date=${date}&collectionId=${collectionId}`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } } ); const data = await response.json(); return data.items.length > 0; } ``` ### Finding Items by Speaker ```javascript theme={null} async function getItemsBySpeaker(speakerId) { const response = await fetch( `${API_URL}/items?speakerId=${speakerId}&_expand[]=speakers`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } } ); const data = await response.json(); return data.items; } ``` ### Getting Latest Items ```javascript theme={null} async function getLatestItems(limit = 10) { const response = await fetch( `${API_URL}/items?published=true&sortBy=date&sortDirection=DESC&pageSize=${limit}`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } } ); const data = await response.json(); return data.items; } ``` ## Best Practices Design your collections before importing content. Changing an item's collection later requires updating the item, which can affect analytics. Maintain consistent naming conventions for speakers and topics to avoid duplicates. * ✅ "John Smith" (everywhere) * ❌ "John Smith", "Pastor John", "Rev. Smith" (creates duplicates) Create items as drafts, add all metadata and media, then publish when complete. Group related items into series for better discovery and user engagement. Rich metadata (descriptions, topics, scriptures) improves search quality and user experience. ## Error Handling Always handle errors gracefully: ```javascript theme={null} async function createItemSafely(itemData) { try { const response = await fetch(`${API_URL}/items/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(itemData) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error.message); } const { item } = await response.json(); return item; } catch (error) { console.error('Failed to create item:', error.message); throw error; } } ``` ## Next Steps Learn how to upload and manage media for your items Explore the full API documentation # Working with Media Source: https://developer.thehaystack.ai/guides/working-with-media Upload and manage video and audio content ## Overview This guide covers the complete workflow for uploading media, monitoring processing, adding chapters, and managing media assets. **Important for YouTube/Vimeo Users:** Even if you're hosting videos on YouTube or Vimeo for playback, you must still upload the original video file to Haystack. This allows Haystack to provide AI-powered features like transcription, semantic search, and chapter generation. Your embedded player will continue using YouTube/Vimeo, while Haystack provides the search and discovery layer. ## Prerequisites * An item created (see [Managing Content](/guides/managing-content)) * Media file or URL ready to upload * API token for authentication ## Upload Process Uploading media to Haystack is a two-step process: ### Step 1: Create Media Asset First, create a media asset record and receive an upload URL: ```javascript theme={null} async function createMediaAsset(itemId, externalConfig = null) { const payload = { itemId }; // Optional: If using YouTube/Vimeo for playback if (externalConfig) { payload.externalUrl = externalConfig.url; payload.externalPlatform = externalConfig.platform; // 'youtube' or 'vimeo' } const response = await fetch(`${API_URL}/media/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const { mediaAsset, uploadUrl } = await response.json(); return { mediaAsset, uploadUrl }; } // Example 1: Using Haystack player const { mediaAsset, uploadUrl } = await createMediaAsset(42); // Example 2: Using YouTube for playback const { mediaAsset, uploadUrl } = await createMediaAsset(42, { url: 'https://www.youtube.com/watch?v=abc123', platform: 'youtube' }); // Example 3: Using Vimeo for playback const { mediaAsset, uploadUrl } = await createMediaAsset(42, { url: 'https://vimeo.com/123456789', platform: 'vimeo' }); ``` **External URLs for playback only:** If you provide a YouTube or Vimeo URL, it will be used for embedded playback in your application. However, you **must still upload the original video file** in Step 2 for AI processing. ### Step 2: Upload File Use the returned `uploadUrl` to upload your video or audio file: ```javascript theme={null} async function uploadFile(uploadUrl, file) { const response = await fetch(uploadUrl, { method: 'PUT', headers: { 'Content-Type': file.type || 'video/mp4' }, body: file }); if (!response.ok) { throw new Error('Upload failed'); } return response; } // Usage const videoFile = document.getElementById('fileInput').files[0]; await uploadFile(uploadUrl, videoFile); console.log('Upload complete! Processing will begin automatically.'); ``` **File size limit:** Maximum file size is 5GB. There is no way to upload files larger than 5GB. **Supported formats:** * Video: MP4, MOV, AVI, MKV, WebM * Audio: MP3, WAV, AAC, M4A, FLAC ### Complete Upload Function Here's a complete helper function that handles both steps: ```javascript theme={null} async function uploadMediaToHaystack(itemId, file, externalConfig = null) { try { // Step 1: Create media asset and get upload URL console.log('Creating media asset...'); const { mediaAsset, uploadUrl } = await createMediaAsset(itemId, externalConfig); // Step 2: Upload the file console.log('Uploading file...'); await uploadFile(uploadUrl, file); console.log('Upload complete! Media asset ID:', mediaAsset.id); return mediaAsset; } catch (error) { console.error('Upload failed:', error); throw error; } } // Usage examples: // With Haystack player const videoFile = document.getElementById('fileInput').files[0]; await uploadMediaToHaystack(42, videoFile); // With YouTube player (must still upload the file!) await uploadMediaToHaystack(42, videoFile, { url: 'https://www.youtube.com/watch?v=abc123', platform: 'youtube' }); ``` ## Monitoring Processing Status ### Polling Method Check processing status periodically: ```javascript theme={null} async function waitForProcessing(mediaAssetId, maxAttempts = 60) { for (let i = 0; i < maxAttempts; i++) { const response = await fetch(`${API_URL}/media/${mediaAssetId}`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } }); const { mediaAsset } = await response.json(); console.log(`Status: ${mediaAsset.status}`); if (mediaAsset.status === 'ready') { console.log('Processing complete!'); return mediaAsset; } if (mediaAsset.status === 'error') { throw new Error('Processing failed: ' + mediaAsset.errors); } // Wait 30 seconds before checking again await new Promise(resolve => setTimeout(resolve, 30000)); } throw new Error('Processing timeout'); } ``` Avoid polling too frequently. Check every 30-60 seconds to avoid rate limits. For the best user experience with bulk uploads, implement a background job that polls the API every 30-60 seconds to check status updates. ## Adding Chapters ### Automatic Chapter Detection Request automatic chapter generation during processing: ```javascript theme={null} async function processWithChapters(itemId) { const response = await fetch(`${API_URL}/items/${itemId}/process`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ generateChapters: true }) }); const { item } = await response.json(); return item; } ``` ### Manual Chapters Create custom chapters with precise control: ```javascript theme={null} async function createChapter(itemId, mediaAssetId, title, startMs) { const response = await fetch(`${API_URL}/media/chapters`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ itemId, mediaAssetId, title, startMs, suggested: false, accepted: true }) }); const { chapter } = await response.json(); return chapter; } // Example: Create multiple chapters const chapters = [ { title: 'Welcome & Announcements', startMs: 0 }, { title: 'Worship', startMs: 180000 }, { title: 'Message: The Power of Prayer', startMs: 1200000 }, { title: 'Altar Call', startMs: 2400000 }, { title: 'Closing Prayer', startMs: 2700000 } ]; for (const chapterData of chapters) { await createChapter(42, 100, chapterData.title, chapterData.startMs); } ``` **Chapter best practices:** * Use 5-15 minute segments * Start each chapter at a natural transition * Use descriptive, clear titles * Order chapters chronologically (handled automatically by `sortOrder`) ## Managing Thumbnails Thumbnail availability depends on which video player you're using. ### For Haystack Custom Player When using the Haystack custom player, thumbnails are automatically generated via Mux. Access them via the Mux image API: ```javascript theme={null} function getThumbnailUrl(muxPlaybackId, options = {}) { const { time = 0, // Seconds into video width = 640, // Width in pixels height = null, // Auto if not specified fitMode = 'smartcrop' } = options; const params = new URLSearchParams({ time: time.toString(), width: width.toString(), fit_mode: fitMode }); if (height) params.set('height', height.toString()); return `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?${params}`; } // Usage const url = getThumbnailUrl('abc123def456', { time: 120, // 2 minutes in width: 1280 }); ``` **Extract thumbnails from key moments:** ```javascript theme={null} // Get thumbnails for each chapter chapters.forEach(chapter => { const thumbnailUrl = getThumbnailUrl(muxPlaybackId, { time: chapter.startTimeSeconds, width: 640 }); console.log(`${chapter.title}: ${thumbnailUrl}`); }); ``` ### For YouTube/Vimeo Players Mux-generated thumbnails are **not available** when using YouTube or Vimeo for playback. Use the artwork files you uploaded to your items instead. When using YouTube or Vimeo players, retrieve thumbnails from your item's artwork: ```javascript theme={null} async function getItemArtwork(itemId) { const response = await fetch(`${API_URL}/items/${itemId}`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } }); const { item } = await response.json(); // Artwork URLs are directly on the item return { square: item.squareImgUrl, wide: item.wideImgUrl, ultraWide: item.ultraWideImgUrl, vertical: item.verticalImgUrl }; } // Usage const artwork = await getItemArtwork(42); console.log('Square thumbnail:', artwork.square); console.log('Wide thumbnail:', artwork.wide); console.log('Ultra-wide thumbnail:', artwork.ultraWide); console.log('Vertical thumbnail:', artwork.vertical); ``` ## Playback Integration ### HLS Streaming Use the HLS playback URL with any HLS-compatible player: ```javascript theme={null} async function getPlaybackUrl(mediaAssetId) { const response = await fetch(`${API_URL}/media/${mediaAssetId}`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } }); const { mediaAsset } = await response.json(); return `https://stream.mux.com/${mediaAsset.muxPlaybackId}.m3u8`; } // Usage with Video.js const playbackUrl = await getPlaybackUrl(100); const player = videojs('my-video', { sources: [{ src: playbackUrl, type: 'application/x-mpegURL' }] }); ``` ### HLS.js Example ```html theme={null} ``` ## Multiple Media Assets Items can have multiple media assets (e.g., audio and video versions): ```javascript theme={null} // Upload video version const videoAsset = await uploadMedia(itemId, videoFile); // Upload audio-only version const audioAsset = await uploadMedia(itemId, audioFile); // List all media for an item const response = await fetch( `${API_URL}/media?itemId=${itemId}`, { headers: { 'Authorization': `Bearer ${API_TOKEN}` } } ); const { mediaAssets } = await response.json(); console.log(`Item has ${mediaAssets.length} media assets`); ``` ## Updating Media ### Replace Media To replace media, delete the old asset and upload a new one: ```javascript theme={null} // Delete old media await fetch(`${API_URL}/media/${oldMediaAssetId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${API_TOKEN}` } }); // Upload new media const newAsset = await uploadMedia(itemId, newFile); ``` Deleting media is permanent and cannot be undone. Ensure you have backups if needed. ## Complete Upload Workflow Here's a complete example combining all steps: ```javascript theme={null} async function completeMediaWorkflow(itemId, videoFile, externalConfig = null) { try { // 1. Upload media console.log('Uploading media...'); const mediaAsset = await uploadMediaToHaystack(itemId, videoFile, externalConfig); // 2. Wait for processing console.log('Processing media...'); const processedAsset = await waitForProcessing(mediaAsset.id); // 3. Add chapters console.log('Adding chapters...'); const chapters = [ { title: 'Introduction', startTimeSeconds: 0 }, { title: 'Main Content', startTimeSeconds: 300 }, { title: 'Conclusion', startTimeSeconds: 2400 } ]; await createChapters(processedAsset.id, chapters); // 4. Publish the item console.log('Publishing item...'); await fetch(`${API_URL}/items/${itemId}/publish`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}` } }); console.log('✓ Complete! Item is now live.'); return processedAsset; } catch (error) { console.error('Workflow failed:', error.message); throw error; } } ``` ## Troubleshooting ### Common Issues **Causes:** * File too large (>5GB for direct upload) * Poor internet connection * Unsupported codec **Solutions:** * Use external URL method for large files * Compress video before upload * Ensure video uses H.264 codec **Causes:** * Corrupt video file * Invalid URL * Unsupported format **Solutions:** * Test playback locally before upload * Ensure URL is publicly accessible * Convert to MP4 with H.264/AAC **Causes:** * Low audio quality * Background noise * Multiple overlapping speakers **Solutions:** * Use high-quality audio recording * Remove background noise in post-production * Ensure clear, single speaker audio **Causes:** * Very long videos (>2 hours) * 4K resolution * High platform load **Solutions:** * Expect 1-2x realtime processing * Poll every 30-60 seconds to check status * Consider splitting very large files into segments ## Best Practices Compress videos to reasonable bitrates (5-10 Mbps for 1080p) to save storage and bandwidth Check processing status every 30-60 seconds to avoid rate limits Break long content into chapters for better user experience and engagement Verify video plays correctly before uploading to catch issues early ## Next Steps Deep dive into media processing pipeline # Getting Started Source: https://developer.thehaystack.ai/index Build powerful content discovery experiences with semantic search and intelligent media management ## What is Haystack? Haystack is a content management and discovery platform that helps organizations make their media libraries searchable, discoverable, and engaging. With powerful semantic search, automatic transcription, and intelligent content organization, Haystack turns your video and audio content into a searchable knowledge base. ## Key Features Natural language search across your entire content library with AI-powered relevance ranking Organize content with items, series, collections, and topics Automatic transcription, chapter detection, and multi-format video delivery ## Get Started Ready to integrate Haystack into your application? Follow our quickstart guide to make your first API call. Get up and running in 5 minutes with our step-by-step tutorial ## Core Workflows Obtain API credentials and authenticate your requests with Bearer tokens Create items, upload media assets, and organize them into series and collections Process content for search indexing and implement search in your application Monitor user engagement and search quality to optimize content discovery ## API Architecture The Haystack API is organized around REST principles with: * **Base URL**: `https://api.thehaystack.ai/v2/haystack` * **Authentication**: Bearer token authentication * **Format**: JSON request and response bodies * **Versioning**: API version included in URL path ## Explore the API Complete endpoint documentation with request/response schemas Learn how to authenticate and secure your API requests Step-by-step guide to creating and organizing content # Quick Start Source: https://developer.thehaystack.ai/quickstart Get started with the Haystack API in 5 minutes ## Overview This guide will walk you through making your first API calls to Haystack. You'll learn how to authenticate, retrieve content, and perform a search. Haystack provides two APIs. Get the credentials you need from the [Haystack Dashboard](https://app.thehaystack.ai): **For the Private API** (content management, analytics): 1. Navigate to **Developer** → **API** 2. In the **Private API** section, click **New key** 3. Enter a name (e.g., "Development Server") and click **Submit** 4. Copy and save your API key securely (you won't see it again!) **For the Search API** (public search, no authentication): 1. Navigate to **Developer** → **API** 2. In the **Search API** section, find your **Search API Base URL** 3. Click **Copy** to copy your church-specific URL Keep your Private API key secure. Never use it in frontend code or commit it to version control. Store your credentials as environment variables: ```bash theme={null} # Private API (for backend use only) export HAYSTACK_API_TOKEN="sk_..." export HAYSTACK_API_URL="https://api.thehaystack.ai/v2/haystack" # Search API (for frontend/public use) export HAYSTACK_SEARCH_URL="https://your-church-name.thehaystack.ai/api" ``` Or create a `.env` file: ```bash .env theme={null} # Private API (backend only) HAYSTACK_API_TOKEN=sk_... HAYSTACK_API_URL=https://api.thehaystack.ai/v2/haystack # Search API (frontend safe) HAYSTACK_SEARCH_URL=https://your-church-name.thehaystack.ai/api ``` Let's retrieve your collections to verify authentication is working: ```bash cURL theme={null} curl "$HAYSTACK_API_URL/collections" \ -H "Authorization: Bearer $HAYSTACK_API_TOKEN" \ -H "Content-Type: application/json" ``` ```javascript JavaScript theme={null} const API_URL = 'https://api.thehaystack.ai/v2/haystack'; const API_TOKEN = process.env.HAYSTACK_API_TOKEN; async function getCollections() { const response = await fetch(`${API_URL}/collections`, { headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' } }); const data = await response.json(); console.log('Collections:', data.collections); return data; } getCollections(); ``` ```python Python theme={null} import os import requests API_URL = 'https://api.thehaystack.ai/v2/haystack' API_TOKEN = os.getenv('HAYSTACK_API_TOKEN') def get_collections(): headers = { 'Authorization': f'Bearer {API_TOKEN}', 'Content-Type': 'application/json' } response = requests.get(f'{API_URL}/collections', headers=headers) data = response.json() print('Collections:', data['collections']) return data get_collections() ``` ```typescript TypeScript theme={null} const API_URL = 'https://api.thehaystack.ai/v2/haystack'; const API_TOKEN = process.env.HAYSTACK_API_TOKEN; interface Collection { id: number; name: string; customerId: number; } interface CollectionsResponse { collections: Collection[]; total: number; page: number; pageSize: number; } async function getCollections(): Promise { const response = await fetch(`${API_URL}/collections`, { headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' } }); const data = await response.json(); console.log('Collections:', data.collections); return data; } getCollections(); ``` **Expected Response:** ```json theme={null} { "collections": [ { "id": 1, "name": "Sermons", "itemDescriptor": "sermon" } ], "total": 1, "page": 1, "pageSize": 20 } ``` Now let's perform a search query to find content: **No Authentication Required**: The search endpoint uses your church-specific Search API URL and does not require authentication. It's safe to call from public websites. ```bash cURL theme={null} curl "$HAYSTACK_SEARCH_URL/haystack/search?q=leadership" ``` ```javascript JavaScript theme={null} const SEARCH_URL = 'https://your-church-name.thehaystack.ai/api'; async function searchContent(query) { const response = await fetch( `${SEARCH_URL}/haystack/search?q=${encodeURIComponent(query)}`, { headers: { 'Content-Type': 'application/json' } } ); const data = await response.json(); console.log('Search results:', data.items); return data; } searchContent('leadership'); ``` ```python Python theme={null} import requests SEARCH_URL = 'https://your-church-name.thehaystack.ai/api' def search_content(query): params = {'q': query} response = requests.get( f'{SEARCH_URL}/haystack/search', params=params ) data = response.json() print('Search results:', data['items']) return data search_content('leadership') ``` ```typescript TypeScript theme={null} const SEARCH_URL = 'https://your-church-name.thehaystack.ai/api'; interface SearchResult { item: { id: number; title: string; description: string; }; score: number; highlights: Array<{ transcript: string; startMs: number; endMs: number; }>; } interface SearchResponse { query: string; queryAnalyticsId: string; items: SearchResult[]; scriptures: any[]; series: any[]; } async function searchContent(query: string): Promise { const response = await fetch( `${SEARCH_URL}/haystack/search?q=${encodeURIComponent(query)}`, { headers: { 'Content-Type': 'application/json' } } ); const data = await response.json(); console.log('Search results:', data.items); return data; } searchContent('leadership'); ``` **Want AI-generated search overviews?** Add `&stream=true` to receive a streaming response with an AI summary of results. See the [Search endpoint documentation](/api-reference/endpoints/search#streaming-response-with-ai-overview-advanced) for details. **Expected Response:** ```json theme={null} { "query": "leadership", "queryAnalyticsId": "abc123", "items": [ { "item": { "id": 42, "title": "Servant Leadership", "description": "A message about leading with humility" }, "score": 0.89, "highlights": [ { "transcript": "Great leaders understand that leadership is about serving others...", "startMs": 45000, "endMs": 52000 } ] } ], "scriptures": [], "series": [] } ``` Let's create a new item in your library: ```bash cURL theme={null} curl -X POST "$HAYSTACK_API_URL/items/create" \ -H "Authorization: Bearer $HAYSTACK_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Getting Started with Haystack", "date": "2025-01-15", "collectionId": 1, "description": "An introduction to using Haystack API" }' ``` ```javascript JavaScript theme={null} async function createItem() { const response = await fetch(`${API_URL}/items/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'Getting Started with Haystack', date: '2025-01-15', collectionId: 1, description: 'An introduction to using Haystack API' }) }); const data = await response.json(); console.log('Created item:', data.item); return data; } createItem(); ``` ```python Python theme={null} def create_item(): headers = { 'Authorization': f'Bearer {API_TOKEN}', 'Content-Type': 'application/json' } payload = { 'title': 'Getting Started with Haystack', 'date': '2025-01-15', 'collectionId': 1, 'description': 'An introduction to using Haystack API' } response = requests.post( f'{API_URL}/items/create', headers=headers, json=payload ) data = response.json() print('Created item:', data['item']) return data create_item() ``` ```typescript TypeScript theme={null} interface CreateItemPayload { title: string; date: string; collectionId: number; description?: string; subTitle?: string; } async function createItem(itemData: CreateItemPayload) { const response = await fetch(`${API_URL}/items/create`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(itemData) }); const data = await response.json(); console.log('Created item:', data.item); return data; } createItem({ title: 'Getting Started with Haystack', date: '2025-01-15', collectionId: 1, description: 'An introduction to using Haystack API' }); ``` **Expected Response:** ```json theme={null} { "item": { "id": 100, "title": "Getting Started with Haystack", "date": "2025-01-15", "collectionId": 1, "description": "An introduction to using Haystack API", "status": "draft" } } ``` ## Error Handling Always check for errors in API responses. The API uses standard HTTP status codes: ```javascript theme={null} async function makeApiRequest(url, options) { const response = await fetch(url, options); if (!response.ok) { const error = await response.json(); throw new Error(`API Error: ${error.error.message}`); } return response.json(); } ``` Common HTTP status codes: * `200` - Success * `400` - Bad Request (invalid parameters) * `401` - Unauthorized (invalid or missing token) * `403` - Forbidden (insufficient permissions) * `404` - Not Found * `500` - Internal Server Error ## Next Steps Congratulations! You've made your first API calls to Haystack. Here's what to explore next: Learn about items, collections, series, and how they relate Upload and manage video and audio files Deep dive into semantic search capabilities Explore the complete API documentation