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

# Python SDK

> Documentation on how to use the Skald Python SDK.

Python client library for the Skald API.

## Installation

```bash theme={null}
pip install skald-sdk
```

## Requirements

* Python 3.8 or higher

## Usage

### Initialize the client

```python theme={null}
import asyncio
from skald_sdk import Skald

async def main():
    async with Skald('your-api-key-here') as skald:
        # Your code here
        pass

asyncio.run(main())
```

### Memo Management

#### Create a Memo

Create a new memo that will be automatically processed (summarized, tagged, chunked, and indexed for search):

```python theme={null}
result = await skald.create_memo({
    'title': 'Meeting Notes',
    'content': 'Full content of the memo...',
    'metadata': {
        'type': 'notes',
        'author': 'John Doe'
    },
    'reference_id': 'external-id-123',
    'tags': ['meeting', 'q1'],
    'source': 'notion',
    'expiration_date': '2024-12-31T23:59:59Z'
})

print(result)  # { 'memo_uuid': '550e8400-e29b-41d4-a716-446655440000' }
```

**Required Fields:**

* `title` (string, max 255 chars) - The title of the memo
* `content` (string) - The full content of the memo

**Optional Fields:**

* `metadata` (dict) - Custom JSON metadata
* `reference_id` (string, max 255 chars) - An ID from your side that you can use to match Skald memo UUIDs with e.g. documents on your end
* `tags` (list of strings) - Tags for categorization
* `source` (string, max 255 chars) - An indication from your side of the source of this content, useful when building integrations
* `expiration_date` (string) - ISO 8601 timestamp for automatic memo expiration

#### Get a Memo

Retrieve a memo by its UUID or your reference ID:

```python theme={null}
# Get by UUID
memo = await skald.get_memo('550e8400-e29b-41d4-a716-446655440000')

# Get by reference ID
memo = await skald.get_memo('external-id-123', 'reference_id')

print(memo['title'])
print(memo['content'])
print(memo['summary'])
print(memo['tags'])
print(memo['chunks'])
```

The `get_memo()` method returns complete memo details including content, AI-generated summary, tags, and content chunks.

**Memo Response Fields:**

* `uuid` - Unique identifier for the memo
* `title` - Memo title
* `content` - Full memo content
* `summary` - AI-generated summary
* `pending` - Boolean indicating if the memo is still being processed
* `archived` - Boolean indicating if the memo is archived
* `type` - Memo type ("document" or "text")
* `metadata` - Custom metadata object
* `tags` - List of tag objects
* `chunks` - List of content chunk objects

#### List Memos

List all memos with pagination:

```python theme={null}
# Get first page with default page size (20)
memos = await skald.list_memos()

# Get specific page with custom page size
memos = await skald.list_memos({'page': 2, 'page_size': 50})

print(f"Total memos: {memos['count']}")
print(f"Results: {len(memos['results'])}")
print(f"Next page: {memos['next']}")
```

**Parameters:**

* `page` (int, optional) - Page number (default: 1)
* `page_size` (int, optional) - Results per page (default: 20, max: 100)

#### Update a Memo

Update an existing memo by UUID or reference ID:

```python theme={null}
# Update by UUID
await skald.update_memo('550e8400-e29b-41d4-a716-446655440000', {
    'title': 'Updated Title',
    'metadata': {'status': 'reviewed'}
})

# Update by reference ID and trigger reprocessing
await skald.update_memo('external-id-123', {
    'content': 'New content that will be reprocessed'
}, 'reference_id')
```

**Note:** When you update the `content` field, the memo will be automatically reprocessed (summary, tags, and chunks regenerated).

**Updatable Fields:**

* `title` (string)
* `content` (string)
* `metadata` (dict)
* `client_reference_id` (string)
* `source` (string)
* `expiration_date` (string)

#### Delete a Memo

Permanently delete a memo and all associated data:

```python theme={null}
# Delete by UUID
await skald.delete_memo('550e8400-e29b-41d4-a716-446655440000')

# Delete by reference ID
await skald.delete_memo('external-id-123', 'reference_id')
```

**Warning:** This operation permanently deletes the memo and all related data (content, summary, tags, chunks) and cannot be undone.

#### Check Memo Processing Status

