Guides

Python SDK Limit Tags API

Overview

This guide explains how to manage limit tags in Pay-i using the Python SDK. Limit tags provide a flexible way to categorize, organize, and filter your spending limits, helping you implement more sophisticated cost control strategies. For a conceptual understanding of limit tags and their role in the Pay-i platform, please refer to the Limit Tags page.

For a detailed API reference with complete parameter and return type information, see the Python SDK Limit Tags API Reference.

When to Use Limit Tags

You'll work with limit tags in the Pay-i Python SDK for several important purposes:

  1. Organization: Categorize limits by department, project, team, or business unit
  2. Filtering: Easily find and manage related limits by filtering on specific tags
  3. Billing Attribution: Track spending across organizational boundaries or cost centers
  4. Reporting: Generate spending reports segmented by custom business dimensions
  5. Lifecycle Management: Mark limits for specific time periods, initiatives, or campaigns

Limit Tags in the Pay-i Developer Portal

Tags can be assigned to limits in two ways:

  1. Programmatically through the Python SDK (as demonstrated in this guide)
  2. Directly through the Pay-i developer portal UI (as explained in the Limit Tags guide)

Once you've assigned tags to your limits, they become powerful organizational tools in the Pay-i developer portal:

  • Tags appear as columns in the Limits management view, making it easy to identify limit purposes at a glance
  • You can edit tags directly in the portal by selecting a limit and using the editing interface
  • Tags become available as filters in dashboards and reports throughout the portal
  • You can quickly filter limits by specific tags to analyze spending patterns across teams or projects
  • Tags provide consistent organizing principles across your entire Pay-i implementation

This makes tags especially powerful for teams managing large numbers of limits, as they enable quick filtering in the UI without requiring additional API calls from your application.

Common Workflows

Working with the Pay-i limit tags API involves several common patterns for organizing and categorizing your spending limits. This section walks you through these workflows with practical code examples. While this guide demonstrates programmatic management of tags, all operations can also be performed through the Pay-i developer portal UI. When writing code that interacts with limits, remember that tags might change if other users modify them through the UI.

The examples below demonstrate how to:

  1. Create a limit with initial tags
  2. Add tags to existing limits
  3. View tags associated with a limit
  4. Update tags on a limit
  5. Remove tags from a limit

These examples build on each other to show a complete workflow, with each code snippet assuming you have initialized the client with your API key.

Note: These examples use the Python SDK's client objects (Payi and AsyncPayi), which provide a resource-based interface to the Pay-i API. For details on client initialization and configuration, see the Pay-i Client Initialization guide.

Setting Up a Tagged Limit Structure

Let's start by creating a limit with initial tags to establish a foundation for our tagging system:

from payi import Payi

# Initialize the Pay-i client
client = Payi()  # API key will be loaded from PAYI_API_KEY environment variable

# Step 1: Create a limit with initial tags
response = client.limits.create(
    limit_name="Marketing Department Budget",
    max=5000.0,
    limit_type="block",  # Block requests when limit is reached
    threshold=0.80,      # Notify at 80% of limit
    limit_tags=["marketing", "department", "2024"]
)

# Step 2: Store the limit ID for future reference
# You'll need this ID to check status, update tags, or access the limit
limit = response.limit
stored_limit_id = limit.limit_id
print(f"Created '{limit.limit_name}' with tags: {limit.limit_tags}")
print(f"Limit ID: {stored_limit_id}")

Expected output:

Created 'Marketing Department Budget' with tags: ['marketing', 'department', '2024']
Limit ID: lim_1234567890

Viewing Limit Tags

After creating a limit with tags, you may need to view the tags associated with it:

# Step 3: View the tags on our new limit
response = client.limits.tags.list(limit_id=stored_limit_id)
if response.limit_tags:
    print(f"Limit has {len(response.limit_tags)} tags: {', '.join(response.limit_tags)}")
else:
    print("Limit has no tags")

Expected output:

Limit has 3 tags: marketing, department, 2024

