Build an AI Cost Allocation Tagger with Claude API
Build a tool that scans untagged or inconsistently tagged AWS resources, infers the correct team/project/environment tags from naming patterns and context, and opens a PR to apply them — closing the FinOps visibility gap without a manual tagging sprint.
Every FinOps initiative hits the same wall: cost allocation by team is impossible because 30-40% of resources have missing or inconsistent tags. A manual tagging sprint is tedious and goes stale again within a quarter. This tool infers the right tags from naming conventions, resource relationships, and context, then proposes them for review instead of guessing silently.
Setup
pip install anthropic boto3Resource Scanner
import boto3
import anthropic
import json
client = anthropic.Anthropic()
ec2 = boto3.client("ec2")
rds = boto3.client("rds")
def get_untagged_resources() -> list[dict]:
"""Find resources missing required tags: team, environment, project."""
required_tags = {"team", "environment", "project"}
untagged = []
for reservation in ec2.describe_instances()["Reservations"]:
for instance in reservation["Instances"]:
tags = {t["Key"].lower(): t["Value"] for t in instance.get("Tags", [])}
missing = required_tags - set(tags.keys())
if missing:
untagged.append({
"resource_id": instance["InstanceId"],
"resource_type": "ec2_instance",
"existing_tags": tags,
"missing_tags": list(missing),
"name": tags.get("name", ""),
"vpc_id": instance.get("VpcId"),
"security_groups": [sg["GroupName"] for sg in instance.get("SecurityGroups", [])],
})
return untaggedContext Gathering — What Claude Needs to Infer Correctly
def gather_context(resource: dict) -> dict:
"""Pull surrounding signals that hint at ownership — name patterns,
which VPC/subnet it's in, what security groups it shares with tagged resources."""
context = {"resource": resource}
# Find tagged resources in the same VPC — they often belong to the same team
if resource.get("vpc_id"):
same_vpc = ec2.describe_instances(
Filters=[{"Name": "vpc-id", "Values": [resource["vpc_id"]]}]
)
tagged_neighbors = []
for reservation in same_vpc["Reservations"]:
for instance in reservation["Instances"]:
tags = {t["Key"].lower(): t["Value"] for t in instance.get("Tags", [])}
if "team" in tags:
tagged_neighbors.append({"name": tags.get("name", ""), "team": tags["team"]})
context["tagged_neighbors_in_vpc"] = tagged_neighbors[:5]
return contextTag Inference with Claude
INFER_PROMPT = """Infer the missing tags for this AWS resource based on naming
patterns and surrounding context. Be conservative — only suggest a tag if
you have reasonable evidence, otherwise mark it "uncertain".
Resource: {resource_id} ({resource_type})
Existing tags: {existing_tags}
Missing tags: {missing_tags}
Resource name: {name}
Security groups: {security_groups}
Tagged resources in the same VPC (evidence of team ownership by proximity):
{tagged_neighbors}
Respond with ONLY valid JSON:
{{"inferred_tags": {{"team": "...", "environment": "...", "project": "..."}},
"confidence": "high"|"medium"|"low",
"reasoning": "one sentence explaining the inference"}}
For any tag you cannot infer with reasonable confidence, use "uncertain" as the value."""
def infer_tags(context: dict) -> dict:
resource = context["resource"]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=400,
messages=[{
"role": "user",
"content": INFER_PROMPT.format(
resource_id=resource["resource_id"],
resource_type=resource["resource_type"],
existing_tags=resource["existing_tags"],
missing_tags=resource["missing_tags"],
name=resource["name"],
security_groups=resource["security_groups"],
tagged_neighbors=context.get("tagged_neighbors_in_vpc", []),
)
}]
)
text = response.content[0].text.strip()
if text.startswith("```"):
text = text.split("```")[1].replace("json", "", 1).strip()
return json.loads(text)Output: A Reviewable Terraform Plan, Not a Silent Apply
def generate_tagging_plan(untagged_resources: list[dict]) -> str:
"""Never apply tags directly — always output a reviewable diff."""
plan_lines = ["# Proposed tag changes — review before applying\n"]
for resource in untagged_resources:
context = gather_context(resource)
inference = infer_tags(context)
if inference["confidence"] == "low":
plan_lines.append(f"# SKIPPED (low confidence): {resource['resource_id']} — needs manual review")
continue
plan_lines.append(f"# {resource['resource_id']} ({resource.get('name', 'unnamed')})")
plan_lines.append(f"# Confidence: {inference['confidence']} — {inference['reasoning']}")
for key, value in inference["inferred_tags"].items():
if value != "uncertain":
plan_lines.append(f"aws ec2 create-tags --resources {resource['resource_id']} --tags Key={key},Value={value}")
plan_lines.append("")
return "\n".join(plan_lines)Usage
python cost_tagger.py --output tagging-plan.txt
# tagging-plan.txt:
# # i-0abc123 (worker-node-payments-3)
# # Confidence: high — name pattern matches 4 other tagged "payments" resources in same VPC
# aws ec2 create-tags --resources i-0abc123 --tags Key=team,Value=payments
# aws ec2 create-tags --resources i-0abc123 --tags Key=environment,Value=production
#
# # SKIPPED (low confidence): i-0xyz789 — needs manual reviewWhy This Stays Human-Reviewed, Not Automated
Wrong cost allocation tags are worse than missing tags — a resource mistagged to the wrong team pollutes that team's cost dashboard with a number they'll spend hours investigating and never resolve, because it isn't actually theirs. Low-confidence inferences are explicitly skipped and flagged for manual review rather than guessed at. Treat this as a first draft that cuts a tagging sprint from days to an afternoon of review, not a fire-and-forget automation.
More FinOps and AI tooling? Read our FinOps guide for DevOps engineers and Build AI AWS cost anomaly detector with Claude API.
Today I Fixed
Short real fixes from production — posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
Build an AI AWS Cost Anomaly Detector with Claude API and Cost Explorer
Step-by-step tutorial to build an AI-powered AWS cost anomaly detector using Claude API and AWS Cost Explorer. Automatically identify unusual spending patterns, find the responsible service, and get plain-English explanations with fix recommendations.
Build an AI Cloud Cost Anomaly Detector with Claude API + AWS Cost Explorer
Cloud costs spike without warning. Build a Python bot using AWS Cost Explorer + Claude API that detects anomalies using Z-score analysis and explains the spike in plain English.
Build an Autonomous Cost Anomaly Remediation Agent with Claude API
Detecting a cloud cost spike is the easy part. Build an agent that investigates the anomaly, identifies the specific orphaned resource or misconfiguration causing it with Claude API, and safely remediates the low-risk cases automatically.