After creating or updating a memo (especially when uploading files), it is automatically processed asynchronously (summarized, tagged, and chunked). Use the `check_memo_status()` method to check processing status:

```python theme={null}
# Create a memo
result = await skald.create_memo({
    'title': 'My Document',
    'content': 'Long content that needs processing...'
})
memo_uuid = result['memo_uuid']

# Check processing status
status = await skald.check_memo_status(memo_uuid)

if status['status'] == 'processing':
    print('Memo is still being processed...')
elif status['status'] == 'processed':
    print('Memo is ready!')
    # Now you can retrieve the full memo with summary and tags
    memo = await skald.get_memo(memo_uuid)
    print(f"Summary: {memo['summary']}")
elif status['status'] == 'error':
    print(f"Processing failed: {status['error_reason']}")

# You can also check status by reference ID
status = await skald.check_memo_status('external-id-123', 'reference_id')
```

**Status Response Fields:**

* `memo_uuid` - The memo's UUID
* `status` - Processing state: "processing", "processed", or "error"
* `processing_started_at` - ISO timestamp when processing started
* `processing_completed_at` - ISO timestamp when processing completed (if finished)
* `error_reason` - Error message if status is "error"

**Note:** Most memos are processed within a few seconds, but larger documents may take longer.

### File Uploads

Upload documents (PDF, DOC, DOCX, PPTX) to your Skald knowledge base. Files are automatically processed to extract text, then summarized, tagged, chunked, and indexed for search.

#### Upload a Document

```python theme={null}
# Upload a document file with optional metadata
result = await skald.create_memo_from_file(
    '/path/to/document.pdf',
    {
        'title': 'Q4 Roadmap Presentation',
        'source': 'Product Team',
        'reference_id': 'ROADMAP-Q4-2024',
        'tags': ['roadmap', 'product', 'q4'],
        'metadata': {
            'quarter': 'Q4',
            'year': '2024',
            'department': 'product'
        },
        'expiration_date': '2024-12-31T23:59:59Z'
    }
)

memo_uuid = result['memo_uuid']
print(f"Uploaded document: {memo_uuid}")

# Check processing status
status = await skald.check_memo_status(memo_uuid)
print(f"Processing status: {status['status']}")

# Poll until processing completes
import asyncio
while True:
    status = await skald.check_memo_status(memo_uuid)
    if status['status'] == 'processed':
        print('Processing complete!')
        break
    elif status['status'] == 'error':
        print(f"Error: {status['error_reason']}")
        break
    await asyncio.sleep(2)
```

**Supported File Types:**

* PDF (`.pdf`)
* Microsoft Word (`.doc`, `.docx`)
* Microsoft PowerPoint (`.pptx`)

**File Size Limit:** 100MB per file

**Parameters:**

* `file_path` (string, required) - Path to the file to upload
* `memo_data` (dict, optional) - Optional metadata for the memo:
  * `title` (string) - Title for the memo
  * `source` (string) - Source system identifier
  * `reference_id` (string) - Your external reference ID
  * `tags` (list of strings) - Tags for categorization
  * `metadata` (dict) - Custom JSON metadata
  * `expiration_date` (string) - ISO 8601 timestamp for automatic memo expiration

**Note:** File processing is asynchronous and may take longer than text-based memos depending on file size and complexity. Use `check_memo_status()` to monitor processing progress.

### Search Memos

Search through your memos using semantic search with optional filters:

```python theme={null}
# Basic semantic search
results = await skald.search({
    'query': 'quarterly goals',
    'limit': 10
})

# Search with filters
filtered = await skald.search({
    'query': 'python tutorial',
    'filters': [
        {
            'field': 'source',
            'operator': 'eq',
            'value': 'notion',
            'filter_type': 'native_field'
        },
        {
            'field': 'level',
            'operator': 'eq',
            'value': 'beginner',
            'filter_type': 'custom_metadata'
        }
    ]
})

print(f"Found {len(filtered['results'])} results")
for result in filtered['results']:
    print(f"- {result['memo_title']} (distance: {result['distance']})")
```

#### Search Parameters

* `query` (string, required) - The search query
* `limit` (integer, optional) - Maximum results to return (1-50, default 10)
* `filters` (list, optional) - List of filter objects to narrow results (see Filters section below)

