Course
Anthropic recently introduced Claude Skills, a task-specific, reusable module that lets Claude load only the expertise a job requires. Instead of long, repetitive prompts, Skills bundles instructions, scripts, and assets so Claude can produce consistent, brand-aligned outputs across the Claude app, Claude Code, and the API.
In this tutorial, we’ll build an “Auto-Invoice Generator” that turns an Excel timesheet into a client-ready invoice (DOCX/PDF). Along the way, you’ll learn how to:
- Create a minimal
SKILL.mdand upload a Skill with supporting assets. - Preprocess data like a timesheet with
pandasinto a predictable JSON payload. - Invoke the Skill through the API with a safe tool-use loop (bash/text-editor tags).
- Capture Claude-generated artifacts (PDF/DOCX)
By the end, you’ll have a portable Skill you can reuse anywhere in the Claude ecosystem.
What Are Claude Skills?
Claude Skills are self-contained task modules that include a SKILL.md (instructions) file, optional scripts, and supporting assets. When a task matches a Skill’s purpose, Claude loads the minimal parts of that Skill to perform the task efficiently. A Skill can be thought of as a packaged expertise you can version, share, and govern across your organization.
Key features of Claude Skills
Claude Skill’s core features explain how they stay fast, reusable, and reliable across the Claude ecosystem. Some of the key features include:
- Composable: You can combine multiple Skills to run multi-step workflows, such as data cleaning, analysis, and report generation.
- Portable: Skills follow a common format, so the same Skill works consistently across the Claude ecosystem.
- Efficient: Claude loads only the minimum components required for the task at hand, which improves both speed and accuracy.
- Code Executable: Skills can execute code in a secure sandbox, enabling deterministic actions like file creation, parsing, and analytics.
When you ask Claude to “create an Excel budget with monthly roll-ups,” it evaluates your request, scans available Skills, and activates only the relevant pieces. The activation is visible in the reasoning view inside the Claude app. Since Skills are versioned components, teams can roll out updates without changing prompts everywhere.
Using Claude Skills Across the Claude Ecosystem
Whether you’re chatting in Claude apps, coding in Claude Code, or automating workflows via the Developer API, the same Skill can be versioned once, centrally managed, and auto-invoked when the task matches its purpose. This leads to consistent quality and faster turnarounds. Thus, one Skill to reusable everywhere.
In this tutorial, we’ll apply an “Auto-Invoice Generator” Skill across the Claude app and the API to parse an Excel file and output a ready-to-send invoice.
Claude App
Claude Skills live in your Claude App and auto-activate when a request matches their purpose. You enable them once in Settings, then just describe the outcome you want. Claude loads the right Skill and shows it in the reasoning view.

Source: Claude Blog
To get started:
- Log in or Sign Up with your Google account and go to Settings.
- Under Capabilities, search for the Skills tab and upload a Skill( usually a SKILL.md) file that can be used in any chat.

Source: Claude Blog
Note: This feature is only available for the Pro, Max, Team, and Enterprise users.
You can also find a list of Skills like algorithmic-art, artifacts-builder, brand-guidelines, canvas-design, internal-comms, mcp-builder, slack-gif-creator, etc, to add in your conversations with Claude.
Click on upload skill and upload a zip file containing the SKILL.md file. Here are the file requirements.
-
ZIP file that includes exactly one SKILL.md file at the root level
-
SKILL.mdcontains a skill name and description formatted in YAML
The SKILL.md file used in this tutorial looks like this:
name: "auto-invoice-generator-monthly-articles"
description: "Generate monthly invoices for written content from simple line items. Produces a branded PDF or editable DOCX/RTF invoice and, optionally, a one-page timesheet if article titles/links are provided."

Once you upload the Skill, Claude automatically recognizes it and opens a new chat where it’s ready to use.

