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

# Python SDK for self-hosting and REST API calls

> Integrate the Velt Python SDK to self-host backend data with MongoDB and S3, or call Velt's REST APIs directly without managing a database.

## Overview

The [Velt Python SDK](https://pypi.org/project/velt-py) exposes two independent backends:

| Backend      | Namespace           | Use case                                              |
| ------------ | ------------------- | ----------------------------------------------------- |
| Self-hosting | `sdk.selfHosting.*` | Store Velt data in your own MongoDB + AWS S3          |
| REST API     | `sdk.api.*`         | Call Velt's REST APIs directly — no database required |

**Self-hosting backend** (`sdk.selfHosting.*`) simplifies backend implementation by **90%**. Instead of writing custom database queries and storage logic, you:

1. Pass your DB and storage configs to the SDK
2. Pass the raw request object directly to SDK methods
3. Return the resulting response object straight to the client

**REST API backend** (`sdk.api.*`) provides parity with [Velt's REST APIs](/docs/api-reference/rest-apis/v2/organizations/get-organizations-v2). 18 services, typed `@dataclass` request objects, and raw Velt API responses. No database or AWS configuration needed.

## Installation

```bash theme={null}
pip install velt-py
```

### Requirements

* Python 3.8+
* Django 4.2.26+ (only if using the self-hosting backend)
* MongoDB (Percona Server or MongoDB Atlas) for self-hosting
* `requests` for REST API calls (installed automatically)
* Optional `velt-py[auth]` extra (PyJWT>=2.8.0, cryptography>=42.0.0) — required only for the built-in JWT/JWKS verifier path of [`verifyToken`](#resolver-token-verification). Install with `pip install 'velt-py[auth]'`. Core install (`pip install velt-py`), the custom-callback verify path, and all non-auth features need no new dependency.

## Quick Start

### Initialize the SDK

<Tabs>
  <Tab title="Self-hosting">
    Self-hosting initialization (MongoDB + optional AWS):

    ```python theme={null}
    from velt_py import VeltSDK

    # Initialize with database and AWS config
    config = {
        'database': {
            # Option 1: Connection string (recommended for MongoDB Atlas)
            'connection_string': 'mongodb+srv://user:pass@cluster.mongodb.net/velt-db',
            
            # Option 2: Individual components
            # 'host': 'localhost:27017',
            # 'username': 'your_username',
            # 'password': 'your_password',
            # 'auth_database': 'admin',
            # 'database_name': 'velt-db'
        },
        # Optional: AWS S3 config for attachments
        'aws': {
            'bucket_name': 'your-bucket',
            'region': 'us-east-1',
            'access_key_id': 'YOUR_ACCESS_KEY',
            'secret_access_key': 'YOUR_SECRET_KEY'
        },
        # Optional: Custom collection names
        'collections': {
            'comments': 'comment_annotations',
            'reactions': 'reaction_annotations',
            'attachments': 'attachments',
            'users': 'users'
        },
        # Required for token generation; also enables sdk.api.*
        'apiKey': 'YOUR_VELT_API_KEY',
        'authToken': 'YOUR_VELT_AUTH_TOKEN'
    }

    sdk = VeltSDK.initialize(config)
    ```
  </Tab>

  <Tab title="REST API">
    REST API-only initialization (no database needed):

    ```python theme={null}
    from velt_py import VeltSDK

    # Minimal config for REST API backend only
    config = {
        'apiKey': 'YOUR_VELT_API_KEY',
        'authToken': 'YOUR_VELT_AUTH_TOKEN'
    }

    sdk = VeltSDK.initialize(config)

    # All sdk.api.* services are now available
    result = sdk.api.organizations.getOrganizations(...)
    ```

    The `database` section is optional when using only `sdk.api.*`. Set `VELT_API_KEY` and `VELT_AUTH_TOKEN` environment variables as an alternative to passing credentials in the config dict.
  </Tab>
</Tabs>

## Configuration

### Environment Variables

The SDK reads the following environment variables automatically. These can be used instead of (or in addition to) config dict keys.

| Variable                    | Config key equivalent | Purpose                                              |
| --------------------------- | --------------------- | ---------------------------------------------------- |
| `VELT_API_KEY`              | `apiKey`              | Velt API key for authenticating REST API calls       |
| `VELT_AUTH_TOKEN`           | `authToken`           | Velt auth token for authenticating REST API calls    |
| `VELT_WORKSPACE_ID`         | —                     | Default workspace ID for workspace-scoped operations |
| `VELT_WORKSPACE_AUTH_TOKEN` | —                     | Auth token scoped to a specific workspace            |

### Self-Hosting Configuration

<Tabs>
  <Tab title="Database">
    Configure MongoDB connection for storing comments, reactions, and user data.

    ```python theme={null}
    config = {
        'database': {
            # Option 1: Connection string (recommended for MongoDB Atlas)
            'connection_string': 'mongodb+srv://user:pass@cluster.mongodb.net/velt-db',
            
            # Option 2: Individual components (for local MongoDB or custom setup)
            # 'host': 'localhost:27017',
            # 'username': 'your_username',
            # 'password': 'your_password',
            # 'auth_database': 'admin',
            # 'database_name': 'velt-db'
        }
    }
    ```
  </Tab>

  <Tab title="AWS (Attachments)">
    Configure AWS S3 for storing file attachments.

    ```python theme={null}
    config = {
        'aws': {
            'bucket_name': 'your-bucket-name',
            'region': 'us-east-1',
            'access_key_id': 'YOUR_AWS_ACCESS_KEY',
            'secret_access_key': 'YOUR_AWS_SECRET_KEY'
        }
    }
    ```
  </Tab>

  <Tab title="Collections">
    Customize collection names for storing Velt data.

    ```python theme={null}
    config = {
        'collections': {
            'comments': 'comment_annotations',    # Default: 'comment_annotations'
            'reactions': 'reaction_annotations',  # Default: 'reaction_annotations'
            'attachments': 'attachments',         # Default: 'attachments'
            'users': 'users'                      # Default: 'users'
        }
    }
    ```
  </Tab>

  <Tab title="User Schema">
    Map your user schema fields to Velt's expected fields if your database uses different field names.

    ```python theme={null}
    config = {
        'user_schema': {
            'userId': 'userId',       # Your field name for user ID
            'name': 'name',           # Your field name for display name
            'photoUrl': 'photoUrl',   # Your field name for avatar URL
            'email': 'email',         # Your field name for email
            'color': 'color',         # Your field name for user color
            'textColor': 'textColor', # Your field name for text color
            'isAdmin': 'isAdmin',     # Your field name for admin flag
            'initial': 'initial'      # Your field name for user initial
        }
    }
    ```
  </Tab>

  <Tab title="Resolver Auth">
    Optional `resolver_auth` block enabling [`sdk.selfHosting.verifyToken`](#resolver-token-verification). The built-in JWT verifier requires `pip install 'velt-py[auth]'`; the custom `verify` callback needs no new dependency.

    ```python theme={null}
    config = {
        'resolver_auth': {
            # Built-in JWT verifier (HS256 example):
            'jwt': {
                'secret': 'your-hmac-secret',          # HS* — or for RS*/ES*:
                # 'public_key': '-----BEGIN PUBLIC KEY-----...',
                # 'jwks_url': 'https://your-idp/.well-known/jwks.json',
                'algorithms': ['HS256'],               # REQUIRED allowlist
                'issuer': 'https://your-idp',          # optional, enforced when set
                'audience': 'velt',                    # optional, enforced when set
                'leeway': 30,                          # optional clock skew (seconds)
                # 'require': ['exp'],                  # optional: reject tokens missing these claims
            },
            # OR a custom escape hatch (takes priority over `jwt`):
            # 'verify': lambda token, headers: my_decode(token),  # return claims dict or None
        }
    }
    ```

    See [Resolver Token Verification](#resolver-token-verification) for full detail.
  </Tab>

  <Tab title="Complete Example">
    Full configuration with database, AWS, collections, and user schema.

    ```python theme={null}
    from velt_py import VeltSDK

    config = {
        'database': {
            'connection_string': 'mongodb+srv://user:pass@cluster.mongodb.net/velt-db',
        },
        'aws': {
            'bucket_name': 'your-bucket-name',
            'region': 'us-east-1',
            'access_key_id': 'YOUR_AWS_ACCESS_KEY',
            'secret_access_key': 'YOUR_AWS_SECRET_KEY'
        },
        'collections': {
            'comments': 'comment_annotations',
            'reactions': 'reaction_annotations',
            'attachments': 'attachments',
            'users': 'users'
        },
        'user_schema': {
            'userId': 'userId',
            'name': 'name',
            'photoUrl': 'photoUrl',
            'email': 'email',
            'color': 'color',
            'textColor': 'textColor',
            'isAdmin': 'isAdmin',
            'initial': 'initial'
        },
        'apiKey': 'YOUR_VELT_API_KEY',
        'authToken': 'YOUR_VELT_AUTH_TOKEN'
    }

    sdk = VeltSDK.initialize(config)
    ```
  </Tab>
</Tabs>

## Self-Hosting Backend

The SDK provides methods that accept the raw request from the frontend and return the properly formatted response.

All self-hosting service classes — the `BaseService` base class plus seven resolver services — are importable from `velt_py.services.self_hosting`. As of **v0.1.12**, `RecorderService`, `NotificationService`, and `ActivityService` are also exported — previously they were wired into `SelfHostingBackend` but missing from the package's `__all__`.

```python theme={null}
from velt_py.services.self_hosting import (
    BaseService,
    CommentService,
    ReactionService,
    AttachmentService,
    RecorderService,      # newly exported in v0.1.12
    NotificationService,  # newly exported in v0.1.12
    ActivityService,      # newly exported in v0.1.12
    UserService,
)
```

### Comments

#### `getComments`

* Fetches comments from your database for a document.
* Params: [GetCommentResolverRequest](/docs/api-reference/sdk/models/data-models#getcommentresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
from velt_py import GetCommentResolverRequest

def get_comments(request):
    data = request.json
    comment_request = GetCommentResolverRequest.from_dict(data)
    result = sdk.selfHosting.comments.getComments(comment_request)
    return result
```

**Response**

```python theme={null}
{
    'success': True,
    'statusCode': 200,
    'data': { 'comments': [ ... ] }
}
```

#### `saveComments`

* Saves new or updated comments to your database.
* Params: [SaveCommentResolverRequest](/docs/api-reference/sdk/models/data-models#savecommentresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
from velt_py import SaveCommentResolverRequest

def save_comments(request):
    data = request.json
    save_request = SaveCommentResolverRequest.from_dict(data)
    result = sdk.selfHosting.comments.saveComments(save_request)
    return result
```

**Response**

```python theme={null}
{ 'success': True, 'statusCode': 200, 'data': { ... } }
```

> As of **v0.1.14**, when used as a save-resolver handler the inbound `event` may be a `ResolverActions` OR a `CommentResolverSaveEvent` member (or a raw string for forward-compat), and the request may carry `targetComment` (request context only — never persisted). See the [`SaveCommentResolverRequest`](#savecommentresolverrequest) entry under Data Models for these additions.

#### `deleteComment`

* Deletes a comment from your database.
* Params: [DeleteCommentResolverRequest](/docs/api-reference/sdk/models/data-models#deletecommentresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
from velt_py import DeleteCommentResolverRequest

def delete_comment(request):
    data = request.json
    delete_request = DeleteCommentResolverRequest.from_dict(data)
    result = sdk.selfHosting.comments.deleteComment(delete_request)
    return result
```

**Response**

```python theme={null}
{ 'success': True, 'statusCode': 200, 'data': { ... } }
```

### Reactions

#### `getReactions`

* Fetches reactions from your database for a document.
* Params: [GetReactionResolverRequest](/docs/api-reference/sdk/models/data-models#getreactionresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
from velt_py import GetReactionResolverRequest

def get_reactions(request):
    data = request.json
    reaction_request = GetReactionResolverRequest.from_dict(data)
    result = sdk.selfHosting.reactions.getReactions(reaction_request)
    return result
```

**Response**

```python theme={null}
{
    'success': True,
    'statusCode': 200,
    'data': { 'reactions': [ ... ] }
}
```

#### `saveReactions`

* Saves new or updated reactions to your database.
* Params: [SaveReactionResolverRequest](/docs/api-reference/sdk/models/data-models#savereactionresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
from velt_py import SaveReactionResolverRequest

def save_reactions(request):
    data = request.json
    save_request = SaveReactionResolverRequest.from_dict(data)
    result = sdk.selfHosting.reactions.saveReactions(save_request)
    return result
```

**Response**

```python theme={null}
{ 'success': True, 'statusCode': 200, 'data': { ... } }
```

#### `deleteReaction`

* Deletes a reaction from your database.
* Params: [DeleteReactionResolverRequest](/docs/api-reference/sdk/models/data-models#deletereactionresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
from velt_py import DeleteReactionResolverRequest

def delete_reaction(request):
    data = request.json
    delete_request = DeleteReactionResolverRequest.from_dict(data)
    result = sdk.selfHosting.reactions.deleteReaction(delete_request)
    return result
```

**Response**

```python theme={null}
{ 'success': True, 'statusCode': 200, 'data': { ... } }
```

### Users

#### `getUsers`

* Fetches users from your database.
* Params: [GetUserResolverRequest](/docs/api-reference/sdk/models/data-models#getuserresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
from velt_py import GetUserResolverRequest

def get_users(request):
    data = request.json
    user_request = GetUserResolverRequest.from_dict(data)
    result = sdk.selfHosting.users.getUsers(user_request)
    return result
```

**Response**

```python theme={null}
{
    'success': True,
    'statusCode': 200,
    'data': { 'users': [ { 'userId': 'user-1', 'name': 'John Doe', ... } ] }
}
```

#### `resolveUserIdsByEmail`

* Resolves a list of email addresses to their user IDs. Backs the frontend's anonymous-user data provider.
* Params: `ResolveUserIdsByEmailRequest` (from `velt_py.models.user`)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
from velt_py.models.user import ResolveUserIdsByEmailRequest

def resolve_user_ids_by_email(request):
    data = request.json
    resolve_request = ResolveUserIdsByEmailRequest.from_dict(data)
    result = sdk.selfHosting.users.resolveUserIdsByEmail(resolve_request)
    return result
```

Custom `user_schema` field mappings are honoured. Repeated addresses are de-duplicated and blank or `None` entries are dropped.

**Response**

`data` is a map of email to user ID. Addresses that do not match a user are simply absent from the map:

```python theme={null}
{
    'success': True,
    'statusCode': 200,
    'data': { 'alice@example.com': 'user-1', 'bob@example.com': 'user-2' }
}
```

### Attachments

#### `saveAttachment`

* Uploads an attachment file to S3 and saves the metadata.
* Params: [SaveAttachmentResolverRequest](/docs/api-reference/sdk/models/data-models#saveattachmentresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

<Tabs>
  <Tab title="Django">
    ```python theme={null}
    import json
    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from django.views.decorators.http import require_http_methods
    from velt_py import SaveAttachmentResolverRequest, DeleteAttachmentResolverRequest
    from .velt_sdk import get_velt_sdk

    @csrf_exempt
    @require_http_methods(["POST"])
    def save_attachment(request):
        try:
            file = request.FILES.get('file')
            request_json_str = request.POST.get('request')
            
            if not file or not request_json_str:
                return JsonResponse({
                    'success': False,
                    'error': 'File and request JSON are required',
                    'errorCode': 'INVALID_INPUT',
                    'statusCode': 400
                }, status=400)
            
            request_data = json.loads(request_json_str)
            save_request = SaveAttachmentResolverRequest.from_dict(request_data)
            
            sdk = get_velt_sdk()
            result = sdk.selfHosting.attachments.saveAttachment(
                save_request,
                file_data=file.read(),
                file_name=file.name,
                mime_type=file.content_type
            )
            return JsonResponse(result, status=result.get('statusCode', 200))
        except Exception as e:
            return JsonResponse({
                'success': False,
                'error': str(e),
                'errorCode': 'INTERNAL_ERROR',
                'statusCode': 500
            }, status=500)
    ```
  </Tab>

  <Tab title="Flask">
    ```python theme={null}
    import json
    from flask import request, jsonify
    from velt_py import SaveAttachmentResolverRequest

    @app.route('/api/velt/attachments/save', methods=['POST'])
    def save_attachment():
        try:
            file = request.files.get('file')
            request_json = request.form.get('request')
            
            if not file or not request_json:
                return jsonify({
                    'success': False,
                    'error': 'File and request JSON are required',
                    'errorCode': 'INVALID_INPUT',
                    'statusCode': 400
                }), 400
            
            request_data = json.loads(request_json)
            save_request = SaveAttachmentResolverRequest.from_dict(request_data)
            
            result = sdk.selfHosting.attachments.saveAttachment(
                save_request,
                file_data=file.read(),
                file_name=file.filename,
                mime_type=file.content_type
            )
            return jsonify(result), result.get('statusCode', 200)
        except Exception as e:
            return jsonify({'success': False, 'error': str(e), 'errorCode': 'INTERNAL_ERROR', 'statusCode': 500}), 500
    ```
  </Tab>

  <Tab title="FastAPI">
    ```python theme={null}
    import json
    from fastapi import Request, UploadFile, File, Form
    from fastapi.responses import JSONResponse
    from velt_py import SaveAttachmentResolverRequest

    @app.post('/api/velt/attachments/save')
    async def save_attachment(file: UploadFile = File(...), request: str = Form(...)):
        try:
            request_data = json.loads(request)
            save_request = SaveAttachmentResolverRequest.from_dict(request_data)
            
            file_bytes = await file.read()
            result = sdk.selfHosting.attachments.saveAttachment(
                save_request,
                file_data=file_bytes,
                file_name=file.filename,
                mime_type=file.content_type
            )
            return JSONResponse(content=result, status_code=result.get('statusCode', 200))
        except Exception as e:
            return JSONResponse(content={'success': False, 'error': str(e), 'errorCode': 'INTERNAL_ERROR', 'statusCode': 500}, status_code=500)
    ```
  </Tab>
</Tabs>

**Response**

```python theme={null}
{ 'success': True, 'statusCode': 200, 'data': { ... } }
```

#### `deleteAttachment`

* Deletes an attachment file from S3 and removes its metadata.
* Params: [DeleteAttachmentResolverRequest](/docs/api-reference/sdk/models/data-models#deleteattachmentresolverrequest)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

<Tabs>
  <Tab title="Django">
    ```python theme={null}
    import json
    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from django.views.decorators.http import require_http_methods
    from velt_py import DeleteAttachmentResolverRequest
    from .velt_sdk import get_velt_sdk

    @csrf_exempt
    @require_http_methods(["POST"])
    def delete_attachment(request):
        try:
            data = json.loads(request.body)
            delete_request = DeleteAttachmentResolverRequest.from_dict(data)
            sdk = get_velt_sdk()
            result = sdk.selfHosting.attachments.deleteAttachment(delete_request)
            return JsonResponse(result, status=result.get('statusCode', 200))
        except Exception as e:
            return JsonResponse({
                'success': False,
                'error': str(e),
                'errorCode': 'INTERNAL_ERROR',
                'statusCode': 500
            }, status=500)
    ```
  </Tab>

  <Tab title="Flask">
    ```python theme={null}
    from flask import request, jsonify
    from velt_py import DeleteAttachmentResolverRequest

    @app.route('/api/velt/attachments/delete', methods=['POST'])
    def delete_attachment():
        try:
            data = request.json
            delete_request = DeleteAttachmentResolverRequest.from_dict(data)
            result = sdk.selfHosting.attachments.deleteAttachment(delete_request)
            return jsonify(result), result.get('statusCode', 200)
        except Exception as e:
            return jsonify({'success': False, 'error': str(e), 'errorCode': 'INTERNAL_ERROR', 'statusCode': 500}), 500
    ```
  </Tab>

  <Tab title="FastAPI">
    ```python theme={null}
    from fastapi import Request
    from fastapi.responses import JSONResponse
    from velt_py import DeleteAttachmentResolverRequest

    @app.post('/api/velt/attachments/delete')
    async def delete_attachment(request: Request):
        try:
            data = await request.json()
            delete_request = DeleteAttachmentResolverRequest.from_dict(data)
            result = sdk.selfHosting.attachments.deleteAttachment(delete_request)
            return JSONResponse(content=result, status_code=result.get('statusCode', 200))
        except Exception as e:
            return JSONResponse(content={'success': False, 'error': str(e), 'errorCode': 'INTERNAL_ERROR', 'statusCode': 500}, status_code=500)
    ```
  </Tab>
</Tabs>

**Response**

```python theme={null}
{ 'success': True, 'statusCode': 200, 'data': { ... } }
```

### Token

#### `getToken`

* Generates a Velt auth token for a user. Self-hosting variant.
* Params: positional — `organizationId`, `userId`, `email` (optional), `isAdmin` (optional)
* Returns: [VeltSelfHostingResponse](/docs/api-reference/sdk/models/data-models#veltselfhostingresponse)

```python theme={null}
result = sdk.selfHosting.token.getToken(
    organizationId='org-123',
    userId='user-1',
    email='user@example.com',
    isAdmin=False
)
```

**Response**

```python theme={null}
{
    'success': True,
    'statusCode': 200,
    'data': { 'token': 'eyJhbGciOi...' }
}
```

> Note: `getToken` takes positional / keyword arguments — it does NOT accept a dataclass request object. Signature: `getToken(organizationId, userId, email=None, isAdmin=False)`.

### Resolver Token Verification

`sdk.selfHosting.verifyToken(headers=None, token=None, **overrides) -> VerifyTokenResult` authenticates the credential the Velt frontend forwards to your resolver endpoints. New in **v0.1.14**. It is **opt-in** via the `resolver_auth` config block, **framework-agnostic**, and **fail-closed** — it returns a structured [`VerifyTokenResult`](#verifytokenresult) and never raises for a verification outcome. It is **authentication only**: it never consults `apiKey`, `organizationId`, or the resolver database; resolver services keep their own scoping. `result.claims` is informational — assert tenant claims against the resolver payload's `organizationId` yourself if you need tenant isolation.

The built-in JWT/JWKS verifier requires the optional extra: `pip install 'velt-py[auth]'`. The custom-callback (`verify`) path needs no new dependency. Configure `resolver_auth` in the [Resolver Auth config tab](#self-hosting-configuration).

* Params:
  * `headers` — Incoming request headers; the token is extracted from the configured header.
  * `token` — A raw JWT string, passed directly instead of `headers`.
  * `**overrides` — One-off `jwt` / `verify` / `header` / `scheme` that merge over the configured `resolver_auth` without changing global config.
* Returns: [`VerifyTokenResult`](#verifytokenresult) — see the Data Models entry for fields.

```python theme={null}
# In your resolver handler:
result = sdk.selfHosting.verifyToken(headers=request.headers)  # or token='<raw_jwt>'
if not result.verified:
    return HttpResponse(status=401)   # result.errorCode tells you why
# result.claims holds the decoded payload
sdk.selfHosting.comments.saveComments(SaveCommentResolverRequest.from_dict(data))
```

**Error codes** (`result.errorCode`)

| Code                    | When                                                                |
| ----------------------- | ------------------------------------------------------------------- |
| `MISSING_TOKEN`         | No token found in header / not passed, empty, or wrong scheme.      |
| `EXPIRED`               | Token `exp` is in the past.                                         |
| `INVALID_SIGNATURE`     | Signature did not verify against the resolved key.                  |
| `CLAIM_MISMATCH`        | `issuer`, `audience`, or a `require`d claim failed the check.       |
| `ALGORITHM_NOT_ALLOWED` | Token `alg` is not in the configured allowlist.                     |
| `KEY_RESOLUTION_FAILED` | JWKS fetch / `public_key` / `secret` could not yield a usable key.  |
| `VERIFICATION_FAILED`   | Generic verification failure not covered above.                     |
| `NOT_CONFIGURED`        | `resolver_auth` is not configured.                                  |
| `DEPENDENCY_MISSING`    | Built-in verifier used without the `velt-py[auth]` extra installed. |

**Token extraction**

Extracted from the configured header (default `Authorization`, case-insensitive), stripping the configured scheme prefix (default `Bearer`); or pass `token='<raw_jwt>'`. Missing, empty, or wrong-scheme credentials → `MISSING_TOKEN`.

**Built-in JWT path — key resolution**

* `jwt.secret` → symmetric HS256/384/512. A PEM string in `secret` is rejected to prevent PEM-as-HMAC forgery.
* `jwt.public_key` → static RSA/EC key for RS\* / ES\*.
* `jwt.jwks_url` → fetched over HTTPS, cached in-process by `(url, kid)`. A fetch failure, non-2xx response, or unresolvable `kid` → `KEY_RESOLUTION_FAILED` without raising.

**Security properties**

* **Fail-closed** — never `verified=True` unless a token was present and fully verified.
* **Algorithm pinning** — the allowlist is always required and passed to PyJWT; `alg=none` is rejected unconditionally even if listed; mixed symmetric + asymmetric allowlists are rejected (HS/RS confusion).
* **HTTPS-only JWKS** — `jwks_url` must be `https://`; a redirect downgrade https→http is rejected; 3xx responses are rejected; cache is scoped by `(url, kid)`, not `kid` alone.
* **No credential leakage** — the token, secret, and claims never appear in error strings or logs.
* **Authentication only** — never consults `apiKey`, `organizationId`, or the resolver database.

**Per-call overrides**

`**overrides` kwargs merge over the configured `resolver_auth`, letting you supply a one-off `jwt`, `verify`, `header`, or `scheme` without mutating global config.

**When `resolver_auth` is not configured**

`verifyToken` returns `verified=False` with `errorCode='NOT_CONFIGURED'`. It does not raise and does not silently pass.

**`ResolverAuthService`**

Newly exported in `velt_py.services.self_hosting.__all__` — the DB-free service backing `verifyToken`. Direct use is optional.

### Framework Examples

<Tabs>
  <Tab title="Django">
    **SDK Initialization (velt\_sdk.py)**

    ```python theme={null}
    from django.conf import settings
    from velt_py import VeltSDK

    _velt_sdk = None

    def get_velt_sdk():
        global _velt_sdk
        if _velt_sdk is None:
            _velt_sdk = VeltSDK.initialize(settings.VELT_SDK_CONFIG)
        return _velt_sdk
    ```

    **API Endpoints (views.py)**

    ```python theme={null}
    import json
    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from django.views.decorators.http import require_http_methods
    from velt_py import GetCommentResolverRequest, SaveCommentResolverRequest, DeleteCommentResolverRequest
    from .velt_sdk import get_velt_sdk

    @csrf_exempt
    @require_http_methods(["POST"])
    def get_comments(request):
        try:
            data = json.loads(request.body)
            comment_request = GetCommentResolverRequest.from_dict(data)
            sdk = get_velt_sdk()
            result = sdk.selfHosting.comments.getComments(comment_request)
            return JsonResponse(result, status=result.get('statusCode', 200))
        except Exception as e:
            return JsonResponse({
                'success': False,
                'error': str(e),
                'errorCode': 'INTERNAL_ERROR',
                'statusCode': 500
            }, status=500)
    ```

    **Configuration (settings.py)**

    ```python theme={null}
    import os

    VELT_SDK_CONFIG = {
        'database': {
            'connection_string': os.environ.get('VELT_MONGODB_CONNECTION_STRING'),
        },
        'aws': {
            'bucket_name': os.environ.get('AWS_S3_BUCKET'),
            'region': os.environ.get('AWS_REGION', 'us-east-1'),
            'access_key_id': os.environ.get('AWS_ACCESS_KEY_ID'),
            'secret_access_key': os.environ.get('AWS_SECRET_ACCESS_KEY'),
        },
        'apiKey': os.environ.get('VELT_API_KEY'),
        'authToken': os.environ.get('VELT_AUTH_TOKEN')
    }
    ```
  </Tab>

  <Tab title="Flask">
    ```python theme={null}
    from flask import Flask, request, jsonify
    from velt_py import VeltSDK, GetCommentResolverRequest, SaveCommentResolverRequest, DeleteCommentResolverRequest

    app = Flask(__name__)

    sdk = VeltSDK.initialize({
        'database': { 'connection_string': 'mongodb+srv://...' }
    })

    @app.route('/api/velt/comments/get', methods=['POST'])
    def get_comments():
        data = request.json
        comment_request = GetCommentResolverRequest.from_dict(data)
        result = sdk.selfHosting.comments.getComments(comment_request)
        return jsonify(result), result.get('statusCode', 200)
    ```
  </Tab>

  <Tab title="FastAPI">
    ```python theme={null}
    from fastapi import FastAPI, Request
    from fastapi.responses import JSONResponse
    from velt_py import VeltSDK, GetCommentResolverRequest

    app = FastAPI()

    sdk = VeltSDK.initialize({
        'database': { 'connection_string': 'mongodb+srv://...' }
    })

    @app.post('/api/velt/comments/get')
    async def get_comments(request: Request):
        data = await request.json()
        comment_request = GetCommentResolverRequest.from_dict(data)
        result = sdk.selfHosting.comments.getComments(comment_request)
        return JSONResponse(content=result, status_code=result.get('statusCode', 200))
    ```
  </Tab>
</Tabs>

## REST API Backend

The `sdk.api.*` namespace gives you parity with [Velt's REST APIs](/docs/api-reference/rest-apis/v2/organizations/get-organizations-v2) across 18 services. Each method takes a typed `@dataclass` request object and returns the raw Velt API response.

You only need `apiKey` and `authToken` — no database or AWS config.

```python theme={null}
from velt_py import VeltSDK

sdk = VeltSDK.initialize({
    'apiKey': 'YOUR_VELT_API_KEY',
    'authToken': 'YOUR_VELT_AUTH_TOKEN'
})

# All 18 services are accessible under sdk.api.*
# e.g. sdk.api.organizations, sdk.api.documents, sdk.api.workspace,
#      sdk.api.workflow ...
```

Every method returns a dict with either a `result` key (success) or an `error` key (failure). See [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse).

### Organizations

#### `addOrganizations`

* Creates one or more organizations.
* Params: [AddOrganizationsRequest](/docs/api-reference/sdk/models/data-models#addorganizationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.organization import AddOrganizationsRequest

result = sdk.api.organizations.addOrganizations(
    AddOrganizationsRequest(
        organizations=[{'organizationId': 'yourOrganizationId', 'organizationName': 'Your Organization Name'}]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Organization(s) added successfully.',
        'data': {
            'yourOrganizationId': {
                'success': True,
                'id': '02cf91e5e7a5f4c0b600c84cf248384b',
                'message': 'Added Successfully'
            }
        }
    }
}
```

#### `getOrganizations`

* Retrieves organizations by ID, with optional pagination.
* Params: [GetOrganizationsRequest](/docs/api-reference/sdk/models/data-models#getorganizationsrequest)
* Returns: [GetOrganizationsResponse](/docs/api-reference/sdk/models/data-models#getorganizationsresponse)

```python theme={null}
from velt_py.models.organization import GetOrganizationsRequest

result = sdk.api.organizations.getOrganizations(
    GetOrganizationsRequest(
        organizationIds=['yourOrganizationId'],
        pageSize=1000,
        pageToken='pageToken'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Organization(s) retrieved successfully.',
        'data': [
            {
                'id': 'yourOrganizationId',
                'organizationName': 'Your Organization Name',
                'disabled': False
                # other metadata fields may be included here
            }
            # ... more organizations if multiple were retrieved
        ],
        'nextPageToken': 'pageToken'
    }
}
```

#### `updateOrganizations`

* Updates properties of one or more organizations.
* Params: [UpdateOrganizationsRequest](/docs/api-reference/sdk/models/data-models#updateorganizationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.organization import UpdateOrganizationsRequest

result = sdk.api.organizations.updateOrganizations(
    UpdateOrganizationsRequest(
        organizations=[{'organizationId': 'yourOrganizationId', 'organizationName': 'Updated Name'}]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Organization(s) updated successfully.',
        'data': {
            'yourOrganizationId': {
                'success': True,
                'id': '02cf91e5e7a5f4c0b600c84cf248384b',
                'message': 'Updated Successfully'
            }
        }
    }
}
```

#### `deleteOrganizations`

* Deletes one or more organizations.
* Params: [DeleteOrganizationsRequest](/docs/api-reference/sdk/models/data-models#deleteorganizationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.organization import DeleteOrganizationsRequest

result = sdk.api.organizations.deleteOrganizations(
    DeleteOrganizationsRequest(organizationIds=['yourOrganizationId1', 'yourOrganizationId2'])
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Organization(s) deleted successfully.',
        'data': {
            'yourOrganizationId1': {
                'success': True,
                'id': '02cf91e5e7a5f4c0b600c84cf248384b',
                'message': 'Deleted Successfully'
            },
            'yourOrganizationId2': {
                'success': False,
                'id': '02cf91e5e7a5f4c0b600c84cf248384b',
                'message': 'Organization does not exist'
            }
        }
    }
}
```

#### `updateOrganizationDisableState`

* Enables or disables access to one or more organizations.
* Params: [UpdateOrganizationDisableStateRequest](/docs/api-reference/sdk/models/data-models#updateorganizationdisablestaterequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.organization import UpdateOrganizationDisableStateRequest

result = sdk.api.organizations.updateOrganizationDisableState(
    UpdateOrganizationDisableStateRequest(
        organizationIds=['yourOrganizationId1', 'yourOrganizationId2'],
        disabled=True
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Updated disable state for Organization(s) successfully.',
        'data': {
            'yourOrganizationId1': {
                'success': True,
                'id': '02cf91e5e7a5f4c0b600c84cf248384b',
                'message': 'Updated disable state for organization Successfully'
            },
            'yourOrganizationId2': {
                'success': False,
                'id': '44e0132f4c6b0d453f18df42d2263b4e',
                'message': 'Organization does not exist'
            }
        }
    }
}
```

### Folders

#### `addFolder`

* Creates one or more folders inside an organization.
* Params: [AddFolderRequest](/docs/api-reference/sdk/models/data-models#addfolderrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.folder import AddFolderRequest

result = sdk.api.folders.addFolder(
    AddFolderRequest(
        organizationId='yourOrganizationId',
        folders=[{
            'folderId': 'yourFolderId',
            'folderName': 'yourFolderName',
            'parentFolderId': 'yourParentFolderId'
        }]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Folder created successfully.',
        'data': {
            'yourFolderId': {
                'success': True,
                'id': 'yourFolderId',
                'message': 'Folder added.'
            }
        }
    }
}
```

#### `getFolders`

* Retrieves folders within an organization.
* Params: [GetFoldersRequest](/docs/api-reference/sdk/models/data-models#getfoldersrequest)
* Returns: [GetFoldersResponse](/docs/api-reference/sdk/models/data-models#getfoldersresponse)

```python theme={null}
from velt_py.models.folder import GetFoldersRequest

result = sdk.api.folders.getFolders(
    GetFoldersRequest(organizationId='yourOrganizationId', folderId='yourFolderId')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Folders retrieved successfully.',
        'data': [
            {
                'folderId': 'folderId1',
                'folderName': 'Folder 1',
                'organizationId': 'yourOrganizationId',
                'parentFolderId': 'root',
                'createdAt': 1738695077691,
                'lastUpdated': 1738695077691,
                'subFolders': [
                    {
                        'folderId': 'childFolderId1',
                        'folderName': 'Child Folder 1',
                        'parentFolderId': 'folderId1',
                        'createdAt': 1738695615706,
                        'lastUpdated': 1738698727591
                    }
                ]
            }
        ]
    }
}
```

#### `updateFolder`

* Updates properties of one or more folders.
* Params: [UpdateFolderRequest](/docs/api-reference/sdk/models/data-models#updatefolderrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.folder import UpdateFolderRequest

result = sdk.api.folders.updateFolder(
    UpdateFolderRequest(
        organizationId='yourOrganizationId',
        folders=[{'folderId': 'yourFolderId', 'folderName': 'yourFolderName', 'parentFolderId': 'yourParentFolderId'}]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Folder Updated successfully.',
        'data': {
            'yourFolderId': {
                'success': True,
                'id': 'yourFolderId',
                'message': 'Folder Updated.'
            }
        }
    }
}
```

#### `deleteFolder`

* Deletes a folder.
* Params: [DeleteFolderRequest](/docs/api-reference/sdk/models/data-models#deletefolderrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.folder import DeleteFolderRequest

result = sdk.api.folders.deleteFolder(
    DeleteFolderRequest(organizationId='yourOrganizationId', folderId='yourFolderId')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Folder deleted successfully.',
        'data': { 'yourFolderId': {'success': True} }
    }
}
```

#### `updateFolderAccess`

* Updates the access type of one or more folders.
* Params: [UpdateFolderAccessRequest](/docs/api-reference/sdk/models/data-models#updatefolderaccessrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.folder import UpdateFolderAccessRequest

result = sdk.api.folders.updateFolderAccess(
    UpdateFolderAccessRequest(
        organizationId='yourOrganizationId',
        folderIds=['yourFolderId'],
        accessType='organizationPrivate'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Updated access for folders successfully.',
        'data': {
            'yourFolderId': {
                'success': True,
                'accessType': 'organizationPrivate',
                'message': 'Folder access type updated.'
            }
        }
    }
}
```

### Documents

#### `addDocuments`

* Creates one or more documents inside an organization.
* Params: [AddDocumentsRequest](/docs/api-reference/sdk/models/data-models#adddocumentsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.document import AddDocumentsRequest

result = sdk.api.documents.addDocuments(
    AddDocumentsRequest(
        organizationId='yourOrganizationId',
        documents=[{'documentId': 'yourDocumentId', 'documentName': 'yourDocumentName'}],
        createOrganization=True,
        folderId='yourFolderId',
        createFolder=True
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Document(s) added successfully.',
        'data': {
            'yourDocumentId': {
                'success': True,
                'id': '8121657101517513',
                'message': 'Added Successfully'
            }
        }
    }
}
```

#### `getDocuments`

* Retrieves documents within an organization.
* Params: [GetDocumentsRequest](/docs/api-reference/sdk/models/data-models#getdocumentsrequest)
* Returns: [GetDocumentsResponse](/docs/api-reference/sdk/models/data-models#getdocumentsresponse)

```python theme={null}
from velt_py.models.document import GetDocumentsRequest

result = sdk.api.documents.getDocuments(
    GetDocumentsRequest(
        organizationId='yourOrganizationId',
        documentIds=['yourDocumentId'],
        folderId='yourFolderId'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Document(s) retrieved successfully.',
        'data': [
            {
                'documentName': 'yourDocumentName',
                'disabled': False,
                'accessType': 'public',
                'id': 'yourDocumentId'
            }
        ],
        'pageToken': 'nextPageToken'
    }
}
```

#### `updateDocuments`

* Updates properties of one or more documents.
* Params: [UpdateDocumentsRequest](/docs/api-reference/sdk/models/data-models#updatedocumentsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.document import UpdateDocumentsRequest

result = sdk.api.documents.updateDocuments(
    UpdateDocumentsRequest(
        organizationId='yourOrganizationId',
        documents=[{'documentId': 'yourDocumentId', 'documentName': 'Updated Name'}]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Document(s) updated successfully.',
        'data': {
            'yourDocumentId': {
                'success': True,
                'id': '8121657101517513',
                'message': 'Updated Successfully'
            }
        }
    }
}
```

#### `deleteDocuments`

* Deletes one or more documents.
* Params: [DeleteDocumentsRequest](/docs/api-reference/sdk/models/data-models#deletedocumentsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.document import DeleteDocumentsRequest

result = sdk.api.documents.deleteDocuments(
    DeleteDocumentsRequest(
        organizationId='yourOrganizationId',
        documentIds=['yourDocumentId1', 'yourDocumentId2']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Document(s) deleted successfully.',
        'data': {
            'yourDocumentId1': {
                'success': True,
                'id': '6737987049068973',
                'message': 'Deleted Successfully'
            },
            'yourDocumentId2': {
                'success': True,
                'id': '2131443384150904',
                'message': 'Document does not exist'
            }
        }
    }
}
```

#### `moveDocuments`

* Moves documents into a target folder.
* Params: [MoveDocumentsRequest](/docs/api-reference/sdk/models/data-models#movedocumentsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.document import MoveDocumentsRequest

result = sdk.api.documents.moveDocuments(
    MoveDocumentsRequest(
        organizationId='yourOrganizationId',
        documentIds=['yourDocumentId1', 'yourDocumentId2'],
        folderId='target-folder-id'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Documents moved successfully.'
    }
}
```

#### `updateDocumentAccess`

* Updates the access type of one or more documents.
* Params: [UpdateDocumentAccessRequest](/docs/api-reference/sdk/models/data-models#updatedocumentaccessrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.document import UpdateDocumentAccessRequest

result = sdk.api.documents.updateDocumentAccess(
    UpdateDocumentAccessRequest(
        organizationId='yourOrganizationId',
        documentIds=['yourDocumentId'],
        accessType='organizationPrivate'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Updated access for documents successfully.',
        'data': {
            'yourDocumentId': {
                'success': True,
                'accessType': 'organizationPrivate',
                'message': 'Document access type updated.'
            }
        }
    }
}
```

#### `updateDocumentDisableState`

* Enables or disables access to one or more documents.
* Params: [UpdateDocumentDisableStateRequest](/docs/api-reference/sdk/models/data-models#updatedocumentdisablestaterequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.document import UpdateDocumentDisableStateRequest

result = sdk.api.documents.updateDocumentDisableState(
    UpdateDocumentDisableStateRequest(
        organizationId='yourOrganizationId',
        documentIds=['yourDocumentId'],
        disabled=True
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Updated disable state for documents successfully.',
        'data': {
            'yourDocumentId': {
                'success': True,
                'disabled': True,
                'message': 'Document disabled state updated.'
            }
        }
    }
}
```

#### `migrateDocuments`

* Starts a job to migrate a document to a new document ID.
* Params: [MigrateDocumentsRequest](/docs/api-reference/sdk/models/data-models#migratedocumentsrequest)
* Returns: [MigrateDocumentsResponse](/docs/api-reference/sdk/models/data-models#migratedocumentsresponse)

```python theme={null}
from velt_py.models.document import MigrateDocumentsRequest

result = sdk.api.documents.migrateDocuments(
    MigrateDocumentsRequest(
        organizationId='yourOrganizationId',
        documentId='old-doc-id',
        newDocumentId='new-doc-id'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Document migration started successfully.',
        'data': { 'migrationId': 'yourMigrationId' }
    }
}
```

> Note: This call is asynchronous. Poll status via `migrateDocumentsStatus`.

#### `migrateDocumentsStatus`

* Polls the status of a document migration job.
* Params: [MigrateDocumentsStatusRequest](/docs/api-reference/sdk/models/data-models#migratedocumentsstatusrequest)
* Returns: [MigrateDocumentsStatusResponse](/docs/api-reference/sdk/models/data-models#migratedocumentsstatusresponse)

```python theme={null}
from velt_py.models.document import MigrateDocumentsStatusRequest

result = sdk.api.documents.migrateDocumentsStatus(
    MigrateDocumentsStatusRequest(
        organizationId='yourOrganizationId',
        migrationId='yourMigrationId'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Migration status retrieved successfully.',
        'data': {
            'migrationId': 'yourMigrationId',
            'status': 'completed'
        }
    }
}
```

#### `getDocumentsCount`

* Gets the document count for an organization. Optionally exclude folder docs, scope to a folder, or narrow with metadata `filters`.
* Params: `GetDocumentsCountRequest` (from `velt_py.models.document`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.document import GetDocumentsCountRequest

result = sdk.api.documents.getDocumentsCount(
    GetDocumentsCountRequest(
        organizationId='org-123',
        excludeFolderDocs=True,
        folderId='folder-1'
    )
)

# Narrow the count with metadata filters (same shape as getDocuments; max 10).
result = sdk.api.documents.getDocumentsCount(
    GetDocumentsCountRequest(
        organizationId='org-123',
        filters=[
            {'key': 'projectType', 'operator': '==', 'value': 'design'},
            {'key': 'category', 'operator': 'in', 'value': ['a', 'b']},
        ]
    )
)
```

`filters` cannot be combined with `excludeFolderDocs` — the API rejects the combination. When filters are supplied the response data also carries `filtersApplied`; a `False` value means the filtered aggregate could not be computed and `count` is an unfiltered fallback.

### Users

#### `addUsers`

* Adds one or more users to an organization.
* Params: [AddUsersRequest](/docs/api-reference/sdk/models/data-models#addusersrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import AddUsersRequest

result = sdk.api.users.addUsers(
    AddUsersRequest(
        organizationId='yourOrganizationId',
        users=[{'userId': 'yourUserId1', 'name': 'John Doe', 'email': 'john@example.com', 'accessRole': 'editor'}],
        createOrganization=True
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'User(s) processed successfully.',
        'data': {
            'yourUserId1': {
                'success': True,
                'id': '4c250058149d6c9fb8c894c9ef29c790',
                'message': 'User added.'
            }
        }
    }
}
```

#### `getUsers`

* Retrieves users in an organization or document.
* Params: [GetUsersRequest](/docs/api-reference/sdk/models/data-models#getusersrequest)
* Returns: [GetUsersResponse](/docs/api-reference/sdk/models/data-models#getusersresponse)
* Now also supports optional `includeInvolvedDocuments` and `searchKey` (free-text search) filters.

```python theme={null}
from velt_py.models.user_api import GetUsersRequest

result = sdk.api.users.getUsers(
    GetUsersRequest(
        organizationId='yourOrganizationId',
        userIds=['yourUserId'],
        documentId='yourDocumentId',
        pageSize=100,
        allDocuments=True,
        groupByDocumentId=True,
        includeInvolvedDocuments=True,
        searchKey='john'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'User(s) retrieved successfully.',
        'data': [
            {'email': 'userEmail@domain.com', 'name': 'userName', 'userId': 'yourUserId'}
        ],
        'nextPageToken': 'pageToken'
    }
}
```

#### `updateUsers`

* Updates metadata for one or more users.
* Params: [UpdateUsersRequest](/docs/api-reference/sdk/models/data-models#updateusersrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import UpdateUsersRequest

result = sdk.api.users.updateUsers(
    UpdateUsersRequest(
        organizationId='yourOrganizationId',
        users=[{'userId': 'yourUserId1', 'name': 'Jane Doe', 'accessRole': 'viewer'}]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'User(s) processed successfully.',
        'data': {
            'yourUserId1': {
                'success': True,
                'id': '7d87015b055a168b098cf05b870e40ff',
                'message': 'User updated.'
            }
        }
    }
}
```

#### `deleteUsers`

* Removes users from an organization.
* Params: [DeleteUsersRequest](/docs/api-reference/sdk/models/data-models#deleteusersrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import DeleteUsersRequest

result = sdk.api.users.deleteUsers(
    DeleteUsersRequest(
        organizationId='yourOrganizationId',
        userIds=['yourUserId1', 'yourUserId2']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'User(s) deleted successfully.',
        'data': {
            'yourUserId1': {
                'success': True,
                'message': 'User removed.'
            }
        }
    }
}
```

#### `getUsersCount`

* Gets the user count for an organization. Optionally scope to a document, a user, or all documents.
* Params: `GetUsersCountRequest` (from `velt_py.models.user_api`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import GetUsersCountRequest

result = sdk.api.users.getUsersCount(
    GetUsersCountRequest(organizationId='org-123', allDocuments=True, userId='user-1')
)
```

#### `getDocUsers`

* Gets document-level users. Optionally filter by user IDs (max 20; max 5 with `includeInvolvedDocuments`), include involved documents, and paginate.
* Params: `GetDocUsersRequest` (from `velt_py.models.user_api`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import GetDocUsersRequest

result = sdk.api.users.getDocUsers(
    GetDocUsersRequest(organizationId='org-123', userIds=['user-1'], includeInvolvedDocuments=True, pageSize=100)
)
```

#### `addUserInvite`

* Creates a user invitation (organization, document, or folder).
* Params: `AddUserInviteRequest` (from `velt_py.models.user_api`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import AddUserInviteRequest

result = sdk.api.users.addUserInvite(
    AddUserInviteRequest(email='invitee@example.com', organizationId='org-123', type='org_user')
)
```

#### `respondToInvite`

* Responds to a user invitation (accept, reject, or withdraw).
* Params: `RespondUserInviteRequest` (from `velt_py.models.user_api`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import RespondUserInviteRequest

result = sdk.api.users.respondToInvite(
    RespondUserInviteRequest(
        email='invitee@example.com', organizationId='org-123', status='ACCEPTED', type='org_user',
        user={'userId': 'user-1', 'name': 'Invitee'}, authToken='invited-user-auth-token'
    )
)
```

#### `getInvitedUsers`

* Gets invited users for an organization (paginated).
* Params: `GetInvitedUsersRequest` (from `velt_py.models.user_api`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import GetInvitedUsersRequest

invited = sdk.api.users.getInvitedUsers(
    GetInvitedUsersRequest(organizationId='org-123')
)
```

#### `getUserInvitations`

* Gets invitations for a specific user email (paginated).
* Params: `GetUserInvitationsRequest` (from `velt_py.models.user_api`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import GetUserInvitationsRequest

invitations = sdk.api.users.getUserInvitations(
    GetUserInvitationsRequest(organizationId='org-123', email='invitee@example.com')
)
```

#### `getInvitedPendingUsersCount`

* Gets the count of pending invited users.
* Params: `GetInvitedPendingUsersCountRequest` (from `velt_py.models.user_api`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_api import GetInvitedPendingUsersCountRequest

pending = sdk.api.users.getInvitedPendingUsersCount(
    GetInvitedPendingUsersCountRequest(organizationId='org-123', type='org_user')
)
```

### User Groups

#### `addUserGroups`

* Creates one or more user groups inside an organization.
* Params: [AddUserGroupsRequest](/docs/api-reference/sdk/models/data-models#addusergroupsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_group import AddUserGroupsRequest

result = sdk.api.userGroups.addUserGroups(
    AddUserGroupsRequest(
        organizationId='yourOrganizationId',
        organizationUserGroups=[{'groupId': 'yourGroupId', 'groupName': 'Engineering'}],
        createOrganization=True
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Organization User Groups added successfully.',
        'data': {
            'yourGroupId': {
                'success': True,
                'id': '77ab6767b022ad0323ba39c24f12cc95',
                'message': 'Organization user group added.'
            }
        }
    }
}
```

#### `addUsersToGroup`

* Adds users to an existing group.
* Params: [AddUsersToGroupRequest](/docs/api-reference/sdk/models/data-models#adduserstogrouprequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_group import AddUsersToGroupRequest

result = sdk.api.userGroups.addUsersToGroup(
    AddUsersToGroupRequest(
        organizationId='yourOrganizationId',
        organizationUserGroupId='yourGroupId',
        userIds=['yourUserId1', 'yourUserId2']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Added organization users to group successfully.',
        'data': {
            'yourUserId1': {
                'success': True,
                'organizationUserGroupId': 'yourGroupId',
                'message': 'User added to organization user group.'
            }
        }
    }
}
```

#### `deleteUsersFromGroup`

* Removes users from a group, or empties the group entirely when `deleteAll` is `True`.
* Params: [DeleteUsersFromGroupRequest](/docs/api-reference/sdk/models/data-models#deleteusersfromgrouprequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.user_group import DeleteUsersFromGroupRequest

result = sdk.api.userGroups.deleteUsersFromGroup(
    DeleteUsersFromGroupRequest(
        organizationId='yourOrganizationId',
        organizationUserGroupId='yourGroupId',
        userIds=['yourUserId1'],
        deleteAll=False
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Deleted users from group successfully.',
        'data': {
            'yourUserId1': {
                'success': True,
                'organizationUserGroupId': 'yourGroupId',
                'message': 'User deleted from organization user group.'
            }
        }
    }
}
```

### Notifications

<Note>
  **Filtering unknown fields**: `updateNotifications` accepts an optional `filter_unknown_fields: bool = False` keyword (backed by `velt_py.models.field_allowlists`). When `True`, unknown/custom top-level keys are dropped before the request is sent, while open-typed fields (`context`, `metadata`, `entityData`, user objects) pass through whole. Filtering is fail-open: if it errors, the original payload is sent, so a write is never blocked. `addNotifications` is not affected — its top-level fields are already fixed. On `updateNotifications`, `isRead` / `isArchived` are not accepted by `/v2/notifications/update` (the backend strips them anyway), so with the flag on they are dropped client-side.
</Note>

#### `addNotifications`

* Sends a notification to one or more users on a document.
* Params: [AddNotificationsRequest](/docs/api-reference/sdk/models/data-models#addnotificationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.notification_api import AddNotificationsRequest

result = sdk.api.notifications.addNotifications(
    AddNotificationsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        actionUser={'userId': 'user-1', 'name': 'John Doe', 'email': 'john@example.com'},
        displayHeadlineMessageTemplate='{actionUser} commented on your document',
        displayBodyMessage='Check out the new comment',
        notifyUsers=[{'userId': 'user-2', 'name': 'Jane Doe'}],
        createOrganization=True,
        createDocument=True
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Notification added successfully.',
        'data': {
            'success': True,
            'message': 'Notification added successfully.'
        }
    }
}
```

#### `getNotifications`

* Retrieves notifications for a user or document.
* Params: [GetNotificationsRequest](/docs/api-reference/sdk/models/data-models#getnotificationsrequest)
* Returns: [GetNotificationsResponse](/docs/api-reference/sdk/models/data-models#getnotificationsresponse)
* Velt filters results by comment visibility: a notification for a private comment is returned only to users who can see that comment.

```python theme={null}
from velt_py.models.notification_api import GetNotificationsRequest

result = sdk.api.notifications.getNotifications(
    GetNotificationsRequest(
        organizationId='yourOrganizationId',
        userId='user-2',
        pageSize=10,
        order='desc'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Notification(s) retrieved successfully.',
        'data': [
            {
                'id': 'notificationId',
                'notificationSource': 'custom',
                'notificationSourceData': {},
                'actionUser': {'email': 'user@example.com', 'name': 'User Name', 'userId': 'yourUserId'},
                'displayBodyMessage': 'This is body message (Secondary message)',
                'displayHeadlineMessageTemplate': 'Template with {actionUser}, {recipientUser}, {customVar}',
                'displayHeadlineMessageTemplateData': {
                    'actionUser': {'email': 'user@example.com', 'name': 'User Name', 'userId': 'yourUserId'},
                    'recipientUser': {'email': 'recipient@example.com', 'name': 'Recipient Name', 'userId': 'recipientUserId'}
                },
                'metadata': {'apiKey': '...', 'documentId': '...', 'organizationId': '...'},
                'notifyUsers': {'yourNotifyUserId': True},
                'notifyUsersByUserId': {'yourNotifyUserById': True},
                'timestamp': 1722409519944
            }
        ],
        'pageToken': 'nextPageToken'
    }
}
```

#### `updateNotifications`

* Updates fields on existing notifications (e.g., mark as read).
* Params: [UpdateNotificationsRequest](/docs/api-reference/sdk/models/data-models#updatenotificationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)
* Accepts the optional `filter_unknown_fields=True` keyword (see the note above).

```python theme={null}
from velt_py.models.notification_api import UpdateNotificationsRequest

result = sdk.api.notifications.updateNotifications(
    UpdateNotificationsRequest(
        organizationId='yourOrganizationId',
        notifications={'5471488637912692': {'isRead': True}}
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Notification(s) updated successfully.',
        'data': {
            '5471488637912692': {
                'success': True,
                'message': 'Notification updated.'
            }
        }
    }
}
```

#### `deleteNotifications`

* Deletes one or more notifications.
* Params: [DeleteNotificationsRequest](/docs/api-reference/sdk/models/data-models#deletenotificationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.notification_api import DeleteNotificationsRequest

result = sdk.api.notifications.deleteNotifications(
    DeleteNotificationsRequest(
        organizationId='yourOrganizationId',
        notificationIds=['8955243231506071']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Notification(s) deleted successfully.',
        'data': {
            '8955243231506071': {
                'success': True,
                'message': 'Notification deleted.'
            }
        }
    }
}
```

#### `getNotificationConfig`

* Gets notification preferences (inbox/email/slack channels) for a user.
* Params: [GetNotificationConfigRequest](/docs/api-reference/sdk/models/data-models#getnotificationconfigrequest)
* Returns: [GetNotificationConfigResponse](/docs/api-reference/sdk/models/data-models#getnotificationconfigresponse)

```python theme={null}
from velt_py.models.notification_api import GetNotificationConfigRequest

result = sdk.api.notifications.getNotificationConfig(
    GetNotificationConfigRequest(
        organizationId='org1',
        userId='USER_ID1'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'User config fetched successfully.',
        'data': [
            {
                'config': {'inbox': 'ALL', 'email': 'ALL'},
                'metadata': {
                    'organizationId': 'org1',
                    'apiKey': 'API_KEY',
                    'documentId': 'doc1',
                    'userId': 'USER_ID1'
                }
            }
        ]
    }
}
```

#### `setNotificationConfig`

* Sets notification preferences for one or more users.
* Params: [SetNotificationConfigRequest](/docs/api-reference/sdk/models/data-models#setnotificationconfigrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.notification_api import SetNotificationConfigRequest

result = sdk.api.notifications.setNotificationConfig(
    SetNotificationConfigRequest(
        organizationId='org1',
        userIds=['USER_ID1'],
        config={'inbox': 'ALL', 'email': 'MINE'}
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'User config set successfully.',
        'data': {
            'USER_ID1': {
                'success': True,
                'userId': 'USER_ID1',
                'documentId': 'doc1',
                'message': 'User config set successfully.'
            }
        }
    }
}
```

### Comment Annotations

<Note>
  **Filtering unknown fields**: The add/update methods below accept an optional `filter_unknown_fields: bool = False` keyword (backed by `velt_py.models.field_allowlists`). When `True`, request entity collections are filtered to only the fields the Velt API accepts, dropping unknown/custom top-level keys before the request is sent. Open-typed fields (`context`, `metadata`, `entityData`, user objects) pass through whole — their nested contents are never filtered. Filtering is fail-open: if it errors, the original payload is sent, so a write is never blocked.
</Note>

#### `addCommentAnnotations`

* Creates one or more comment annotations on a document.
* Params: [AddCommentAnnotationsRequest](/docs/api-reference/sdk/models/data-models#addcommentannotationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)
* Accepts the optional `filter_unknown_fields=True` keyword (see the note above).

```python theme={null}
from velt_py.models.comment_annotation_api import AddCommentAnnotationsRequest

result = sdk.api.commentAnnotations.addCommentAnnotations(
    AddCommentAnnotationsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        commentAnnotations=[{
            'location': {'id': 'yourLocationId', 'locationName': 'yourLocationName'},
            'commentData': [{
                'commentText': 'Sample Comment',
                'commentHtml': '<div>Sample Comment</div>',
                'from': {'userId': 'yourUserId', 'name': 'yourUserName', 'email': 'yourUserEmail'}
            }]
        }]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Comment Annotation(s) added successfully.',
        'data': {
            '-O0mpUziLcBwzREvZKs6': {
                'success': True,
                'annotationId': '-O0mpUziLcBwzREvZKs6',
                'commentIds': [126535],
                'message': 'Added Successfully'
            }
        }
    }
}
```

#### `getCommentAnnotations`

* Retrieves comment annotations on a document.
* Params: [GetCommentAnnotationsRequest](/docs/api-reference/sdk/models/data-models#getcommentannotationsrequest)
* Returns: [GetCommentAnnotationsResponse](/docs/api-reference/sdk/models/data-models#getcommentannotationsresponse)

```python theme={null}
from velt_py.models.comment_annotation_api import GetCommentAnnotationsRequest

result = sdk.api.commentAnnotations.getCommentAnnotations(
    GetCommentAnnotationsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        statusIds=['OPEN'],
        pageSize=10
    )
)

# Scope a listing to agent-authored annotations. Supply at most one agent filter
# per request: agentId, executionId, agentType, agentSource, agentSuggestions,
# or agentComments.
result = sdk.api.commentAnnotations.getCommentAnnotations(
    GetCommentAnnotationsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        agentSuggestions=True
    )
)
```

<Note>
  Agent filtering is annotation-scoped and requires advanced queries to be enabled on your workspace. When they are not enabled, the API fails closed rather than returning unfiltered results.
</Note>

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Annotation(s) fetched successfully.',
        'data': [
            {
                'annotationId': 'yourAnnotationId',
                'annotationNumber': 2,
                'type': 'comment',
                'createdAt': 1777973713421,
                'lastUpdated': 1777978714209,
                'hasDraftComments': False,
                'context': {'access': {'default': 'velt'}, 'accessFields': ['default:velt']},
                'visibilityConfig': {'type': 'public'},
                'metadata': {'apiKey': '...', 'organizationId': '...', 'documentId': '...'},
                'from': {'userId': 'user123', 'name': 'John Doe', 'email': 'john.doe@example.com'},
                'comments': [
                    {
                        'commentId': 123456,
                        'commentText': 'This is a sample comment text.',
                        'commentHtml': '<p>This is a sample comment text.</p>',
                        'createdAt': 1777973714914,
                        'lastUpdated': '2026-05-05T09:35:15.048Z',
                        'reactionAnnotationIds': ['reactionAnnotationId1'],
                        'from': {'userId': 'user123', 'name': 'John Doe'}
                    }
                ],
                'status': {'id': 'OPEN', 'name': 'Open', 'type': 'default'}
            }
        ],
        'nextPageToken': 'pageToken'
    }
}
```

#### `getCommentAnnotationsCount`

* Counts total and unread annotations across one or more documents.
* Params: [GetCommentAnnotationsCountRequest](/docs/api-reference/sdk/models/data-models#getcommentannotationscountrequest)
* Returns: [GetCommentAnnotationsCountResponse](/docs/api-reference/sdk/models/data-models#getcommentannotationscountresponse)

```python theme={null}
from velt_py.models.comment_annotation_api import GetCommentAnnotationsCountRequest

result = sdk.api.commentAnnotations.getCommentAnnotationsCount(
    GetCommentAnnotationsCountRequest(
        organizationId='ORG_ID',
        documentIds=['DOC_1', 'DOC_2'],
        userId='1.1',
        statusIds=['OPEN', 'IN_PROGRESS']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Comment count retrieved successfully.',
        'data': {
            'DOC_1': {'total': 4, 'unread': 2},
            'DOC_2': {'total': 2, 'unread': 0}
        }
    }
}
```

#### `updateCommentAnnotations`

* Updates annotation fields (e.g., change status).
* Params: [UpdateCommentAnnotationsRequest](/docs/api-reference/sdk/models/data-models#updatecommentannotationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)
* Accepts the optional `filter_unknown_fields=True` keyword (see the note above).

```python theme={null}
from velt_py.models.comment_annotation_api import UpdateCommentAnnotationsRequest

result = sdk.api.commentAnnotations.updateCommentAnnotations(
    UpdateCommentAnnotationsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        annotationIds=['yourAnnotationId1'],
        updatedData={'status': {'id': 'inprogress', 'name': 'In Progress', 'type': 'ongoing'}}
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Annotations updated successfully.',
        'data': {
            'yourAnnotationId1': {
                'success': True,
                'id': 'yourAnnotationId1',
                'message': 'Annotations updated successfully'
            }
        }
    }
}
```

#### `deleteCommentAnnotations`

* Deletes one or more comment annotations.
* Params: [DeleteCommentAnnotationsRequest](/docs/api-reference/sdk/models/data-models#deletecommentannotationsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.comment_annotation_api import DeleteCommentAnnotationsRequest

result = sdk.api.commentAnnotations.deleteCommentAnnotations(
    DeleteCommentAnnotationsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        annotationIds=['yourAnnotationId1', 'yourAnnotationId2']
    )
)

# Agent filters combine, so you can scope a sweep precisely — for example
# "this agent's still-pending suggestions on these pages", which leaves a
# concurrent crawl's freshly-created annotations untouched.
result = sdk.api.commentAnnotations.deleteCommentAnnotations(
    DeleteCommentAnnotationsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        agentId='agent-7',
        agentSuggestions=True,
        agentUrls=['https://app.example.com/page-1', 'https://app.example.com/page-2']
    )
)
```

`agentUrls` matches annotations scoped to **any** of the supplied pages.

<Warning>
  Agent filters require advanced queries to be enabled on your workspace. When they are not enabled — or when none of the supplied `agentUrls` resolve — the API fails closed and deletes nothing, rather than widening to a document-wide delete.
</Warning>

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Annotations deleted successfully.',
        'data': {
            'yourAnnotationId1': {
                'success': True,
                'id': 'yourAnnotationId',
                'message': 'Deleted Successfully'
            },
            'yourAnnotationId2': {
                'success': False,
                'id': 'yourAnnotationId2',
                'message': 'Annotation not found.'
            }
        }
    }
}
```

#### `addComments`

* Adds reply comments to an existing annotation.
* Params: [AddCommentsRequest](/docs/api-reference/sdk/models/data-models#addcommentsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)
* Accepts the optional `filter_unknown_fields=True` keyword (see the note above).

```python theme={null}
from velt_py.models.comment_annotation_api import AddCommentsRequest

result = sdk.api.commentAnnotations.addComments(
    AddCommentsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        annotationId='yourAnnotationId',
        commentData=[{
            'commentText': 'I agree, let me fix this',
            'from': {'userId': 'user-2', 'name': 'Jane Doe'}
        }]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Comment(s) added successfully.',
        'data': [778115]
    }
}
```

#### `getComments`

* Retrieves individual comments inside an annotation.
* Params: [GetCommentsRequest](/docs/api-reference/sdk/models/data-models#getcommentsrequest)
* Returns: [GetCommentsResponse](/docs/api-reference/sdk/models/data-models#getcommentsresponse)

```python theme={null}
from velt_py.models.comment_annotation_api import GetCommentsRequest

result = sdk.api.commentAnnotations.getComments(
    GetCommentsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        annotationId='yourAnnotationId',
        userIds=['yourUserId']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Comments retrieved successfully.',
        'data': [
            {
                'commentId': 123456,
                'type': 'text',
                'commentText': 'This is a sample comment text.',
                'commentHtml': '<p>This is a sample comment text.</p>',
                'createdAt': 1777973714914,
                'lastUpdated': '2026-05-05T09:35:15.048Z',
                'reactionAnnotationIds': ['reactionAnnotationId1'],
                'from': {'userId': 'user123', 'name': 'John Doe', 'email': 'john.doe@example.com'}
            }
        ]
    }
}
```

#### `updateComments`

* Updates fields on individual comments inside an annotation.
* Params: [UpdateCommentsRequest](/docs/api-reference/sdk/models/data-models#updatecommentsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)
* Accepts the optional `filter_unknown_fields=True` keyword (see the note above).

```python theme={null}
from velt_py.models.comment_annotation_api import UpdateCommentsRequest

result = sdk.api.commentAnnotations.updateComments(
    UpdateCommentsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        annotationId='yourAnnotationId',
        commentIds=[607395],
        updatedData={'commentText': 'Updated text', 'commentHtml': '<p>Updated text</p>'}
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Comment updated successfully.',
        'data': {
            '607395': {
                'success': True,
                'id': 607395,
                'message': 'Updated successfully'
            }
        }
    }
}
```

#### `deleteComments`

* Deletes one or more comments from an annotation, or all comments if `commentIds` is omitted.
* Params: [DeleteCommentsRequest](/docs/api-reference/sdk/models/data-models#deletecommentsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.comment_annotation_api import DeleteCommentsRequest

result = sdk.api.commentAnnotations.deleteComments(
    DeleteCommentsRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        annotationId='yourAnnotationId',
        commentIds=[153783, 607395]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Comments(s) deleted successfully.',
        'data': {
            '153783': {
                'success': True,
                'id': 153783,
                'message': 'Deleted successfully'
            },
            '607395': {
                'success': False,
                'id': 607395,
                'message': 'Not found'
            }
        }
    }
}
```

### Activities

<Note>
  **Filtering unknown fields**: The add/update methods below accept an optional `filter_unknown_fields: bool = False` keyword (backed by `velt_py.models.field_allowlists`). When `True`, request entity collections are filtered to only the fields the Velt API accepts, dropping unknown/custom top-level keys before the request is sent. Open-typed fields (`context`, `metadata`, `entityData`, user objects) pass through whole — their nested contents are never filtered. Filtering is fail-open: if it errors, the original payload is sent, so a write is never blocked.
</Note>

```python theme={null}
result = sdk.api.activities.addActivities(
    AddActivitiesRequest(
        organizationId='org-123', documentId='doc-456',
        activities=[{'featureType': 'comment', 'actionType': 'comment.add', 'actionUser': {'userId': 'user-1'}, 'internalTrackingId': 'abc-123'}]
    ),
    filter_unknown_fields=True,
)
# The 'internalTrackingId' key is removed before the request is sent.
```

#### `addActivities`

* Logs one or more activity events.
* Params: [AddActivitiesRequest](/docs/api-reference/sdk/models/data-models#addactivitiesrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)
* Accepts the optional `filter_unknown_fields=True` keyword (see the note above).

```python theme={null}
from velt_py.models.activity_api import AddActivitiesRequest

result = sdk.api.activities.addActivities(
    AddActivitiesRequest(
        organizationId='org-123',
        documentId='doc-456',
        activities=[{
            'featureType': 'comment',
            'actionType': 'comment.add',
            'actionUser': {'userId': 'user-1', 'name': 'User Name', 'email': 'user@example.com'},
            'displayMessageTemplate': '{{user}} added a comment'
        }]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Activity(s) added successfully.',
        'data': {
            'success': True,
            'message': 'Activity(s) added successfully.'
        }
    }
}
```

#### `getActivities`

* Retrieves activities with optional filters and pagination.
* Params: [GetActivitiesRequest](/docs/api-reference/sdk/models/data-models#getactivitiesrequest)
* Returns: [GetActivitiesResponse](/docs/api-reference/sdk/models/data-models#getactivitiesresponse)

```python theme={null}
from velt_py.models.activity_api import GetActivitiesRequest

result = sdk.api.activities.getActivities(
    GetActivitiesRequest(
        organizationId='org-123',
        documentId='doc-456',
        featureTypes=['comment'],
        order='desc',
        pageSize=50
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Activity(s) retrieved successfully.',
        'data': [
            {
                'id': 'activity-001',
                'featureType': 'comment',
                'actionType': 'comment.add',
                'actionUser': {'userId': 'user-1', 'email': 'user@example.com', 'name': 'User Name'},
                'targetEntityId': 'annotation-789',
                'displayMessageTemplate': '{{user}} added a comment',
                'displayMessageTemplateData': {'user': {'userId': 'user-1', 'name': 'User Name'}},
                'metadata': {'apiKey': 'yourApiKey', 'documentId': 'doc-456', 'organizationId': 'org-123'},
                'timestamp': 1722409519944
            }
        ],
        'pageToken': 'nextPageToken'
    }
}
```

#### `updateActivities`

* Updates fields on existing activities.
* Params: [UpdateActivitiesRequest](/docs/api-reference/sdk/models/data-models#updateactivitiesrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)
* Accepts the optional `filter_unknown_fields=True` keyword (see the note above).

```python theme={null}
from velt_py.models.activity_api import UpdateActivitiesRequest

result = sdk.api.activities.updateActivities(
    UpdateActivitiesRequest(
        organizationId='org-123',
        activities=[{
            'id': 'activity-001',
            'displayMessageTemplate': '{{user}} updated the comment'
        }]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Activity(s) updated successfully.',
        'data': {
            'activity-001': {
                'success': True,
                'message': 'Activity updated.'
            }
        }
    }
}
```

#### `deleteActivities`

* Deletes activities by document, target entity, or specific IDs.
* Params: [DeleteActivitiesRequest](/docs/api-reference/sdk/models/data-models#deleteactivitiesrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.activity_api import DeleteActivitiesRequest

result = sdk.api.activities.deleteActivities(
    DeleteActivitiesRequest(
        organizationId='org-123',
        activityIds=['activity-001', 'activity-002']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Activity(s) deleted successfully.',
        'data': {
            'activity-001': {
                'success': True,
                'message': 'Activity deleted.'
            }
        }
    }
}
```

### Access Control

#### `addPermissions`

* Grants resource-level permissions to a user.
* Params: [AddPermissionsRequest](/docs/api-reference/sdk/models/data-models#addpermissionsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.access_control import AddPermissionsRequest

result = sdk.api.accessControl.addPermissions(
    AddPermissionsRequest(
        user={'userId': 'some-user-id'},
        permissions={'resources': [
            {'type': 'organization', 'id': 'YOUR_ORGANIZATION_ID', 'accessRole': 'editor'},
            {'type': 'document', 'id': 'YOUR_DOCUMENT_ID', 'organizationId': 'YOUR_ORGANIZATION_ID', 'accessRole': 'viewer', 'expiresAt': 1728902400}
        ]}
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Permissions added successfully.'
    }
}
```

#### `getPermissions`

* Retrieves permissions for one or more users.
* Params: [GetPermissionsRequest](/docs/api-reference/sdk/models/data-models#getpermissionsrequest)
* Returns: [GetPermissionsResponse](/docs/api-reference/sdk/models/data-models#getpermissionsresponse)

```python theme={null}
from velt_py.models.access_control import GetPermissionsRequest

result = sdk.api.accessControl.getPermissions(
    GetPermissionsRequest(
        organizationId='org1',
        userIds=['1.1'],
        documentIds=['document1']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'User permissions retrieved successfully.',
        'data': {
            '1.1': {
                'folders': {
                    'folder2': {'accessRole': 'editor', 'accessType': 'restricted'}
                },
                'documents': {
                    'document1': {'accessRole': 'viewer', 'accessType': 'restricted'}
                },
                'organization': {
                    'org1': {'accessRole': 'editor'}
                }
            }
        }
    }
}
```

#### `removePermissions`

* Revokes resource-level permissions from a user.
* Params: [RemovePermissionsRequest](/docs/api-reference/sdk/models/data-models#removepermissionsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.access_control import RemovePermissionsRequest

result = sdk.api.accessControl.removePermissions(
    RemovePermissionsRequest(
        userId='USER_ID',
        permissions={'resources': [
            {'type': 'organization', 'id': 'ORGANIZATION_ID'},
            {'type': 'document', 'id': 'DOCUMENT_ID', 'organizationId': 'ORGANIZATION_ID'}
        ]}
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Permissions removed successfully.'
    }
}
```

#### `generateSignature`

* Generates a signed payload for Velt's Permission Provider flow.
* Params: [GenerateSignatureRequest](/docs/api-reference/sdk/models/data-models#generatesignaturerequest)
* Returns: [GenerateSignatureResponse](/docs/api-reference/sdk/models/data-models#generatesignatureresponse)

```python theme={null}
from velt_py.models.access_control import GenerateSignatureRequest

result = sdk.api.accessControl.generateSignature(
    GenerateSignatureRequest(
        permissions=[{
            'userId': 'user123',
            'resourceId': 'document456',
            'type': 'document',
            'hasAccess': True,
            'accessRole': 'viewer',
            'expiresAt': 1759745729823
        }]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Signature generated successfully.',
        'data': { 'signature': 'a1b2c3d4e5f6...' }
    }
}
```

#### `generateToken`

* Generates a Velt JWT token bound to a user and a set of resource permissions.
* Params: [GenerateTokenRequest](/docs/api-reference/sdk/models/data-models#generatetokenrequest)
* Returns: [GenerateTokenResponse](/docs/api-reference/sdk/models/data-models#generatetokenresponse)

```python theme={null}
from velt_py.models.access_control import GenerateTokenRequest

result = sdk.api.accessControl.generateToken(
    GenerateTokenRequest(
        userId='user123',
        userProperties={'isAdmin': False, 'name': 'John Doe', 'email': 'john@example.com'},
        permissions={'resources': [
            {'type': 'organization', 'id': 'org_123', 'accessRole': 'viewer'},
            {'type': 'document', 'id': 'doc_456', 'organizationId': 'org_123', 'accessRole': 'editor', 'expiresAt': 1640995200}
        ]}
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Token generated successfully.',
        'data': { 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }
    }
}
```

### CRDT

#### `addCrdtData`

* Stores CRDT data (text, map, array, or xml) for an editor on a document.
* Params: [AddCrdtDataRequest](/docs/api-reference/sdk/models/data-models#addcrdtdatarequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.crdt import AddCrdtDataRequest

result = sdk.api.crdt.addCrdtData(
    AddCrdtDataRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        editorId='my-collab-note',
        data='Hello, collaborative world!',
        type='text'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'CRDT data added successfully.'
    }
}
```

#### `getCrdtData`

* Retrieves CRDT data for one or all editors on a document.
* Params: [GetCrdtDataRequest](/docs/api-reference/sdk/models/data-models#getcrdtdatarequest)
* Returns: [GetCrdtDataResponse](/docs/api-reference/sdk/models/data-models#getcrdtdataresponse)

```python theme={null}
from velt_py.models.crdt import GetCrdtDataRequest

result = sdk.api.crdt.getCrdtData(
    GetCrdtDataRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        editorId='my-collab-note'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'CRDT data retrieved successfully.',
        'data': [
            {
                'data': 'Hello, collaborative world!',
                'id': 'my-collab-note',
                'lastUpdate': '2025-01-20T10:30:00.000Z',
                'lastUpdatedBy': 'user-123',
                'sessionId': 'session-abc-456'
            }
        ]
    }
}
```

#### `updateCrdtData`

* Updates existing CRDT data for an editor.
* Params: [UpdateCrdtDataRequest](/docs/api-reference/sdk/models/data-models#updatecrdtdatarequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.crdt import UpdateCrdtDataRequest

result = sdk.api.crdt.updateCrdtData(
    UpdateCrdtDataRequest(
        organizationId='yourOrganizationId',
        documentId='yourDocumentId',
        editorId='my-collab-note',
        data='Updated collaborative content!',
        type='text'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'CRDT data updated successfully.'
    }
}
```

#### `deleteCrdtData`

* Deletes CRDT editor data. Optionally target specific `editorIds`; omit to delete all editors for a document.
* Params: `DeleteCrdtDataRequest` (from `velt_py.models.crdt`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.crdt import DeleteCrdtDataRequest

result = sdk.api.crdt.deleteCrdtData(
    DeleteCrdtDataRequest(
        organizationId='org-123',
        documentId='doc-1',
        editorIds=['my-collab-note']
    )
)
```

### Presence

#### `addPresence`

* Adds users to a document's presence list.
* Params: [AddPresenceRequest](/docs/api-reference/sdk/models/data-models#addpresencerequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.presence import AddPresenceRequest

result = sdk.api.presence.addPresence(
    AddPresenceRequest(
        organizationId='org-123',
        documentId='doc-456',
        users=[
            {'userId': 'user-1', 'name': 'John Doe', 'email': 'john@example.com', 'status': 'online'},
            {'userId': 'user-2', 'name': 'Jane Doe', 'status': 'away'}
        ]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Presence added successfully.',
        'data': { 'success': True }
    }
}
```

#### `updatePresence`

* Updates presence status for one or more users on a document.
* Params: [UpdatePresenceRequest](/docs/api-reference/sdk/models/data-models#updatepresencerequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.presence import UpdatePresenceRequest

result = sdk.api.presence.updatePresence(
    UpdatePresenceRequest(
        organizationId='org-123',
        documentId='doc-456',
        users=[{'userId': 'user-1', 'status': 'away'}]
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Presence updated successfully.',
        'data': { 'success': True }
    }
}
```

#### `deletePresence`

* Removes users from a document's presence list.
* Params: [DeletePresenceRequest](/docs/api-reference/sdk/models/data-models#deletepresencerequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.presence import DeletePresenceRequest

result = sdk.api.presence.deletePresence(
    DeletePresenceRequest(
        organizationId='org-123',
        documentId='doc-456',
        userIds=['user-1', 'user-2']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Presence deleted successfully.',
        'data': { 'success': True }
    }
}
```

### Livestate

#### `broadcastEvent`

* Broadcasts an event payload to all clients subscribed to a livestate ID on a document.
* Params: [BroadcastEventRequest](/docs/api-reference/sdk/models/data-models#broadcasteventrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.livestate import BroadcastEventRequest

result = sdk.api.livestate.broadcastEvent(
    BroadcastEventRequest(
        organizationId='org-123',
        documentId='doc-1',
        liveStateDataId='my-live-state',
        data={'status': 'active', 'message': 'Hello World'},
        merge=True
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Event broadcasted successfully.',
        'data': { 'success': True }
    }
}
```

### Recordings

#### `getRecordings`

* Retrieves recording metadata, optionally scoped to a document or set of recording IDs.
* Params: [GetRecordingsRequest](/docs/api-reference/sdk/models/data-models#getrecordingsrequest)
* Returns: [GetRecordingsResponse](/docs/api-reference/sdk/models/data-models#getrecordingsresponse)

```python theme={null}
from velt_py.models.recording import GetRecordingsRequest

result = sdk.api.recordings.getRecordings(
    GetRecordingsRequest(
        organizationId='org-123',
        documentId='doc-456',
        recordingIds=['rec-1'],
        pageSize=10,
        pageToken='encryptedPageToken'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Recorder annotations retrieved successfully.',
        'data': [
            {
                'type': 'recorder',
                'recordingType': 'screen',
                'mode': 'floating',
                'metadata': {
                    'apiKey': 'YOUR_API_KEY',
                    'documentId': 'doc-456',
                    'organizationId': 'org-123'
                },
                'recordedTime': {'duration': 4204.55, 'display': '00:00:04'},
                'displayName': 'Screen Recording 1773814490242.mp4',
                'annotationId': 'ypvmVTROaNU1qP4kq7Cc',
                'attachments': [
                    {
                        'attachmentId': 875113,
                        'url': 'https://storage.googleapis.com/...',
                        'mimeType': 'video/mp4',
                        'name': 'recording_1773814490242.mp4',
                        'type': 'mp4',
                        'size': 103551
                    }
                ],
                'latestVersion': 5
            }
        ],
        'pageToken': 'nextPageToken'
    }
}
```

### Rewriter

#### `askAi`

* Sends a model, prompt, and text to Velt's AI rewriter and returns the transformed text.
* Params: [AskAiRequest](/docs/api-reference/sdk/models/data-models#askairequest)
* Returns: [AskAiResponse](/docs/api-reference/sdk/models/data-models#askairesponse)

```python theme={null}
from velt_py.models.rewriter import AskAiRequest

result = sdk.api.rewriter.askAi(
    AskAiRequest(
        model='gpt-4o',
        prompt='Fix the grammar and spelling in the following text.',
        text='Their going to the store to by some apples.'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'success': True,
        'text': "They're going to the store to buy some apples."
    }
}
```

> Note: This call may take several seconds and has no client-side timeout. Wrap calls in your own timeout if needed.

### GDPR

#### `deleteAllUserData`

* Requests deletion of all data associated with one or more users.
* Params: [DeleteAllUserDataRequest](/docs/api-reference/sdk/models/data-models#deletealluserdatarequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.gdpr import DeleteAllUserDataRequest

result = sdk.api.gdpr.deleteAllUserData(
    DeleteAllUserDataRequest(
        userIds=['user-1', 'user-2'],
        organizationIds=['org-123']
    )
)
```

**Response**

```python theme={null}
{
    'status': 'success',
    'message': 'Data deletion process has been initiated.',
    'data': {
        'jobId': 'dsQuvPmIynANgPLLEhCm',
        'tasksCount': 5
    },
    'statusCode': 202
}
```

> Note: This call is asynchronous and returns a `jobId`. Poll status via `getDeleteUserDataStatus`.

#### `getAllUserData`

* Exports all data associated with a user, paginated.
* Params: [GetAllUserDataRequest](/docs/api-reference/sdk/models/data-models#getalluserdatarequest)
* Returns: [GetAllUserDataResponse](/docs/api-reference/sdk/models/data-models#getalluserdataresponse)

```python theme={null}
from velt_py.models.gdpr import GetAllUserDataRequest

result = sdk.api.gdpr.getAllUserData(
    GetAllUserDataRequest(
        organizationId='org-123',
        userId='user-1',
        pageToken='next-page-token'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Data fetched successfully.',
        'data': {
            'comments': [],       # Up to 100 items per page.
            'reactions': [],      # Up to 100 items per page.
            'recordings': [],     # Up to 100 items per page.
            'notifications': []   # Up to 100 items per page.
        },
        'nextPageToken': 'bhdwdqwjs298e39e479ddkeuw==329'
    }
}
```

#### `getDeleteUserDataStatus`

* Polls the status of a `deleteAllUserData` job.
* Params: [GetDeleteUserDataStatusRequest](/docs/api-reference/sdk/models/data-models#getdeleteuserdatastatusrequest)
* Returns: [GetDeleteUserDataStatusResponse](/docs/api-reference/sdk/models/data-models#getdeleteuserdatastatusresponse)

```python theme={null}
from velt_py.models.gdpr import GetDeleteUserDataStatusRequest

result = sdk.api.gdpr.getDeleteUserDataStatus(
    GetDeleteUserDataStatusRequest(jobId='job-id-from-delete-call')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Data fetched successfully.',
        'data': {
            'isDeleteCompleted': True,
            'tasksLeft': 0,
            'lastTaskCompletedTime': 1748972106739
        }
    }
}
```

### Workspace

#### `createWorkspace`

* Creates a new workspace.
* Params: [CreateWorkspaceRequest](/docs/api-reference/sdk/models/data-models#createworkspacerequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import CreateWorkspaceRequest

result = sdk.api.workspace.createWorkspace(
    CreateWorkspaceRequest(
        ownerEmail='owner@example.com',
        workspaceName='My Workspace',
        name='John Doe'
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Workspace created successfully.',
        'data': {
            'id': 'workspace_abc123',
            'name': 'My Workspace',
            'owner': {
                'email': 'owner@example.com',
                'id': 'owner_id_123',
                'name': 'John Doe',
                'avatar': ''
            },
            'authToken': 'eyJhbGciOiJSUzI1NiIs...',
            'apiKeyList': {
                'velt_api_key_1': {
                    'apiKeyName': 'John Doe Test API Key',
                    'id': 'velt_api_key_1',
                    'type': 'testing'
                }
            }
        }
    }
}
```

#### `getWorkspace`

* Retrieves details about the current workspace.
* Params: [GetWorkspaceRequest](/docs/api-reference/sdk/models/data-models#getworkspacerequest)
* Returns: [GetWorkspaceResponse](/docs/api-reference/sdk/models/data-models#getworkspaceresponse)

```python theme={null}
from velt_py.models.workspace import GetWorkspaceRequest

result = sdk.api.workspace.getWorkspace(GetWorkspaceRequest())
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Workspace retrieved successfully.',
        'data': {
            'id': 'workspace_abc123',
            'name': 'My Workspace',
            'owner': {
                'email': 'owner@example.com',
                'id': 'owner_id_123',
                'name': 'John Doe',
                'avatar': ''
            },
            'authToken': 'eyJhbGciOiJSUzI1NiIs...',
            'apiKeyList': {
                'velt_api_key_1': {
                    'apiKeyName': 'John Doe Test API Key',
                    'id': 'velt_api_key_1',
                    'type': 'testing'
                }
            }
        }
    }
}
```

#### `createApiKey`

* Creates a new API key in the workspace.
* Params: [CreateApiKeyRequest](/docs/api-reference/sdk/models/data-models#createapikeyrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import CreateApiKeyRequest

result = sdk.api.workspace.createApiKey(
    CreateApiKeyRequest(
        ownerEmail='owner@example.com',
        type='production',
        createAuthToken=True,
        apiKeyName='Production Key',
        allowedDomains=['example.com', '*.example.com']
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'API key created successfully.',
        'data': { 'apiKey': 'your_new_api_key' }
    }
}
```

#### `updateApiKey`

* Updates an existing API key (e.g., rename it).
* Params: [UpdateApiKeyRequest](/docs/api-reference/sdk/models/data-models#updateapikeyrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdateApiKeyRequest

result = sdk.api.workspace.updateApiKey(
    UpdateApiKeyRequest(apiKey='velt_api_key_1', apiKeyName='Renamed Key')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'API key updated successfully.',
        'data': None
    }
}
```

#### `getApiKeys`

* Lists all API keys in the workspace.
* Params: [GetApiKeysRequest](/docs/api-reference/sdk/models/data-models#getapikeysrequest)
* Returns: [GetApiKeysResponse](/docs/api-reference/sdk/models/data-models#getapikeysresponse)

```python theme={null}
from velt_py.models.workspace import GetApiKeysRequest

result = sdk.api.workspace.getApiKeys(
    GetApiKeysRequest(pageSize=50, pageToken='next-page-token')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'API keys retrieved successfully.',
        'data': [
            {'id': 'velt_api_key_1', 'apiKeyName': 'Production Key', 'type': 'production'},
            {'id': 'velt_api_key_2', 'apiKeyName': 'Testing Key', 'type': 'testing'}
        ],
        'nextPageToken': 'eyJsYXN0SWQiOiJ2ZWx0X2FwaV9rZXlfMiJ9'
    }
}
```

#### `getApiKeyMetadata`

* Gets metadata for the API key the SDK is authenticated with.
* Params: [GetApiKeyMetadataRequest](/docs/api-reference/sdk/models/data-models#getapikeymetadatarequest)
* Returns: [GetApiKeyMetadataResponse](/docs/api-reference/sdk/models/data-models#getapikeymetadataresponse)
* **Fixed in v0.1.11**: now targets the canonical `/v2/workspace/apikeyconfig/get` endpoint (was `/v2/workspace/apikeymetadata/get`, retained as a legacy backend alias). Method signature is unchanged — no call-site change needed.

```python theme={null}
from velt_py.models.workspace import GetApiKeyMetadataRequest

result = sdk.api.workspace.getApiKeyMetadata(GetApiKeyMetadataRequest())
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'API key metadata retrieved successfully.',
        'data': {
            'defaultDocumentAccessType': 'public',
            'planInfo': {'type': 'sdk test'},
            'ownerEmail': 'owner@example.com'
        }
    }
}
```

#### `resetAuthToken`

* Rotates the auth token associated with an API key.
* Params: [ResetAuthTokenRequest](/docs/api-reference/sdk/models/data-models#resetauthtokenrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import ResetAuthTokenRequest

result = sdk.api.workspace.resetAuthToken(
    ResetAuthTokenRequest(apiKey='velt_api_key_1')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Auth token reset successfully.',
        'data': {
            'apiKey': 'velt_api_key_1',
            'newAuthToken': {
                'token': 'eyJhbGciOiJSUzI1NiIs...',
                'name': 'Default Auth Token',
                'domains': []
            }
        }
    }
}
```

#### `getAuthTokens`

* Lists auth tokens for an API key.
* Params: [GetAuthTokensRequest](/docs/api-reference/sdk/models/data-models#getauthtokensrequest)
* Returns: [GetAuthTokensResponse](/docs/api-reference/sdk/models/data-models#getauthtokensresponse)

```python theme={null}
from velt_py.models.workspace import GetAuthTokensRequest

result = sdk.api.workspace.getAuthTokens(
    GetAuthTokensRequest(apiKey='velt_api_key_1')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Auth tokens retrieved successfully.',
        'data': {
            'apiKey': 'velt_api_key_1',
            'authTokens': [
                {'token': 'eyJhbGciOiJSUzI1NiIs...', 'name': 'Default Auth Token', 'domains': []}
            ]
        }
    }
}
```

#### `addDomains`

* Adds allowed domains to the workspace.
* Params: [AddDomainsRequest](/docs/api-reference/sdk/models/data-models#adddomainsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import AddDomainsRequest

result = sdk.api.workspace.addDomains(
    AddDomainsRequest(domains=['google.com', '*.firebase.com'])
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Domain(s) added successfully to allowed domains.',
        'data': {
            'domainsAdded': ['google.com', '*.firebase.com']
        }
    }
}
```

#### `deleteDomains`

* Removes allowed domains from the workspace.
* Params: [DeleteDomainsRequest](/docs/api-reference/sdk/models/data-models#deletedomainsrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import DeleteDomainsRequest

result = sdk.api.workspace.deleteDomains(
    DeleteDomainsRequest(domains=['google.com', '*.firebase.com'])
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Domain(s) removed successfully from allowed domains.',
        'data': {
            'domainsRemoved': ['google.com', '*.firebase.com']
        }
    }
}
```

#### `getDomains`

* Lists allowed domains for the workspace.
* Params: [GetDomainsRequest](/docs/api-reference/sdk/models/data-models#getdomainsrequest)
* Returns: [GetDomainsResponse](/docs/api-reference/sdk/models/data-models#getdomainsresponse)

```python theme={null}
from velt_py.models.workspace import GetDomainsRequest

result = sdk.api.workspace.getDomains(GetDomainsRequest())
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Allowed domains retrieved successfully.',
        'data': {
            'allowedDomains': ['localhost', '127.0.0.1', 'example.com']
        }
    }
}
```

#### `getEmailStatus`

* Checks email verification status for a workspace owner.
* Params: [GetEmailStatusRequest](/docs/api-reference/sdk/models/data-models#getemailstatusrequest)
* Returns: [GetEmailStatusResponse](/docs/api-reference/sdk/models/data-models#getemailstatusresponse)

```python theme={null}
from velt_py.models.workspace import GetEmailStatusRequest

result = sdk.api.workspace.getEmailStatus(
    GetEmailStatusRequest(ownerEmail='owner@example.com')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Email verified and workspace reactivated',
        'data': { 'verified': True }
    }
}
```

#### `sendLoginLink`

* Sends a login link email to a user.
* Params: [SendLoginLinkRequest](/docs/api-reference/sdk/models/data-models#sendloginlinkrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import SendLoginLinkRequest

result = sdk.api.workspace.sendLoginLink(
    SendLoginLinkRequest(email='user@example.com', continueUrl='https://app.example.com/dashboard')
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Login link sent successfully.',
        'data': None
    }
}
```

#### `getEmailConfig`

* Retrieves the workspace's email service configuration.
* Params: [GetEmailConfigRequest](/docs/api-reference/sdk/models/data-models#getemailconfigrequest)
* Returns: [GetEmailConfigResponse](/docs/api-reference/sdk/models/data-models#getemailconfigresponse)

```python theme={null}
from velt_py.models.workspace import GetEmailConfigRequest

result = sdk.api.workspace.getEmailConfig(GetEmailConfigRequest())
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Email configuration retrieved successfully.',
        'data': {
            'useEmailService': False,
            'emailServiceConfig': {
                'type': 'default',
                'apiKey': '',
                'fromEmail': '',
                'fromCompany': '',
                'commentTemplateId': '',
                'tagTemplateId': ''
            }
        }
    }
}
```

#### `updateEmailConfig`

* Updates the workspace's email service configuration.
* Params: [UpdateEmailConfigRequest](/docs/api-reference/sdk/models/data-models#updateemailconfigrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdateEmailConfigRequest

result = sdk.api.workspace.updateEmailConfig(
    UpdateEmailConfigRequest(
        useEmailService=True,
        emailServiceConfig={'type': 'sendgrid', 'apiKey': 'sg-key', 'fromEmail': 'noreply@example.com'}
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Email configuration updated successfully.',
        'data': None
    }
}
```

#### `getWebhookConfig`

* Retrieves the workspace's webhook configuration.
* Params: [GetWebhookConfigRequest](/docs/api-reference/sdk/models/data-models#getwebhookconfigrequest)
* Returns: [GetWebhookConfigResponse](/docs/api-reference/sdk/models/data-models#getwebhookconfigresponse)

```python theme={null}
from velt_py.models.workspace import GetWebhookConfigRequest

result = sdk.api.workspace.getWebhookConfig(GetWebhookConfigRequest())
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Webhook configuration retrieved successfully.',
        'data': {
            'useWebhookService': False,
            'webhookServiceConfig': {
                'authToken': '',
                'rawNotificationUrl': '',
                'processedNotificationUrl': ''
            }
        }
    }
}
```

#### `updateWebhookConfig`

* Updates the workspace's webhook configuration.
* Params: [UpdateWebhookConfigRequest](/docs/api-reference/sdk/models/data-models#updatewebhookconfigrequest)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdateWebhookConfigRequest

result = sdk.api.workspace.updateWebhookConfig(
    UpdateWebhookConfigRequest(
        useWebhookService=True,
        webhookServiceConfig={
            'authToken': 'webhook-secret',
            'rawNotificationUrl': 'https://example.com/webhook/raw',
            'processedNotificationUrl': 'https://example.com/webhook/processed'
        }
    )
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'message': 'Webhook configuration updated successfully.',
        'data': None
    }
}
```

#### `getRequestedDomains`

* Gets requested (pending) domains.
* Params: `GetRequestedDomainsRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import GetRequestedDomainsRequest

result = sdk.api.workspace.getRequestedDomains(GetRequestedDomainsRequest())
```

#### `createDomainRequest`

* Creates a domain access request.
* Params: `CreateDomainRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import CreateDomainRequest

result = sdk.api.workspace.createDomainRequest(
    CreateDomainRequest(domain='https://example.com')
)
```

#### `acceptRejectAdditionalUrlRequest`

* Accepts or rejects an additional-URL request.
* Params: `AcceptRejectAdditionalUrlRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import AcceptRejectAdditionalUrlRequest

result = sdk.api.workspace.acceptRejectAdditionalUrlRequest(
    AcceptRejectAdditionalUrlRequest(action='accept', id='request-1', clientDocId='doc-1')
)
```

#### `copyApiKey`

* Copies configuration from one API key to another (workspace-scoped).
* Params: `CopyApiKeyRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import CopyApiKeyRequest

result = sdk.api.workspace.copyApiKey(
    CopyApiKeyRequest(sourceApiKey='velt_api_key_1', targetApiKey='velt_api_key_2')
)
```

#### `updateApiKeyMetadata`

* Updates API key app configuration (private comments, JWT, default access, AI keys).
* Params: `UpdateApiKeyMetadataRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdateApiKeyMetadataRequest

result = sdk.api.workspace.updateApiKeyMetadata(
    UpdateApiKeyMetadataRequest(defaultDocumentAccessType='organizationPrivate')
)
```

#### `getNotificationConfig`

* Gets the workspace notification service config.
* Params: `GetWorkspaceNotificationConfigRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import GetWorkspaceNotificationConfigRequest

result = sdk.api.workspace.getNotificationConfig(GetWorkspaceNotificationConfigRequest())
```

#### `updateNotificationConfig`

* Updates the workspace notification service config.
* Params: `UpdateWorkspaceNotificationConfigRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdateWorkspaceNotificationConfigRequest

result = sdk.api.workspace.updateNotificationConfig(
    UpdateWorkspaceNotificationConfigRequest(useNotificationService=True)
)
```

#### `getPermissionProviderConfig`

* Gets the permission provider config.
* Params: `GetPermissionProviderConfigRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import GetPermissionProviderConfigRequest

result = sdk.api.workspace.getPermissionProviderConfig(GetPermissionProviderConfigRequest())
```

#### `updatePermissionProviderConfig`

* Updates the permission provider config.
* Params: `UpdatePermissionProviderConfigRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdatePermissionProviderConfigRequest

result = sdk.api.workspace.updatePermissionProviderConfig(
    UpdatePermissionProviderConfigRequest(usePermissionProvider=True)
)
```

#### `getActivityConfig`

* Gets the activity logs config.
* Params: `GetActivityConfigRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import GetActivityConfigRequest

result = sdk.api.workspace.getActivityConfig(GetActivityConfigRequest())
```

#### `updateActivityConfig`

* Updates the activity logs config.
* Params: `UpdateActivityConfigRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdateActivityConfigRequest

result = sdk.api.workspace.updateActivityConfig(
    UpdateActivityConfigRequest(activityServiceConfig={'isEnabled': True})
)
```

#### `getAdvancedWebhookConfig`

* Gets the advanced (Svix) webhook config.
* Params: `GetAdvancedWebhookConfigRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import GetAdvancedWebhookConfigRequest

result = sdk.api.workspace.getAdvancedWebhookConfig(GetAdvancedWebhookConfigRequest())
```

#### `updateAdvancedWebhookConfig`

* Updates the advanced (Svix) webhook config.
* Params: `UpdateAdvancedWebhookConfigRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdateAdvancedWebhookConfigRequest

result = sdk.api.workspace.updateAdvancedWebhookConfig(
    UpdateAdvancedWebhookConfigRequest(isEnabled=True)
)
```

#### `getAdvancedWebhookEndpoints`

* Lists advanced webhook endpoints.
* Params: `GetAdvancedWebhookEndpointsRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import GetAdvancedWebhookEndpointsRequest

result = sdk.api.workspace.getAdvancedWebhookEndpoints(GetAdvancedWebhookEndpointsRequest())
```

#### `createAdvancedWebhookEndpoint`

* Creates an advanced webhook endpoint.
* Params: `CreateAdvancedWebhookEndpointRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import CreateAdvancedWebhookEndpointRequest

result = sdk.api.workspace.createAdvancedWebhookEndpoint(
    CreateAdvancedWebhookEndpointRequest(url='https://example.com/webhook')
)
```

#### `updateAdvancedWebhookEndpoint`

* Updates an advanced webhook endpoint.
* Params: `UpdateAdvancedWebhookEndpointRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import UpdateAdvancedWebhookEndpointRequest

result = sdk.api.workspace.updateAdvancedWebhookEndpoint(
    UpdateAdvancedWebhookEndpointRequest(endpointId='endpoint-1', url='https://example.com/webhook/v2')
)
```

#### `deleteAdvancedWebhookEndpoint`

* Deletes an advanced webhook endpoint.
* Params: `DeleteAdvancedWebhookEndpointRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import DeleteAdvancedWebhookEndpointRequest

result = sdk.api.workspace.deleteAdvancedWebhookEndpoint(
    DeleteAdvancedWebhookEndpointRequest(endpointId='endpoint-1')
)
```

#### `getAdvancedWebhookEndpointSecret`

* Gets an advanced webhook endpoint's signing secret.
* Params: `GetAdvancedWebhookEndpointSecretRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import GetAdvancedWebhookEndpointSecretRequest

result = sdk.api.workspace.getAdvancedWebhookEndpointSecret(
    GetAdvancedWebhookEndpointSecretRequest(endpointId='endpoint-1')
)
```

#### `ensureWorkspaceAuthToken`

* Ensures a workspace auth token exists (workspace-scoped).
* Params: `EnsureWorkspaceAuthTokenRequest` (from `velt_py.models.workspace`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workspace import EnsureWorkspaceAuthTokenRequest

result = sdk.api.workspace.ensureWorkspaceAuthToken(EnsureWorkspaceAuthTokenRequest())
```

### Workflow

A new service for the Review Workflow Builder / workflow orchestration: definitions, executions, lifecycle events, and step resolution (`/v2/workflow/*`). 14 methods. Request types are imported from `velt_py.models.workflow`.

```python theme={null}
from velt_py.models.workflow import (
    CreateWorkflowDefinitionRequest, GetWorkflowDefinitionRequest, ListWorkflowDefinitionsRequest,
    DispatchExecutionRequest, GetExecutionRequest, ListExecutionsRequest,
    ResolveStepRequest, RecordReviewerDecisionRequest
)
```

#### `createDefinition`

* Creates a workflow definition.
* Params: `CreateWorkflowDefinitionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)
* Every `human` node needs at least one outgoing edge with `on: 'reject'`, either a forward reject route or a reject back-edge. Definitions that omit it are rejected.

<Warning>
  **`loops` is deprecated.** Loop regions are no longer part of the workflow definition contract. The field is still accepted for backward compatibility but is stripped before the request is sent, and setting it raises a `DeprecationWarning`. Express loops as edges instead — an `on: 'reject'` back-edge carrying a `loop` object:

  ```python theme={null}
  edges=[{'from': 'review', 'to': 'draft', 'on': 'reject', 'loop': {'maxIterations': 3}}]
  ```
</Warning>

```python theme={null}
result = sdk.api.workflow.createDefinition(
    CreateWorkflowDefinitionRequest(
        definitionId='doc-approval', name='Document Approval',
        nodes=[
            {'nodeId': 'review', 'type': 'human', 'config': {'reviewers': [{'userId': 'reviewer-1', 'mandatory': True}]}},
            {'nodeId': 'rework', 'type': 'agent', 'config': {'agentId': '__mock__', 'urlPath': 'documentUrl'}}
        ],
        edges=[{'from': 'review', 'to': 'rework', 'on': 'reject'}]
    )
)
```

#### `updateDefinition`

* Updates a workflow definition. Supports optimistic concurrency via `ifVersion`.
* Params: `UpdateWorkflowDefinitionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workflow import UpdateWorkflowDefinitionRequest

result = sdk.api.workflow.updateDefinition(
    UpdateWorkflowDefinitionRequest(
        definitionId='doc-approval',
        name='Document Approval v2',
        nodes=[
            {'nodeId': 'review', 'type': 'human', 'config': {'reviewers': [{'userId': 'reviewer-1', 'mandatory': True}]}},
            {'nodeId': 'rework', 'type': 'agent', 'config': {'agentId': '__mock__', 'urlPath': 'documentUrl'}}
        ],
        edges=[{'from': 'review', 'to': 'rework', 'on': 'reject'}],
        ifVersion=1
    )
)
```

#### `deleteDefinition`

* Deletes a workflow definition.
* Params: `DeleteWorkflowDefinitionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workflow import DeleteWorkflowDefinitionRequest

result = sdk.api.workflow.deleteDefinition(
    DeleteWorkflowDefinitionRequest(definitionId='doc-approval')
)
```

#### `getDefinition`

* Gets a workflow definition.
* Params: `GetWorkflowDefinitionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
result = sdk.api.workflow.getDefinition(GetWorkflowDefinitionRequest(definitionId='doc-approval'))
```

#### `listDefinitions`

* Lists workflow definitions.
* Params: `ListWorkflowDefinitionsRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
result = sdk.api.workflow.listDefinitions(ListWorkflowDefinitionsRequest(pageSize=20))
```

#### `dispatchExecution`

* Dispatches a workflow execution.
* Params: `DispatchExecutionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
result = sdk.api.workflow.dispatchExecution(
    DispatchExecutionRequest(definitionId='doc-approval', idempotencyKey='order-12345', triggerContext={'orderId': '12345'})
)
```

#### `cancelExecution`

* Cancels a workflow execution.
* Params: `CancelExecutionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workflow import CancelExecutionRequest

result = sdk.api.workflow.cancelExecution(CancelExecutionRequest(executionId='execution-id'))
```

#### `getExecution`

* Gets a workflow execution.
* Params: `GetExecutionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
result = sdk.api.workflow.getExecution(GetExecutionRequest(executionId='execution-id'))
```

#### `getExecutionEvents`

* Gets lifecycle events for an execution (paginated).
* Params: `GetExecutionEventsRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workflow import GetExecutionEventsRequest

result = sdk.api.workflow.getExecutionEvents(
    GetExecutionEventsRequest(executionId='execution-id', pageSize=50)
)
```

#### `listExecutions`

* Lists workflow executions.
* Params: `ListExecutionsRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
result = sdk.api.workflow.listExecutions(
    ListExecutionsRequest(definitionId='doc-approval', status='running')
)
```

#### `cancelStep`

* Cancels a step within an execution.
* Params: `CancelStepRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workflow import CancelStepRequest

result = sdk.api.workflow.cancelStep(
    CancelStepRequest(executionId='execution-id', stepId='step-id', actorId='admin-1')
)
```

#### `resolveStep`

* Resolves a step (admin/reviewer override).
* Params: `ResolveStepRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
result = sdk.api.workflow.resolveStep(
    ResolveStepRequest(executionId='execution-id', stepId='step-id', action='force-approve', actorId='admin-1')
)
```

#### `recordAgentResolution`

* Records an agent resolution against a blocking-agent step.
* Params: `RecordAgentResolutionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
from velt_py.models.workflow import RecordAgentResolutionRequest

result = sdk.api.workflow.recordAgentResolution(
    RecordAgentResolutionRequest(executionId='execution-id', stepId='step-id', responseId='response-1', resolution='resolved', actorId='agent-1')
)
```

#### `recordReviewerDecision`

* Records a reviewer decision against a human step.
* Params: `RecordReviewerDecisionRequest` (from `velt_py.models.workflow`)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
result = sdk.api.workflow.recordReviewerDecision(
    RecordReviewerDecisionRequest(executionId='execution-id', stepId='step-id', reviewerId='reviewer-1', decision='approve')
)
```

### Token

#### `getToken`

* Generates a Velt auth token for a user.
* Params: positional — `organizationId`, `userId`, `email` (optional), `isAdmin` (optional)
* Returns: [VeltApiResponse](/docs/api-reference/sdk/models/data-models#veltapiresponse)

```python theme={null}
result = sdk.api.token.getToken(
    organizationId='org-123',
    userId='user-1',
    email='user@example.com',
    isAdmin=False
)
```

**Response**

```python theme={null}
{
    'result': {
        'status': 'success',
        'data': { 'token': 'eyJhbGciOi...' }
    }
}
```

> Note: `getToken` takes positional / keyword arguments — it does NOT accept a dataclass request object. Signature: `getToken(organizationId, userId, email=None, isAdmin=False)`.

### Data Isolation

Every `sdk.api.*` method requires an `organizationId` in its request payload. Velt's API enforces data isolation server-side — requests without a valid `organizationId` will be rejected.

## Error Handling

The SDK raises typed exceptions for different failure modes. All exceptions extend `VeltSDKError`.

| Exception             | When raised                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `VeltSDKError`        | Base class; catch for any SDK-level error                                                                          |
| `VeltValidationError` | SDK-level validation (e.g., missing required config); `sdk.api.*` methods do not validate request payloads locally |
| `VeltTokenError`      | Token generation or authentication failure                                                                         |
| `VeltApiError`        | REST API errors (network failures, unexpected responses)                                                           |

```python theme={null}
from velt_py.exceptions import VeltSDKError, VeltApiError

try:
    result = sdk.api.organizations.addOrganizations(
        AddOrganizationsRequest(organizations=[{'organizationId': 'org-123'}])
    )
except VeltApiError as e:
    print(f'API error: {e.message}')
except VeltSDKError as e:
    print(f'SDK error: {e.message}')
```

Import path: `from velt_py.exceptions import VeltSDKError, VeltValidationError, VeltTokenError, VeltApiError`

Common self-hosting error codes returned in the response `errorCode` field:

* `INVALID_INPUT` — Request data is malformed or missing required fields
* `INTERNAL_ERROR` — Server-side error during processing
* `NOT_FOUND` — Requested resource was not found

## Data Models

Python-SDK-specific dataclasses used by the self-hosting backend. These are not in the shared API reference because they are only relevant to `sdk.selfHosting.*` implementations.

### `PartialCommentAnnotation`

Represents a partial update payload for a comment annotation. Starting in **v0.1.10**, `from`, `assignedTo`, `targetTextRange`, and `resolvedByUserId` are first-class typed fields (previously silent pass-through via `extra_fields`).

```python theme={null}
from velt_py.models import (
    PartialCommentAnnotation,
    PartialTargetTextRange,
    PartialUser,
    PartialComment,
    BaseMetadata,
)
from velt_py.models.comment import UNSET

@dataclass
class PartialCommentAnnotation:
    annotationId: str
    metadata: Optional[BaseMetadata] = None
    comments: Optional[Dict[str, PartialComment]] = None
    from_: Optional[PartialUser] = None             # 'from' on the wire; from_ avoids Python keyword.
    assignedTo: Optional[PartialUser] = None
    targetTextRange: Optional[PartialTargetTextRange] = None
    resolvedByUserId: Any = UNSET                   # Tri-state: UNSET | None | str. See below.
    extra_fields: Optional[Dict[str, Any]] = None   # Catch-all for customer-configured custom keys.
```

**Field notes**

* `from_` — Python alias for the JSON key `from` (reserved keyword). Serialized as `from` in the Mongo document.
* `assignedTo` — Typed as `PartialUser`. Omit to leave unchanged; the Mongo write skips the field when `None`.
* `targetTextRange` — Typed `PartialTargetTextRange` for the selected text snippet a comment is anchored to.
* `resolvedByUserId` — See [UNSET sentinel](#unset-sentinel) for the difference between absent (`UNSET`) and explicit `None`.
* `extra_fields` — Retained as a catch-all because the frontend contract includes `[key: string]: any` for customer-configured `fieldsToRemove` and `additionalFields`.

### `PartialReactionAnnotation`

Represents a partial update payload for a reaction annotation. Starting in **v0.1.12**, the reaction-author field is `from_` (wire key `from`), replacing the former `user` field — aligning with the velt-sdk contract and `PartialCommentAnnotation`.

```python theme={null}
from velt_py.models.reaction import PartialReactionAnnotation
from velt_py.models.user import PartialUser

@dataclass
class PartialReactionAnnotation:
    annotationId: str
    metadata: Optional[BaseMetadata] = None
    icon: Optional[str] = None
    from_: Optional[PartialUser] = None             # 'from' on the wire; from_ avoids the Python keyword. Replaces the former 'user' field.
    extra_fields: Optional[Dict[str, Any]] = None   # Catch-all for customer-configured custom keys.
```

**Field notes**

* `from_` — Python alias for the JSON key `from` (reserved keyword); serialized as `from`. Replaces the former `user` field (renamed in v0.1.12 to match the velt-sdk contract and the comment models). Constructing with `user=` no longer works — use `from_=`.
* Backward-compatible reads: `from_dict()` accepts either the canonical `from` key or the legacy `user` key (`from` wins when both are present) and populates `from_`. `to_dict()` always emits `from`. No data migration is required for reaction documents already stored under `user`.

```python theme={null}
# Legacy document stored under the old `user` key still resolves:
ann = PartialReactionAnnotation.from_dict({'annotationId': 'r-1', 'icon': '+1', 'user': {'userId': 'u-legacy'}})
ann.from_.userId         # 'u-legacy'
ann.to_dict()['from']    # {'userId': 'u-legacy'}  (re-serialized as `from`)
```

```python theme={null}
# Before (v0.1.11 and earlier):
PartialReactionAnnotation(annotationId='r-1', icon='+1', user=PartialUser(userId='u-1'))    # -> {'user': {...}}
# After (v0.1.12):
PartialReactionAnnotation(annotationId='r-1', icon='+1', from_=PartialUser(userId='u-1'))   # -> {'from': {...}}
```

### `PartialTargetTextRange`

Anchors a comment to a selected text snippet.

```python theme={null}
from velt_py.models import PartialTargetTextRange

@dataclass
class PartialTargetTextRange:
    text: str = ''        # The selected text content.
```

### UNSET Sentinel

`UNSET` is a sentinel value exported from `velt_py.models.comment`. It signals that a field was not included in the incoming request payload and should be skipped entirely during the MongoDB write.

```python theme={null}
from velt_py.models.comment import UNSET

# Field absent from payload → UNSET → field is NOT written to Mongo
annotation = PartialCommentAnnotation()
# annotation.resolvedByUserId is UNSET → Mongo $set will not include this field

# Field explicitly set to None → None → field IS written as null to Mongo
annotation = PartialCommentAnnotation(resolvedByUserId=None)
# annotation.resolvedByUserId is None → Mongo $set writes null (frontend unresolve action)
```

| Value   | Payload                     | Mongo write               |
| ------- | --------------------------- | ------------------------- |
| `UNSET` | Field absent                | Field skipped — no change |
| `None`  | Field present, value `null` | Field written as `null`   |

### `BaseMetadata`

Base metadata attached to every comment annotation. Starting in **v0.1.10**, `sdkVersion` and `documentMetadata` are fully modeled and preserved in MongoDB writes. These fields were previously sent by the frontend on every annotation save but silently dropped.

```python theme={null}
@dataclass
class BaseMetadata:
    apiKey: Optional[str] = None
    documentId: Optional[str] = None
    clientDocumentId: Optional[str] = None
    organizationId: Optional[str] = None
    clientOrganizationId: Optional[str] = None
    folderId: Optional[str] = None
    veltFolderId: Optional[str] = None
    documentMetadata: Optional[Dict[str, Any]] = None   # New in v0.1.10.
    sdkVersion: Optional[str] = None                    # New in v0.1.10.
```

No import change is required — `BaseMetadata` is populated automatically from the incoming request payload. The fix applies transparently to all `saveComments` calls.

### `VerifyTokenResult`

Structured result returned by [`sdk.selfHosting.verifyToken`](#resolver-token-verification). New in **v0.1.14**. Module: `velt_py.models.resolver_auth`; exported from `velt_py` and `velt_py.models`.

```python theme={null}
from velt_py.models import VerifyTokenResult

@dataclass
class VerifyTokenResult:
    verified: bool
    claims: Optional[Dict] = None
    error: Optional[str] = None
    errorCode: Optional[str] = None
```

**Field notes**

* `verified` — `True` only when a token was present and fully verified, or a custom callback returned non-`None` claims.
* `claims` — Decoded payload when verified; otherwise `None`.
* `error` — Generic, code-based message; never contains the token or secret.
* `errorCode` — One of: `MISSING_TOKEN`, `EXPIRED`, `INVALID_SIGNATURE`, `CLAIM_MISMATCH`, `ALGORITHM_NOT_ALLOWED`, `KEY_RESOLUTION_FAILED`, `VERIFICATION_FAILED`, `NOT_CONFIGURED`, `DEPENDENCY_MISSING`.

### `CommentResolverSaveEvent`

Enum of comment save-resolver actions the frontend forwards to your save handler. New in **v0.1.14**. A `(str, Enum)` exported from `velt_py` and `velt_py.models`; wire values are byte-identical to the upstream frontend enum.

```python theme={null}
from velt_py import CommentResolverSaveEvent
from velt_py.models import CommentResolverSaveEvent  # same class
```

| Member               | Wire value                              |
| -------------------- | --------------------------------------- |
| `STATUS_CHANGE`      | `comment_annotation.status_change`      |
| `PRIORITY_CHANGE`    | `comment_annotation.priority_change`    |
| `ASSIGN`             | `comment_annotation.assign`             |
| `ACCESS_MODE_CHANGE` | `comment_annotation.access_mode_change` |
| `CUSTOM_LIST_CHANGE` | `comment_annotation.custom_list_change` |
| `APPROVE`            | `comment_annotation.approve`            |
| `ACCEPT`             | `comment.accept`                        |
| `REJECT`             | `comment.reject`                        |
| `SUGGESTION_ACCEPT`  | `comment_annotation.suggestion_accept`  |
| `SUGGESTION_REJECT`  | `comment_annotation.suggestion_reject`  |
| `REACTION_ADD`       | `comment.reaction_add`                  |
| `REACTION_DELETE`    | `comment.reaction_delete`               |
| `SUBSCRIBE`          | `comment_annotation.subscribe`          |
| `UNSUBSCRIBE`        | `comment_annotation.unsubscribe`        |

**Parse order** — `SaveCommentResolverRequest.from_dict` tries `ResolverActions` FIRST (preserving every existing value), then `CommentResolverSaveEvent`, then keeps the raw string for unknown future values.

**Distinctness** — `CommentResolverSaveEvent.REACTION_ADD` (`comment.reaction_add`) is distinct from `ResolverActions.REACTION_ADD` (`reaction.add`, the reaction-resolver action); the parse order routes each correctly.

### `SaveCommentResolverRequest`

The full shared shape is documented in the [API reference](/docs/api-reference/sdk/models/data-models#savecommentresolverrequest). This entry covers only the **v0.1.14** additions — additive; existing payloads parse byte-identically.

```python theme={null}
from velt_py import SaveCommentResolverRequest, CommentResolverSaveEvent

request = SaveCommentResolverRequest.from_dict(data)
if request.targetComment is not None:
    print(request.targetComment.commentId)   # request context — NOT written to MongoDB
sdk.selfHosting.comments.saveComments(request)
```

**Field notes**

* `targetComment: Optional[PartialComment]` — The comment the action occurred on, resolved by the frontend from `commentId`. `from_dict` parses it fail-open (a malformed/partial dict never raises). `to_dict` emits it only when not `None`. `saveComments` NEVER persists it (request context only).
* `event: Optional[Union[ResolverActions, CommentResolverSaveEvent, str]]` — Widened. Every existing `ResolverActions` value still parses to the same member. Parse order: `ResolverActions` first, then [`CommentResolverSaveEvent`](#commentresolversaveevent), then raw string for unknown future values.

## Resources

* [Velt-Py PyPI Package](https://pypi.org/project/velt-py)
* [Self-Host Data Documentation](/docs/self-host-data/overview)
