Spaces

Kibana Spaces provide multi-tenancy by isolating dashboards, visualizations, connectors, and other resources. The kibana-py client library provides comprehensive space support across all API clients.

Overview

Spaces allow you to:

  • Organize resources by team, project, or environment

  • Implement multi-tenancy with isolated resource access

  • Control feature availability per space

  • Manage permissions and access control

Supported APIs

All the following APIs support space-scoped operations:

  • Actions API: Connectors and actions

  • Saved Objects API: Dashboards, visualizations, index patterns, etc.

  • Spaces API: Space management (inherently space-aware)

Space Support Patterns

The kibana-py library provides two complementary patterns for working with spaces:

  1. Individual Space Parameters: Add space_id parameter to specific method calls

  2. Space-Scoped Clients: Create client instances that automatically operate within a specific space

Both patterns can be used together and provide the same underlying functionality.

Managing Spaces

Creating a Space

from kibana import Kibana

client = Kibana("http://localhost:5601", api_key="your_api_key")

# Create a space
space = client.spaces.create(
    id="marketing",
    name="Marketing Team",
    description="Space for marketing team's dashboards and visualizations",
    color="#FF6B6B",
    initials="MK",
    disabled_features=["dev_tools", "advancedSettings"]
)

print(f"Space URL: http://localhost:5601/s/{space.body['id']}/app/home")
client.close()