How does this affect usage in chats?
- You don’t have to “turn on” a Skill inside each conversation. When relevant, Claude auto invokes enabled Skills and shows them in the reasoning view for transparency.
- Disabling a Skill prevents it from being considered in any chat until you re-enable it.
- If your organization pins a canonical set of Skills like brand templates, reporting, etc, keeping them enabled ensures consistent outputs every time.



Next, I passed my own timesheet and asked Claude to build an editable invoice.

Claude spotted the Skill on its own, read the Excel file, and returned an editable Word invoice, which can also be exported to PDF or Word. The invoice layout looks clean, and the subtotal and total are also on point. It adhered to the prompt, and both editable DOCX and formatted PDF files styling matched the Skill’s brand settings.
Now let’s run the same example using the API.
Claude Developer Platform (API)
Claude skills can also be accessed via the Claude API. In this section, we’ll explore how we can mimic the Claude app interface via the Claude API.
Step 1: Prerequisites
Start by installing all the runtime dependencies:
-
Anthropicfor the Claude Messages API -
pandasandopenpyxlto read timesheets from Excel -
reportlabto generate a fallback PDF invoice locally
!pip -q install anthropic pandas openpyxl reportlab
Now we have all the dependencies installed. Next, we can configure our API key.
Step 2: Configure your API key
Before we can call the Messages API, we need to authenticate. This step creates a single, reusable anthropic.Client with the API key.
import os, json, sys, re
import anthropic
from datetime import datetime, timedelta
API_KEY = "YOUR_ANTHROPIC_API_KEY"
client = anthropic.Client(api_key=API_KEY)
Log in to Anthropic Console and locate the API keys tab under Settings. Click on Create Key and copy your Anthropic API key.