#### Search Response

```python theme={null}
{
    'results': [
        {
            'memo_uuid': str,
            'chunk_uuid': str,
            'memo_title': str,
            'memo_summary': str,
            'content_snippet': str,
            'distance': float | None
        }
    ]
}
```

* `memo_uuid` - Unique identifier for the memo
* `chunk_uuid` - Unique identifier for the chunk
* `memo_title` - Memo title
* `memo_summary` - Auto-generated summary for the memo
* `content_snippet` - A snippet containing content from the matching chunk
* `distance` - A decimal from 0 to 2 determining how close the result was deemed to be to the query. The closer to 0 the more related the content is to the query.

### Chat with Your Knowledge Base

Ask questions about your memos using an AI agent. The agent retrieves relevant context and generates answers with inline citations.

#### Non-Streaming Chat

```python theme={null}
response = await skald.chat({
    'query': 'What were the main points discussed in the Q1 meeting?'
})

print(response['response'])
# "The main points discussed in the Q1 meeting were:
# 1. Revenue targets    
# 2. Hiring plans
# 3. Product roadmap"

print(response['ok'])  # True
```

#### Streaming Chat

For real-time responses, use streaming chat:

```python theme={null}
async for event in skald.streamed_chat({
    'query': 'What are our quarterly goals?'
}):
    if event['type'] == 'token':
        # Print each token as it arrives
        print(event['content'], end='', flush=True)
    elif event['type'] == 'done':
        print('\nDone!')
```

#### Chat Parameters

* `query` (string, required) - The question to ask
* `filters` (list, optional) - List of filter objects to focus chat context on specific sources (see Filters section below)

#### Chat Response

Non-streaming responses include:

* `ok` (bool) - Success status
* `response` (str) - The AI's answer
* `intermediate_steps` (list) - Steps taken by the agent (for debugging)

Streaming responses yield events:

* `{'type': 'token', 'content': str}` - Each text token as it's generated
* `{'type': 'done'}` - Indicates the stream has finished

### Filters

Filters allow you to narrow down results based on memo metadata. You can filter by native fields or custom metadata fields. Filters are supported in `search()`, `chat()`, and their streaming variants.

See [Filters](/docs/api-reference/filters) for complete documentation.

#### Filter Structure

```python theme={null}
{
    'field': str,                       # Field name to filter on
    'operator': str,                    # Comparison operator
    'value': str | list[str],          # Value(s) to compare against
    'filter_type': 'native_field' | 'custom_metadata'
}
```

#### Native Fields

Native fields are built-in memo properties:

* `title` - Memo title
* `source` - Source system (e.g., "notion", "confluence")
* `client_reference_id` - Your external reference ID
* `tags` - Memo tags (list)

#### Custom Metadata Fields

You can filter on any field from the `metadata` dict you provided when creating the memo.

#### Filter Operators

* **`eq`** - Equals (exact match)
* **`neq`** - Not equals
* **`contains`** - Contains substring (case-insensitive)
* **`startswith`** - Starts with prefix (case-insensitive)
* **`endswith`** - Ends with suffix (case-insensitive)
* **`in`** - Value is in list (requires list value)
* **`not_in`** - Value is not in list (requires list value)

#### Filter Examples

```python theme={null}
# Filter by source
{
    'field': 'source',
    'operator': 'eq',
    'value': 'notion',
    'filter_type': 'native_field'
}

# Filter by multiple tags
{
    'field': 'tags',
    'operator': 'in',
    'value': ['security', 'compliance'],
    'filter_type': 'native_field'
}

# Filter by title containing text
{
    'field': 'title',
    'operator': 'contains',
    'value': 'meeting',
    'filter_type': 'native_field'
}

# Filter by custom metadata field
{
    'field': 'department',
    'operator': 'eq',
    'value': 'engineering',
    'filter_type': 'custom_metadata'
}

# Exclude specific sources
{
    'field': 'source',
    'operator': 'not_in',
    'value': ['draft', 'archive'],
    'filter_type': 'native_field'
}
```

#### Combining Multiple Filters

When you provide multiple filters, they are combined with AND logic (all filters must match):