Adding Tags to an Existing Limit

As your tagging strategy evolves, you'll often need to add new tags to existing limits:

# Step 4: Add campaign-specific tags to our existing limit
response = client.limits.tags.create(
    limit_id=stored_limit_id,
    limit_tags=["campaign_q2_2024", "social_media"]
)
print(f"Updated tags: {response.limit_tags}")

# Verify the new set of tags
updated_tags = client.limits.tags.list(limit_id=stored_limit_id)
print(f"Limit now has {len(updated_tags.limit_tags)} tags:")
for tag in updated_tags.limit_tags:
    print(f"  - {tag}")

Expected output:

Updated tags: ['marketing', 'department', '2024', 'campaign_q2_2024', 'social_media']
Limit now has 5 tags:
  - marketing
  - department
  - 2024
  - campaign_q2_2024
  - social_media

Updating Tags

When your organizational structure changes, you may want to completely replace the existing tags with a new set:

# Step 5: Replace all existing tags with a new taxonomy
response = client.limits.tags.update(
    limit_id=stored_limit_id,
    limit_tags=["mktg", "digital", "2024_q2", "social_campaigns"]
)
print(f"Tags completely replaced. New tags: {response.limit_tags}")

Expected output:

Tags completely replaced. New tags: ['mktg', 'digital', '2024_q2', 'social_campaigns']

Removing Tags

When campaigns end or priorities shift, you may need to remove specific tags while keeping others:

# Step 6: Remove specific tags that are no longer relevant
response = client.limits.tags.remove(
    limit_id=stored_limit_id,
    limit_tags=["2024_q2"]  # Remove just the quarterly tag
)
print(f"After removing '2024_q2': {response.limit_tags}")

# Step 7: In some cases, you might want to clear all tags
clear_response = client.limits.tags.delete(limit_id=stored_limit_id)
print(f"All tags removed. Current tags: {clear_response.limit_tags}")

Expected output:

After removing '2024_q2': ['mktg', 'digital', 'social_campaigns']
All tags removed. Current tags: []

Note: For more detailed guidance on when to remove tags versus when to delete limits entirely, see the Tag Management Best Practices section later in this guide.

Finding and Filtering Limits by Tags

One of the main benefits of tags is the ability to find and group limits based on their tags. Let's explore this powerful capability:

# Step 8: Create several limits with a consistent tagging strategy
departments = ["marketing", "engineering", "sales", "support"]
dept_limit_ids = {}

for department in departments:
    response = client.limits.create(
        limit_name=f"{department.capitalize()} Department Budget",
        max=10000.0,
        limit_type="allow",  # For monitoring purposes
        threshold=0.85,
        limit_tags=[department, "department", "2024_budget"]
    )
    dept_limit_ids[department] = response.limit.limit_id
    print(f"Created {department} budget with id: {response.limit.limit_id}")

# Step 9: Find all limits with a specific tag
all_limits = client.limits.list()
department_limits = [limit for limit in all_limits if hasattr(limit, "limit_tags") and "department" in limit.limit_tags]

print(f"\nFound {len(department_limits)} limits with the 'department' tag:")
for limit in department_limits:
    print(f"  {limit.limit_name}: {limit.limit_tags}")

Expected output:

Created marketing budget with id: lim_1234567891
Created engineering budget with id: lim_1234567892
Created sales budget with id: lim_1234567893
Created support budget with id: lim_1234567894

Found 4 limits with the 'department' tag:
  Marketing Department Budget: ['marketing', 'department', '2024_budget']
  Engineering Department Budget: ['engineering', 'department', '2024_budget']
  Sales Department Budget: ['sales', 'department', '2024_budget']
  Support Department Budget: ['support', 'department', '2024_budget']

Adapting Tags to Organizational Changes

When your organization evolves, your tagging strategy should adapt. For example, during a department restructuring:

# Step 10: After a department reorganization, update tags
client.limits.tags.update(
    limit_id=dept_limit_ids["marketing"],
    limit_tags=["marketing", "digital_marketing_team", "2024_reorganization"]
)