Source: Anthropic API
Note: If you are using a private notebook for personal use, then add your API Key. Otherwise, use a secure environment variable so your key isn’t exposed in the notebook or logs.
The above code initializes the Anthropic SDK client and sets up the environment. The client object is then reused for all subsequent Messages API calls.
Step 3: Preprocessing data
In this step, we’ll turn our timesheet.xlsxsheet into a clean, predictable JSON object that the Skill can consume. This keeps downstream logic simple and avoids fragile prompt parsing.
def load_invoice_from_timesheet(excel_path):
import pandas as pd
df = pd.read_excel(excel_path)
df.columns = df.columns.str.strip()
invoice_period = "2025-10"
if 'Date' in df.columns:
first_date = str(df['Date'].iloc[0])
date_match = re.search(r'(\d{2})\s+(\w+)\s+(\d{4})', first_date)
if date_match:
month_name = date_match.group(2)
year = date_match.group(3)
month_num = datetime.strptime(month_name[:3], '%b').month
invoice_period = f"{year}-{month_num:02d}"
article_col = next((col for col in df.columns if 'article' in col.lower() and 'name' in col.lower()), None)
amount_col = next((col for col in df.columns if 'amount' in col.lower()), None)
topic_col = next((col for col in df.columns if 'topic' in col.lower()), None)
line_items = []
timesheet_articles = []
for idx, row in df.iterrows():
if pd.isna(row.get(article_col)) or row.get(article_col) == '':
continue
article_name = str(row[article_col]).strip()
amount = row.get(amount_col, 0)
if isinstance(amount, str):
amount = float(amount.replace('$', '').replace(',', '').strip())
line_items.append({
"title": article_name,
"rate_type": "flat",
"qty": 1,
"rate": float(amount)
})
timesheet_articles.append({
"title": article_name,
"topic": str(row.get(topic_col, 'N/A')) if topic_col else 'N/A'
})
return {
"client_name": "Client",
"billing_contact": "billing@example.com",
"invoice_period": invoice_period,
"currency": "USD",
"payment_terms": "Net-30",
"line_items": line_items,
"discount_pct": 0.0,
"tax_pct": 0.0,
"timesheet": timesheet_articles
}
The load_invoice_from_timesheet function converts an uploaded Excel file into a normalized invoice JSON payload.
-
It reads the Excel with pandas and normalizes column headers.
-
The code then infers
invoice_periodfrom the firstDaterow (if present) using a regex and month parsing. -
It finally detects column names for article title, amount, and topic in a case-insensitive manner.
-
This emits two structures:
-
line_items: This is used for billing math (flat rate per article here). -
timesheet: A flat list of{title, topic}entries for an optional appendix. -
The regex
(\d{2})\s+(\w+)\s+(\d{4})expects formats like01 Oct 2025adjust it if your sheet uses another format. -
Missing columns/values are handled by skipping empty rows, or you can extend the code to fail fast if required.
Our input is now ready. Next, we’ll call the Claude Skill via the API to turn the processed timesheet into an editable invoice.
Step 4: Helper functions
In this section, we define two helper functions that simulate the tool execution that Claude requests during a Skill run.
def execute_bash_tool(command):
try:
dangerous = ['rm -rf /', 'sudo', 'chmod', 'mkfs', '> /dev/']
if any(d in command for d in dangerous):
return f"Error: Command blocked for safety: {command}"
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=30,
cwd=tempfile.gettempdir()
)
output = result.stdout if result.stdout else result.stderr
return output if output else "Command executed successfully"
except subprocess.TimeoutExpired:
return "Error: Command timed out"
except Exception as e:
return f"Error executing command: {str(e)}"
def execute_text_editor_tool(params):
try:
command = params.get('command')
path = params.get('path')
if command == 'create':
file_text = params.get('file_text', '')
os.makedirs(os.path.dirname(path) if os.path.dirname(path) else '.', exist_ok=True)
with open(path, 'w') as f:
f.write(file_text)
return f"File created: {path}"
elif command == 'view':
if os.path.exists(path):
with open(path, 'r') as f:
content = f.read()
return content[:1000]
return f"Error: File not found: {path}"
elif command == 'str_replace':
if os.path.exists(path):
with open(path, 'r') as f:
content = f.read()
old_str = params.get('old_str', '')
new_str = params.get('new_str', '')
content = content.replace(old_str, new_str)
with open(path, 'w') as f:
f.write(content)
return f"File updated: {path}"
return f"Error: File not found: {path}"
return f"Unknown command: {command}"
except Exception as e:
return f"Error: {str(e)}"
The execute_bash_tool function simulates a safe bash tool for Skill-driven actions. It first blocks dangerous patterns like rm -rf /, sudo, chmod, mkfs, etc, as a safety gate and runs the command with subprocess.run() in the OS temp directory with an enforced 30-second timeout.
The execute_text_editor_tool function provides a minimal text-editing interface used by Skills. It supports three commands, namely create (to write a new file with file_text), view (to return up to 1,000 chars of a file), and str_replace ( for new_str in place). It also auto-creates parent folders for create, checks file existence for view and str_replace, and writes updates back to disk.
These helpers let you complete a tool-use loop locally with guardrails. Now, Skill can request file edits or shell actions during invoice generation without risking your system.
Step 5: Invoke the Skill via API
This step drives the end-to-end invoice generation through the Claude Messages API. It sends a structured request, enables tool use, iterates through any tool calls Claude requests, and finally collects any generated PDF files from the working and temp directories.
def generate_invoice_with_claude(invoice):
user_text = f"""Generate a professional PDF invoice with the following data:
Client: {invoice['client_name']}
Period: {invoice['invoice_period']}
Currency: {invoice['currency']}
Payment Terms: {invoice['payment_terms']}
Line Items:
{json.dumps(invoice['line_items'], indent=2)}
Timesheet Articles:
{json.dumps(invoice['timesheet'], indent=2)}
Please create a professional PDF invoice with:
1. Invoice header with invoice number (INV-{invoice['invoice_period'].replace('-', '')}-001)
2. Client billing information
3. Line items table with amounts
4. Subtotal and total calculations
5. Timesheet section showing all articles and topics
Save the PDF as: invoice_{invoice['client_name'].lower()}_{invoice['invoice_period']}.pdf
"""
tools = [
{"type": "bash_20250124", "name": "bash"},
{"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"}
]
messages = [{"role": "user", "content": user_text}]
iteration = 0
max_iterations = 15
while iteration < max_iterations:
iteration += 1
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=8192,
tools=tools,
messages=messages
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
break
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
if tool_name == "bash":
result = execute_bash_tool(tool_input.get('command', ''))
elif tool_name == "str_replace_based_edit_tool":
result = execute_text_editor_tool(tool_input)
else:
result = f"Unknown tool: {tool_name}"
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
messages.append({"role": "user", "content": tool_results})
else:
break
pdf_files = []
for file in os.listdir('.'):
if file.endswith('.pdf') and 'invoice' in file.lower():
pdf_files.append(file)
for file in os.listdir(tempfile.gettempdir()):
if file.endswith('.pdf') and 'invoice' in file.lower():
temp_path = os.path.join(tempfile.gettempdir(), file)
import shutil
dest_path = os.path.join('.', file)
shutil.copy2(temp_path, dest_path)
pdf_files.append(file)
return pdf_files
def main():
if len(sys.argv) < 2:
print("Usage: python app.py <timesheet.xlsx>")
sys.exit(1)
excel_file = sys.argv[1]
if not os.path.exists(excel_file):
print(f"Error: File not found: {excel_file}")
sys.exit(1)
try:
invoice = load_invoice_from_timesheet(excel_file)
pdf_files = generate_invoice_with_claude(invoice)
if pdf_files:
for pdf in pdf_files:
print(f"Invoice generated: {os.path.abspath(pdf)}")
else:
print("Error: No PDF file was generated.")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
The generate_invoice_with_claude function sends the normalized invoice to the Claude API and drives Skill execution end-to-end. Here is what the code does:
-
It builds a clear
user_textthat embeds client, period, currency, terms, line items, and timesheet entries, plus explicit output requirements like header, tables, totals, and filename. -
Then it enables tool execution by declaring a
bash_numberandtext_editor_numberwith a tool-aware message loop that posts the request, inspectsstop_reason, and whentool_useis requested, it dispatches to local helpers and returnstool_resultblocks to continue the exchange. -
Finally, it stops on
end_turnor when the loop limit is reached, then scans the working directory and the OS temp directory for files matchinginvoice*.pdf. It copies any temp artifacts back to the project folder and returns the list of discovered PDFs. -
The
mainfunction provides a simple CLI command:python app.py <timesheet.xlsx>, which validates the input path, then runs the pipeline:load_invoice_from_timesheet(...)to build the payload, andgenerate_invoice_with_claude(...)to invoke the Skill and collect PDFs.
With this in place, the API call should produce a client-ready PDF. If no file appears, review the tool_result logs and Skill configuration, then retry with a small test sheet.

Conclusion
You now have a working Auto-Invoice Generator powered by Claude Skills, and the API is capable of turning raw spreadsheets into client-ready invoices. This demo showcases what Skills do best, i.e, portable setup, auto-activation, deterministic code execution, and consistent results across the app and API without recreating prompts in every thread. Claude Skills returns consistent outputs across contexts, which is reusable domain knowledge and code-backed. Though Skills can execute code, you still must enforce security by enabling only trusted Skills, using org-level controls, and reviewing updates before rollout.
To go further, explore Anthropic’s Skills docs and the Console for versioning and upgrades, then start composing multiple Skills to build end-to-end workflows.
Introduction to Claude Models

I am a Google Developers Expert in ML(Gen AI), a Kaggle 3x Expert, and a Women Techmakers Ambassador with 3+ years of experience in tech. I co-founded a health-tech startup in 2020 and am pursuing a master's in computer science at Georgia Tech, specializing in machine learning.