```python theme={null}
results = await skald.search({
    'query': 'security best practices',
    'filters': [
        {
            'field': 'source',
            'operator': 'eq',
            'value': 'security-docs',
            'filter_type': 'native_field'
        },
        {
            'field': 'tags',
            'operator': 'in',
            'value': ['approved', 'current'],
            'filter_type': 'native_field'
        },
        {
            'field': 'status',
            'operator': 'neq',
            'value': 'draft',
            'filter_type': 'custom_metadata'
        }
    ]
})
```

#### Filters with Chat

Focus chat context on specific sources:

```python theme={null}
response = await skald.chat({
    'query': 'What are our security practices?',
    'filters': [
        {
            'field': 'tags',
            'operator': 'in',
            'value': ['security', 'compliance'],
            'filter_type': 'native_field'
        }
    ]
})
```

### Error Handling

```python theme={null}
try:
    result = await skald.create_memo({
        'title': 'My Memo',
        'content': 'Content here'
    })
    print('Success:', result)
except Exception as e:
    print(f'Error: {e}')
```

## Type Hints Support

This package includes full type hints for better IDE support and type checking.

```python theme={null}
from skald_sdk import Skald
from skald_sdk.types import (
    MemoData,
    MemoFileData,
    UpdateMemoData,
    SearchRequest,
    ChatRequest,
    Filter,
    FilterOperator,
    FilterType,
    MemoStatus,
    CreateMemoResponse,
    Memo,
    ListMemosResponse,
    UpdateMemoResponse,
    MemoStatusResponse,
    IdType,
    SearchResponse,
    ChatResponse,
    ChatStreamEvent
)

async def main():
    async with Skald('your-api-key-here') as skald:
        # Create a memo with types
        memo_data: MemoData = {
            'title': 'My Memo',
            'content': 'Content here',
            'tags': ['tag1', 'tag2'],
            'metadata': {'department': 'engineering'}
        }

        create_response: CreateMemoResponse = await skald.create_memo(memo_data)

        # Get memo with types
        memo: Memo = await skald.get_memo('550e8400-e29b-41d4-a716-446655440000')
        memo_by_ref: Memo = await skald.get_memo('external-id-123', 'reference_id')

        # List memos with types
        memos: ListMemosResponse = await skald.list_memos({'page': 1, 'page_size': 20})

        # Update memo with types
        update_data: UpdateMemoData = {
            'title': 'Updated Title',
            'metadata': {'status': 'reviewed'}
        }
        update_response: UpdateMemoResponse = await skald.update_memo(
            '550e8400-e29b-41d4-a716-446655440000',
            update_data
        )

        # Delete memo
        await skald.delete_memo('550e8400-e29b-41d4-a716-446655440000')

        # Upload a file with types
        file_data: MemoFileData = {
            'title': 'Q4 Roadmap',
            'source': 'Product Team',
            'reference_id': 'ROADMAP-Q4-2024',
            'tags': ['roadmap', 'product'],
            'metadata': {'quarter': 'Q4', 'year': '2024'}
        }
        file_response: CreateMemoResponse = await skald.create_memo_from_file(
            '/path/to/document.pdf',
            file_data
        )

        # Check memo status with types
        status: MemoStatusResponse = await skald.check_memo_status(file_response['memo_uuid'])
        memo_status: MemoStatus = status['status']  # "processing", "processed", or "error"

        # Search with filters and types
        filters: list[Filter] = [
            {
                'field': 'source',
                'operator': 'eq',
                'value': 'notion',
                'filter_type': 'native_field'
            },
            {
                'field': 'department',
                'operator': 'eq',
                'value': 'engineering',
                'filter_type': 'custom_metadata'
            }
        ]

        search_request: SearchRequest = {
            'query': 'quarterly goals',
            'limit': 10,
            'filters': filters
        }

        search_response: SearchResponse = await skald.search(search_request)

        # Chat with filters and types
        chat_response: ChatResponse = await skald.chat({
            'query': 'What are our quarterly goals?',
            'filters': filters
        })
        print(chat_response['response'])

        # Streaming chat with types
        async for event in skald.streamed_chat({
            'query': 'What are our quarterly goals?',
            'filters': filters
        }):
            typed_event: ChatStreamEvent = event
            if typed_event['type'] == 'token':
                print(typed_event['content'], end='', flush=True)
```