# Verify the updated tags
updated_tags = client.limits.tags.list(limit_id=dept_limit_ids["marketing"])
print(f"Marketing department's new tags: {updated_tags.limit_tags}")

Expected output:

Marketing department's new tags: ['marketing', 'digital_marketing_team', '2024_reorganization']

Real-World Example: Implementing a Cost Allocation System

Let's pull everything together into a comprehensive end-to-end example of implementing a cost allocation system using limit tags to track spending across different departments and projects.

from payi import Payi
import datetime

# Initialize the Pay-i client
client = Payi()

print("---- Building a Cost Allocation System with Pay-i Limit Tags ----")

# Step 1: Set up department budgets with consistent tagging scheme
def setup_department_budgets():
    print("\nšŸ“Š Setting up department budgets...")
    departments = {
        "marketing": 5000.0,
        "engineering": 10000.0,
        "product": 7500.0,
        "research": 8000.0
    }
    
    current_quarter = f"q{(datetime.datetime.now().month-1)//3+1}_{datetime.datetime.now().year}"
    limit_ids = {}
    
    for dept, budget in departments.items():
        # Create a limit with department tags
        response = client.limits.create(
            limit_name=f"{dept.capitalize()} Department Budget",
            max=budget,
            limit_type="allow",  # For tracking purposes
            threshold=0.80,
            limit_tags=[dept, "department", current_quarter]
        )
        limit_ids[dept] = response.limit.limit_id
        print(f"  Created {dept} budget: ${budget:.2f}, ID: {response.limit.limit_id}")
    
    return limit_ids

# Step 2: Add project tags to department limits
def tag_projects_to_departments(dept_limit_ids):
    print("\nšŸ·ļø Adding project-specific tags to each department...")

    # Marketing projects
    marketing_tags = ["social_campaign", "content_production", "analytics"]
    print(f"  Adding to marketing: {marketing_tags}")
    client.limits.tags.create(
        limit_id=dept_limit_ids["marketing"],
        limit_tags=marketing_tags
    )
    
    # Engineering projects
    engineering_tags = ["backend_api", "frontend_redesign", "infrastructure"]
    print(f"  Adding to engineering: {engineering_tags}")
    client.limits.tags.create(
        limit_id=dept_limit_ids["engineering"],
        limit_tags=engineering_tags
    )
    
    # Get updated tags for verification
    for dept, limit_id in dept_limit_ids.items():
        tags = client.limits.tags.list(limit_id=limit_id).limit_tags
        print(f"  {dept} department tags: {tags}")

# Step 3: Track usage against tagged limits
def track_usage_with_tags(dept_limit_ids):
    print("\nšŸ“ˆ Recording AI usage against tagged department limits...")
    
    # Simulate usage for different departments and projects
    for dept, limit_id in dept_limit_ids.items():
        if dept == "marketing":
            # Track social media campaign costs
            client.ingest.units(
                category="system.openai",
                resource="gpt-4o-2024-05-13",
                units={"text": {"input": 10000, "output": 5000}},
                limit_ids=[limit_id],
                metadata={"project": "social_campaign"}
            )
            print(f"  Recorded usage for {dept} - social_campaign")
        
        elif dept == "engineering":
            # Track API development costs
            client.ingest.units(
                category="system.openai",
                resource="gpt-4o-2024-05-13",
                units={"text": {"input": 20000, "output": 8000}},
                limit_ids=[limit_id],
                metadata={"project": "backend_api"}
            )
            print(f"  Recorded usage for {dept} - backend_api")

# Step 4: Generate a spending report by department
def generate_spending_report(dept_limit_ids):
    print("\nšŸ“ Generating spending report by department...")
    print("-----------------------------")
    
    total_spent = 0
    for dept, limit_id in dept_limit_ids.items():
        limit = client.limits.retrieve(limit_id=limit_id).limit
        spent = limit.totals.cost.total.base
        total_spent += spent
        budget = limit.max
        usage_pct = (spent / budget) * 100 if budget > 0 else 0
        
        print(f"  {dept.capitalize()}: ${spent:.2f} of ${budget:.2f} budget ({usage_pct:.1f}%)")
    
    print(f"\n  Total company spending: ${total_spent:.2f}")

