Skip to main content

Overview

Scope Management allows you to segment data within a chat using custom attributes. This is perfect for multi-tenant applications, playlist-based content, or any scenario where you need to filter queries to specific subsets of documents.
Think of scopes as “tags” or “filters” that you attach to documents and then use to filter queries.

Use Cases

Multi-Tenant SaaS

Filter data by workspace_id or tenant_id

Content Platforms

Organize by playlist_id, album_id, or category

Version Control

Track by version, branch, or environment

Access Control

Segment by user_id, team_id, or permission_level

Scope Configuration

Define Scopes for a Chat

Configure custom scope fields for your chat:
POST /v1/{chat_id}/scopes/configure
curl -X POST https://api.trainlyai.com/v1/chat_abc123/scopes/configure \
  -H "Authorization: Bearer tk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "scopes": [
      {
        "name": "playlist_id",
        "type": "string",
        "required": true,
        "description": "ID of the playlist this document belongs to"
      },
      {
        "name": "workspace_id",
        "type": "string",
        "required": false,
        "description": "Optional workspace identifier"
      },
      {
        "name": "version",
        "type": "number",
        "required": false,
        "description": "Document version number"
      },
      {
        "name": "is_public",
        "type": "boolean",
        "required": false,
        "description": "Whether content is publicly accessible"
      }
    ]
  }'
Request Body
scopes
array
required
Array of scope definitions
Response
{
  "success": true,
  "message": "Scope configuration saved successfully",
  "chat_id": "chat_abc123",
  "scopes": [
    {
      "name": "playlist_id",
      "type": "string",
      "required": true,
      "description": "ID of the playlist this document belongs to"
    },
    {
      "name": "workspace_id",
      "type": "string",
      "required": false,
      "description": "Optional workspace identifier"
    }
  ]
}

Get Scope Configuration

Retrieve current scope configuration:
GET /v1/{chat_id}/scopes
curl -X GET https://api.trainlyai.com/v1/chat_abc123/scopes \
  -H "Authorization: Bearer tk_your_api_key"
Response
{
  "chat_id": "chat_abc123",
  "scopes": [
    {
      "name": "playlist_id",
      "type": "string",
      "required": true,
      "description": "ID of the playlist this document belongs to"
    },
    {
      "name": "workspace_id",
      "type": "string",
      "required": false,
      "description": "Optional workspace identifier"
    }
  ]
}

Delete Scope Configuration

Clear all scope configurations:
DELETE /v1/{chat_id}/scopes
curl -X DELETE https://api.trainlyai.com/v1/chat_abc123/scopes \
  -H "Authorization: Bearer tk_your_api_key"
This only removes the configuration, not the scope data on existing documents.
Response
{
  "success": true,
  "message": "Scope configuration cleared",
  "chat_id": "chat_abc123"
}

Upload with Scopes

Upload File with Scope Values

Attach scope values when uploading documents:
POST /v1/{chat_id}/upload_with_scopes
curl -X POST https://api.trainlyai.com/v1/chat_abc123/upload_with_scopes \
  -H "Authorization: Bearer tk_your_api_key" \
  -F "file=@song_lyrics.pdf" \
  -F 'scope_values={"playlist_id":"playlist_summer_2024","workspace_id":"ws_acme","is_public":true}'
Request Body (Multipart Form)
file
file
required
File to upload (max 5MB)
scope_values
string
required
JSON string of scope key-value pairs matching your configuration
Response
{
  "success": true,
  "filename": "song_lyrics.pdf",
  "file_id": "chat_abc123_song_lyrics.pdf_1609459200",
  "chat_id": "chat_abc123",
  "scope_values": {
    "playlist_id": "playlist_summer_2024",
    "workspace_id": "ws_acme",
    "is_public": true
  },
  "size_bytes": 204800,
  "message": "File uploaded successfully with custom scope values"
}

V1 Upload with Scopes

For V1 authenticated users:
POST /v1/me/chats/files/upload
curl -X POST https://api.trainlyai.com/v1/me/chats/files/upload \
  -H "Authorization: Bearer <USER_OAUTH_ID_TOKEN>" \
  -H "X-App-ID: app_your_app_id" \
  -F "file=@document.pdf" \
  -F 'scope_values={"playlist_id":"playlist_123","category":"research"}'

Query with Scope Filters

Filter Queries by Scopes

Only search within documents matching specific scope values:
POST /v1/{chat_id}/answer_question
curl -X POST https://api.trainlyai.com/v1/chat_abc123/answer_question \
  -H "Authorization: Bearer tk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "What are the song lyrics?",
    "scope_filters": {
      "playlist_id": "playlist_summer_2024",
      "is_public": true
    }
  }'
Request Body
question
string
required
The question to ask
scope_filters
object
Key-value pairs to filter documents. Only documents with matching scope values will be searched.
Response
{
  "answer": "The lyrics include...",
  "context": [
    {
      "chunk_id": "doc1_chunk_0",
      "chunk_text": "Verse 1: ...",
      "score": 0.94
    }
  ],
  "scope_filters_applied": {
    "playlist_id": "playlist_summer_2024",
    "is_public": true
  }
}

V1 Query with Scopes

POST /v1/me/chats/query
curl -X POST https://api.trainlyai.com/v1/me/chats/query \
  -H "Authorization: Bearer <USER_OAUTH_ID_TOKEN>" \
  -H "X-App-ID: app_your_app_id" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "messages=[{\"role\":\"user\",\"content\":\"What songs are in this playlist?\"}]" \
  -d 'scope_filters={"playlist_id":"playlist_summer_2024"}'