Parameters:

  • id (required): Unique identifier for the space (URL-friendly, lowercase, no spaces)

  • name (required): Display name for the space

  • description (optional): Description of the space’s purpose

  • color (optional): Hex color code for the space (e.g., “#FF0000”)

  • initials (optional): Initials to display for the space (1-2 characters)

  • disabled_features (optional): List of feature IDs to disable in this space

Getting a Space

# Get space by ID
space = client.spaces.get(id="marketing")
print(f"Space name: {space.body['name']}")
print(f"Description: {space.body['description']}")

Listing All Spaces

# Get all spaces
spaces = client.spaces.get_all()

for space in spaces.body:
    print(f"- {space['name']} ({space['id']})")
    print(f"  Color: {space.get('color', 'default')}")
    print(f"  Disabled features: {space.get('disabledFeatures', [])}")

Updating a Space

# Update space properties
updated = client.spaces.update(
    id="marketing",
    name="Marketing & Sales Team",
    description="Updated description for combined team",
    color="#00FF00"
)

Deleting a Space

# Delete a space (this also deletes all objects in the space)
client.spaces.delete(id="marketing")
print("Space deleted successfully")

Warning

Deleting a space permanently deletes all saved objects within that space. This action cannot be undone.

Space-Scoped Operations

Individual Space Parameters

Add the space_id parameter to any method that supports space-scoped operations:

from kibana import Kibana

client = Kibana("http://localhost:5601", api_key="your_api_key")

# Create connector in specific space
connector = client.actions.create(
    name="Marketing Webhook",
    connector_type_id=".webhook",
    config={"url": "https://marketing.example.com/webhook"},
    space_id="marketing"  # Operates in 'marketing' space
)

# Create saved object in specific space
dashboard = client.saved_objects.create(
    type="dashboard",
    attributes={"title": "Marketing Dashboard"},
    space_id="marketing"  # Same space as connector
)

# Operations without space_id use the default space
global_connector = client.actions.create(
    name="Global Webhook",
    connector_type_id=".webhook",
    config={"url": "https://global.example.com/webhook"}
    # No space_id = default space
)

client.close()

Space-Scoped Clients

Create a client instance that automatically operates within a specific space:

from kibana import Kibana

# Create main client
client = Kibana("http://localhost:5601", api_key="your_api_key")

# Create space-scoped client (validates space exists immediately)
marketing_client = client.space("marketing")

# All operations automatically use the marketing space
connector = marketing_client.actions.create(
    name="Marketing Webhook",
    connector_type_id=".webhook",
    config={"url": "https://marketing.example.com/webhook"}
    # No space_id needed - automatically uses 'marketing'
)

dashboard = marketing_client.saved_objects.create(
    type="dashboard",
    attributes={"title": "Marketing Dashboard"}
    # No space_id needed - automatically uses 'marketing'
)

client.close()

Overriding Space Context

Even with space-scoped clients, you can override the space for specific operations:

marketing_client = client.space("marketing")

# This connector goes to marketing space (default)
marketing_connector = marketing_client.actions.create(
    name="Marketing Webhook",
    connector_type_id=".webhook",
    config={"url": "https://marketing.example.com/webhook"}
)

# Override to use sales space for this operation
sales_connector = marketing_client.actions.create(
    name="Sales Webhook",
    connector_type_id=".webhook",
    config={"url": "https://sales.example.com/webhook"},
    space_id="sales"  # Override default space
)

# Use default space for this operation
global_connector = marketing_client.actions.create(
    name="Global Webhook",
    connector_type_id=".webhook",
    config={"url": "https://global.example.com/webhook"},
    space_id=None  # Explicitly use default space
)

Validation and Caching

Automatic Space Validation

By default, the client validates that spaces exist before performing operations:

# This will validate that 'marketing' space exists
connector = client.actions.create(
    name="Test Connector",
    connector_type_id=".index",
    config={"index": "test"},
    space_id="marketing"  # Validates space exists
)

# If space doesn't exist, raises SpaceNotFoundError
from kibana.exceptions import SpaceNotFoundError

try:
    connector = client.actions.create(
        name="Test Connector",
        connector_type_id=".index",
        config={"index": "test"},
        space_id="nonexistent"
    )
except SpaceNotFoundError as e:
    print(f"Space not found: {e.space_id}")

Validation Caching

Space validation results are cached for 5 minutes by default to improve performance:

# First call validates space (API call to /api/spaces/space/marketing)
connector1 = client.actions.create(
    name="Connector 1",
    connector_type_id=".index",
    config={"index": "test1"},
    space_id="marketing"
)

# Second call uses cached result (no additional API call)
connector2 = client.actions.create(
    name="Connector 2",
    connector_type_id=".index",
    config={"index": "test2"},
    space_id="marketing"  # Uses cached validation
)

Disabling Validation

For performance-critical scenarios, you can disable validation:

# Disable validation when creating space-scoped client
fast_client = client.space("marketing", validate=False)
connector = fast_client.actions.create(
    name="Fast Connector",
    connector_type_id=".index",
    config={"index": "test"}
    # No validation performed
)

# Disable validation for specific operation
connector = client.actions.create(
    name="Fast Connector",
    connector_type_id=".index",
    config={"index": "test"},
    space_id="marketing",
    validate_space=False  # Skip validation for this operation
)

Multi-Tenancy Patterns

Team-Based Isolation

# Create spaces for different teams
teams = ["engineering", "marketing", "sales", "support"]

for team in teams:
    client.spaces.create(
        id=team,
        name=f"{team.capitalize()} Team",
        description=f"Space for {team} team resources"
    )

# Create team-specific resources
engineering_client = client.space("engineering")
engineering_connector = engineering_client.actions.create(
    name="Engineering Alerts",
    connector_type_id=".slack",
    secrets={"webhookUrl": "https://hooks.slack.com/engineering"}
)

marketing_client = client.space("marketing")
marketing_dashboard = marketing_client.saved_objects.create(
    type="dashboard",
    attributes={"title": "Marketing Metrics"}
)

Environment-Based Isolation

# Create spaces for different environments
environments = {
    "development": {"color": "#00FF00", "initials": "DV"},
    "staging": {"color": "#FFA500", "initials": "ST"},
    "production": {"color": "#FF0000", "initials": "PR"}
}

for env_id, config in environments.items():
    client.spaces.create(
        id=env_id,
        name=f"{env_id.capitalize()} Environment",
        description=f"Resources for {env_id} environment",
        color=config["color"],
        initials=config["initials"]
    )

# Use environment-specific clients
dev_client = client.space("development")
prod_client = client.space("production")

# Development resources
dev_connector = dev_client.actions.create(
    name="Dev Webhook",
    connector_type_id=".webhook",
    config={"url": "https://dev.example.com/webhook"}
)

# Production resources (isolated from dev)
prod_connector = prod_client.actions.create(
    name="Prod Webhook",
    connector_type_id=".webhook",
    config={"url": "https://prod.example.com/webhook"}
)

Project-Based Isolation

# Create space for a specific project
project_space = client.spaces.create(
    id="project-alpha",
    name="Project Alpha",
    description="Dashboards and alerts for Project Alpha",
    color="#9B59B6",
    initials="PA",
    disabled_features=["dev_tools"]  # Restrict features
)

# Create project-specific client
project_client = client.space("project-alpha")

# All project resources in isolated space
project_connector = project_client.actions.create(
    name="Project Alpha Alerts",
    connector_type_id=".slack",
    secrets={"webhookUrl": "https://hooks.slack.com/project-alpha"}
)

project_dashboard = project_client.saved_objects.create(
    type="dashboard",
    attributes={"title": "Project Alpha Dashboard"}
)

Performance Considerations

Validation Overhead

Space validation adds minimal overhead:

  • First validation: ~10-50ms (API call to check space existence)

  • Cached validations: <1ms (cache lookup)

  • No validation: 0ms overhead

When to Disable Validation

Consider disabling validation in these scenarios:

  1. High-frequency operations: When making many requests per second

  2. Known valid spaces: When you’re certain the space exists

  3. Performance-critical paths: When every millisecond matters

  4. Batch operations: When processing large numbers of items

# High-frequency scenario
fast_client = client.space("marketing", validate=False)
for i in range(1000):
    connector = fast_client.actions.create(
        name=f"Connector {i}",
        connector_type_id=".index",
        config={"index": f"test-{i}"}
        # No validation overhead
    )

Error Handling

Space-Specific Exceptions

from kibana.exceptions import SpaceNotFoundError, InvalidSpaceIdError

try:
    connector = client.actions.create(
        name="Test Connector",
        connector_type_id=".index",
        config={"index": "test"},
        space_id="nonexistent"
    )
except SpaceNotFoundError as e:
    print(f"Space '{e.space_id}' does not exist")
    # Handle space not found
except InvalidSpaceIdError as e:
    print(f"Invalid space ID format: '{e.space_id}'")
    # Handle invalid space ID format

Enhanced Error Context

All API errors include space context when relevant:

try:
    connector = client.actions.get(
        id="nonexistent-connector",
        space_id="marketing"
    )
except NotFoundError as e:
    print(f"Error: {e.message}")
    # Output: "[Space: marketing] Connector not found: nonexistent-connector"

Best Practices

1. Choose the Right Pattern

Use Individual Space Parameters When:

  • Making occasional space-scoped operations

  • Working with multiple spaces in the same code

  • Need fine-grained control over each operation

# Good for mixed operations
global_connector = client.actions.create(name="Global", ...)
marketing_connector = client.actions.create(name="Marketing", ..., space_id="marketing")
sales_connector = client.actions.create(name="Sales", ..., space_id="sales")

Use Space-Scoped Clients When:

  • Most operations target the same space

  • Building space-specific functionality

  • Want to avoid repeating space_id parameters

# Good for space-focused operations
marketing_client = client.space("marketing")
connector = marketing_client.actions.create(name="Webhook", ...)
dashboard = marketing_client.saved_objects.create(type="dashboard", ...)
visualization = marketing_client.saved_objects.create(type="visualization", ...)

2. Validation Strategy

Enable Validation (Default) When:

  • Developing and testing applications

  • Working with user-provided space IDs

  • Space existence is uncertain

Disable Validation When:

  • High-frequency operations (>100 requests/second)

  • Batch processing large datasets

  • Spaces are guaranteed to exist

  • Performance is critical

3. Resource Management

Clean up resources in the correct spaces:

def create_and_cleanup_demo():
    # Create resources in specific space
    marketing_client = client.space("marketing")

    connector = marketing_client.actions.create(
        name="Demo Connector",
        connector_type_id=".index",
        config={"index": "demo"}
    )

    dashboard = marketing_client.saved_objects.create(
        type="dashboard",
        attributes={"title": "Demo Dashboard"}
    )

    try:
        # Use resources
        result = marketing_client.actions.execute(
            id=connector.body["id"],
            params={"documents": [{"message": "demo"}]}
        )
        return result
    finally:
        # Clean up in same space
        marketing_client.actions.delete(id=connector.body["id"])
        marketing_client.saved_objects.delete(
            type="dashboard",
            id=dashboard.body["id"]
        )

4. Feature Control

Use disabled_features to control what’s available in each space:

# Restrict features for external users
external_space = client.spaces.create(
    id="external-users",
    name="External Users",
    description="Limited access space for external users",
    disabled_features=[
        "dev_tools",
        "advancedSettings",
        "indexPatterns",
        "savedObjectsManagement"
    ]
)

# Full features for internal team
internal_space = client.spaces.create(
    id="internal-team",
    name="Internal Team",
    description="Full access space for internal team",
    disabled_features=[]  # All features enabled
)

Troubleshooting

Space Not Found Errors

Problem: SpaceNotFoundError: Space not found: marketing

Solutions:

# Check if space exists
try:
    space = client.spaces.get(id="marketing")
    print(f"Space exists: {space.body['name']}")
except NotFoundError:
    print("Space does not exist")
    # Create the space
    client.spaces.create(
        id="marketing",
        name="Marketing Team"
    )

Permission Denied

Problem: AuthorizationException: Forbidden

Solutions:

# Check your permissions
try:
    spaces = client.spaces.get_all()
    print("Available spaces:")
    for space in spaces.body:
        print(f"  - {space['id']}: {space['name']}")
except AuthorizationException:
    print("No permission to list spaces")
    print("Check your API key or user permissions")

Resources Not Found in Expected Space

Problem: Created resource in one space, can’t find it in another

Solution:

# Resources are isolated by space
marketing_client = client.space("marketing")
sales_client = client.space("sales")

# Create in marketing space
connector = marketing_client.actions.create(
    name="Marketing Connector",
    connector_type_id=".index",
    config={"index": "marketing"}
)

# Won't be found in sales space
try:
    sales_connector = sales_client.actions.get(id=connector.body["id"])
except NotFoundError:
    print("Connector not found in sales space (expected)")

# Will be found in marketing space
marketing_connector = marketing_client.actions.get(id=connector.body["id"])
print(f"Found in marketing space: {marketing_connector.body['name']}")

Next Steps