# Execute the workflow
dept_limit_ids = setup_department_budgets()
tag_projects_to_departments(dept_limit_ids)
track_usage_with_tags(dept_limit_ids)
generate_spending_report(dept_limit_ids)

print("\nāœ… Cost allocation system implementation complete!")

Expected output:

---- Building a Cost Allocation System with Pay-i Limit Tags ----

šŸ“Š Setting up department budgets...
  Created marketing budget: $5000.00, ID: lim_2224
  Created engineering budget: $10000.00, ID: lim_2225
  Created product budget: $7500.00, ID: lim_2226
  Created research budget: $8000.00, ID: lim_2227

šŸ·ļø Adding project-specific tags to each department...
  Adding to marketing: ['social_campaign', 'content_production', 'analytics']
  Adding to engineering: ['backend_api', 'frontend_redesign', 'infrastructure']
  marketing department tags: ['marketing', 'department', 'q2_2024', 'social_campaign', 'content_production', 'analytics']
  engineering department tags: ['engineering', 'department', 'q2_2024', 'backend_api', 'frontend_redesign', 'infrastructure']
  product department tags: ['product', 'department', 'q2_2024']
  research department tags: ['research', 'department', 'q2_2024']

šŸ“ˆ Recording AI usage against tagged department limits...
  Recorded usage for marketing - social_campaign
  Recorded usage for engineering - backend_api

šŸ“ Generating spending report by department...
-----------------------------
  Marketing: $1.75 of $5000.00 budget (0.0%)
  Engineering: $3.28 of $10000.00 budget (0.0%)
  Product: $0.00 of $7500.00 budget (0.0%)
  Research: $0.00 of $8000.00 budget (0.0%)

  Total company spending: $5.03

āœ… Cost allocation system implementation complete!

This comprehensive example demonstrates how to:

  1. Create department-level budgets with consistent tagging
  2. Add project-specific tags to each department budget
  3. Track usage against those tagged limits
  4. Generate spending reports by department

Understanding Tag Behavior

When working with limit tags, it's important to understand their relationship with limits:

  • Tags are attributes of limits, providing a way to categorize and filter them
  • When you modify tags, you're changing how current and historical usage data can be filtered
  • Changing a limit's tags affects all past and future data associated with that limit

For example, if you have six months of usage data associated with a limit tagged "marketing," and you then remove that tag, those historical records won't appear in searches for "marketing" tagged limits.

Tag Management Best Practices

As your usage of limits and tags evolves, you'll need to make decisions about managing your tagging structure. There are two distinct operations to consider:

When to Remove Tags vs. When to Delete Limits

Removing Tags (using remove() or update()) is appropriate when:

  • A specific classification is no longer relevant (e.g., a campaign has ended)
  • You're reorganizing your tagging scheme while keeping the limits active
  • You want to maintain the limit and its history but update its categorization
  • Tags were applied incorrectly and need correction
# Remove campaign-specific tags at the end of a quarter
client.limits.tags.remove(
    limit_id="lim_1234567890",
    limit_tags=["q2_2024"]  # Just remove the quarterly tag
)

Deleting Limits based on tags requires more careful consideration:

  • This is a destructive operation that removes the entire limit, not just the tags
  • Any limits deleted will no longer track usage or enforce spending limits
  • All historical data and configuration associated with those limits is lost

Cleaning Up Orphaned or Testing Limits

When working with tags, you'll often encounter scenarios where you need to clean up limits that were created for testing, development, or temporary purposes. The most effective strategy is to use a designated "cleanup" tag (like "temp" or "test") for limits that are meant to be temporary.