Scope Validation

Automatic Validation

Trainly automatically validates scope values against your configuration:
1

Name Validation

Scope names must start with a letter and contain only alphanumeric, underscores, or hyphens (max 64 chars)
2

Type Validation

Values must match the configured type (string, number, or boolean)
3

Required Check

Required scopes must be present on upload
4

Length Limits

String values are limited to 255 characters

Validation Examples

Valid Scope Names
✅ playlist_id
✅ workspace123
✅ user-role
✅ version_2_0
Invalid Scope Names
❌ 123playlist (starts with number)
❌ user@email (invalid character)
❌ workspace$id (invalid character)
❌ my scope (spaces not allowed)

Implementation Examples

Multi-Tenant SaaS

// Upload document for tenant
await fetch("https://api.trainlyai.com/v1/chat_abc/upload_with_scopes", {
  method: "POST",
  headers: {
    Authorization: "Bearer tk_api_key",
  },
  body: formData({
    file: document,
    scope_values: JSON.stringify({
      tenant_id: "tenant_acme",
      workspace_id: "ws_engineering",
      access_level: "internal",
    }),
  }),
});

// Query only tenant's data
const response = await fetch(
  "https://api.trainlyai.com/v1/chat_abc/answer_question",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer tk_api_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      question: "What are our Q4 goals?",
      scope_filters: {
        tenant_id: "tenant_acme",
        workspace_id: "ws_engineering",
      },
    }),
  },
);

Content Platform with Playlists

// Upload song to playlist
await uploadWithScopes({
  file: songFile,
  scope_values: {
    playlist_id: "playlist_workout_2024",
    artist_id: "artist_123",
    genre: "electronic",
    is_explicit: false,
  },
});

// Find songs in specific playlist
const songs = await query({
  question: "List all upbeat songs",
  scope_filters: {
    playlist_id: "playlist_workout_2024",
    is_explicit: false,
  },
});

Version Control System

// Upload documentation for specific version
await uploadWithScopes({
  file: docsFile,
  scope_values: {
    version: 2.1,
    branch: "main",
    environment: "production",
    is_latest: true,
  },
});

// Query specific version docs
const answer = await query({
  question: "How do I configure the cache?",
  scope_filters: {
    version: 2.1,
    environment: "production",
    is_latest: true,
  },
});

Advanced Patterns

Hierarchical Scopes

Combine multiple scopes for fine-grained control:
{
  "organization_id": "org_abc",
  "team_id": "team_engineering",
  "project_id": "proj_backend",
  "environment": "staging"
}

Dynamic Scopes

Change scope values based on user context:
function getScopeFilters(user) {
  return {
    tenant_id: user.tenantId,
    workspace_id: user.activeWorkspace,
    access_level: user.role,
  };
}

const answer = await query({
  question: userQuestion,
  scope_filters: getScopeFilters(currentUser),
});

Scope Inheritance

Parent chat scopes are automatically inherited by subchats in V1 auth:
// Parent chat has scopes: project_id, team_id
// User subchat inherits these scopes automatically
// User can add additional scopes like user_preferences

Performance Considerations

Index Performance

Scopes are indexed in Neo4j for fast filtering

Query Optimization

Use specific scope values instead of wildcards

Cardinality

Lower cardinality scopes perform better

Combination Limits

Limit to 3-5 scope filters per query

Query Performance

// Fast: Specific values
scope_filters: {
  playlist_id: "playlist_123",
  is_public: true
}

// Slower: Many combinations
scope_filters: {
  tag1: "value1",
  tag2: "value2",
  tag3: "value3",
  tag4: "value4",
  tag5: "value5"
}

Best Practices

Design your scope structure before uploading documents. Consider:
  • What entities do you need to filter by?
  • What’s the cardinality of each scope?
  • Which scopes will be queried together?
Only mark scopes as required if they’re truly essential. This provides flexibility for different document types.
Use consistent naming conventions across your application: - snake_case for scope names - Descriptive but concise names - Avoid abbreviations
Maintain documentation of what each scope means and when to use it.

Limitations

Current Limitations: - Maximum 20 scopes per chat - String values limited to 255 characters - Scope names limited to 64 characters - No wildcard matching (must use exact values)

Migration Guide

Adding Scopes to Existing Chat

  1. Configure Scopes - Define your scope schema
  2. Set as Optional - Make scopes optional initially
  3. Re-upload Documents - Re-upload documents with scope values
  4. Verify Filtering - Test queries with scope filters
  5. Make Required - Optionally make scopes required for new uploads
// Step 1: Configure scopes
await configureScopes({
  scopes: [{ name: "project_id", type: "string", required: false }],
});

// Step 2: Re-upload with scopes
for (const doc of existingDocs) {
  await uploadWithScopes({
    file: doc.file,
    scope_values: { project_id: doc.projectId },
  });
}

// Step 3: Update to required
await configureScopes({
  scopes: [{ name: "project_id", type: "string", required: true }],
});

Error Handling

400 Bad Request
error
Invalid scope configuration or values
{
  "detail": "Invalid scope name: user@email. Must contain only alphanumeric, underscores, hyphens."
}
400 Bad Request
error
Required scope missing
{
  "detail": "Required scope 'playlist_id' is missing"
}
400 Bad Request
error
Type mismatch
{
  "detail": "Scope 'version' expects number, got string"
}