This function demonstrates how to safely delete only those limits that have a single specific tag and no other tags. This is particularly useful for:

  • Cleaning up testing or development limits without affecting production limits
  • Removing temporary limits that were created for specific experiments
  • Performing maintenance tasks that target only "orphaned" limits
  • Implementing a tag-based lifecycle policy (e.g., automatically deleting limits tagged only as "deprecated")

By targeting only limits with a single tag, you ensure that multi-tagged limits that serve multiple purposes are preserved. This is much safer than batch-deleting all limits with a certain tag, as that approach might inadvertently remove limits that are still needed for other purposes.

This approach is particularly valuable in automated environments, where scripts in CI/CD pipelines might create numerous temporary limits during testing, QA, or staging processes. Using the SDK in these scenarios provides a programmatic way to clean up environment-specific limits that would be impractical to manage through the UI.

# Function to safely delete limits that have ONLY the specified tag
def delete_limits_with_single_tag(tag, require_confirmation=True):
    """Delete limits that have ONLY the specified tag and no other tags."""
    # First, identify all limits with the tag
    all_limits = client.limits.list()
    matching_limits = [limit for limit in all_limits
                      if hasattr(limit, "limit_tags") and tag in limit.limit_tags]
    
    if not matching_limits:
        print(f"No limits found with tag: {tag}")
        return
    
    # Filter to only limits that have exactly one tag (the specified tag)
    single_tag_limits = []
    multi_tag_limits = []
    
    for limit in matching_limits:
        if len(limit.limit_tags) == 1:
            single_tag_limits.append(limit)
        else:
            multi_tag_limits.append(limit)
    
    # Summary of what was found
    print(f"Found {len(matching_limits)} limits with tag '{tag}':")
    print(f"  - {len(single_tag_limits)} limits with ONLY the '{tag}' tag")
    print(f"  - {len(multi_tag_limits)} limits with multiple tags (will be preserved)")
    
    if not single_tag_limits:
        print(f"No limits found with just the '{tag}' tag. Nothing to delete.")
        return
    
    # List the limits that would be deleted
    print("\nLimits that will be deleted:")
    for limit in single_tag_limits:
        print(f"  - {limit.limit_name} (ID: {limit.limit_id})")
    
    # List the limits that will be preserved
    if multi_tag_limits:
        print("\nLimits that will be preserved (have multiple tags):")
        for limit in multi_tag_limits:
            other_tags = [t for t in limit.limit_tags if t != tag]
            print(f"  - {limit.limit_name} (ID: {limit.limit_id})")
            print(f"    Also tagged with: {other_tags}")
    
    # Confirmation step
    if require_confirmation:
        confirmation = input(f"\nType 'DELETE' to confirm deletion of {len(single_tag_limits)} limits: ")
        if confirmation != "DELETE":
            print("Deletion cancelled")
            return
    
    # Proceed with deletion
    deleted_count = 0
    for limit in single_tag_limits:
        try:
            client.limits.delete(limit_id=limit.limit_id)
            print(f"āœ… Deleted: {limit.limit_name} (ID: {limit.limit_id})")
            deleted_count += 1
        except Exception as e:
            print(f"āŒ Error deleting {limit.limit_name}: {str(e)}")
    
    print(f"\nDeleted {deleted_count} limits that had only the '{tag}' tag")
    print(f"Preserved {len(multi_tag_limits)} limits with multiple tags")

# Example usage:
# delete_limits_with_single_tag("test", require_confirmation=True)

For conceptual information about limit tags and how they can be used to categorize and organize your limits, see Limit Tags.

API Reference

For detailed information on all the methods, parameters, and response types provided by the client.limits.tags resource, please refer to the Python SDK Limit Tags API Reference.

The reference documentation includes:

  • Complete method signatures with all parameters
  • Return type structures for all response types
  • Detailed explanations of parameter behavior
  • REST API endpoint mappings
  • Examples for each method

This separate reference guide complements the workflow examples provided in this document, offering a more technical and comprehensive view of the limit tags API.