Cursus
Anthropic introduceerde onlangs Claude Skills, een taakspecifieke, herbruikbare module waarmee Claude alleen de expertise laadt die een klus vereist. In plaats van lange, repetitieve prompts bundelen Skills instructies, scripts en assets zodat Claude consistente, merkconforme output kan leveren in de Claude-app, Claude Code en de API.
In deze tutorial bouwen we een “Auto-Invoice Generator” die een Excel-urenstaat omzet in een factuur klaar voor de klant (DOCX/PDF). Onderweg leer je hoe je:
- Een minimale
SKILL.mdmaakt en een Skill met ondersteunende assets uploadt. - Data zoals een urenstaat vooraf verwerkt met
pandasnaar een voorspelbare JSON-payload. - De Skill aanroept via de API met een veilige tool-use-loop (bash/text-editor-tags).
- Door Claude gegenereerde artefacten vastlegt (PDF/DOCX)
Aan het eind heb je een draagbare Skill die je overal in het Claude-ecosysteem kunt hergebruiken.
Wat zijn Claude Skills?
Claude Skills zijn zelfstandige taakmodules die een SKILL.md- (instructie-)bestand, optionele scripts en ondersteunende assets bevatten. Wanneer een taak past bij het doel van een Skill, laadt Claude de minimale onderdelen van die Skill om de taak efficiënt uit te voeren. Je kunt een Skill zien als verpakte expertise die je kunt versiebeheerden, delen en beheren binnen je organisatie.
Belangrijkste kenmerken van Claude Skills
De kernfuncties van Claude Skills verklaren hoe ze snel, herbruikbaar en betrouwbaar blijven in het Claude-ecosysteem. Enkele kernpunten:
- Composeerbaar: je kunt meerdere Skills combineren om meerstaps-workflows te draaien, zoals datacleaning, analyse en rapportage.
- Draagbaar: Skills volgen een gemeenschappelijk formaat, waardoor dezelfde Skill consistent werkt in het hele Claude-ecosysteem.
- Efficiënt: Claude laadt alleen de minimale componenten die nodig zijn voor de taak, wat zowel snelheid als nauwkeurigheid verbetert.
- Code-uitvoerbaar: Skills kunnen code uitvoeren in een beveiligde sandbox, voor deterministische acties zoals bestanden maken, parsen en analyses.
Als je Claude vraagt om “een Excelbudget met maandelijkse roll-ups te maken”, beoordeelt het je verzoek, scant beschikbare Skills en activeert alleen de relevante onderdelen. De activatie is zichtbaar in de redeneerweergave in de Claude-app. Omdat Skills geversioneerde componenten zijn, kunnen teams updates uitrollen zonder overal prompts aan te passen.
Claude Skills gebruiken in het hele Claude-ecosysteem
Of je nu chat in Claude-apps, codeert in Claude Code of workflows automatiseert via de Developer API: dezelfde Skill kun je één keer versies geven, centraal beheren en automatisch laten aanroepen als de taak bij het doel past. Dit zorgt voor consistente kwaliteit en snellere doorlooptijden. Eén Skill dus, overal herbruikbaar.
In deze tutorial passen we een “Auto-Invoice Generator”-Skill toe in de Claude-app en de API om een Excel-bestand te parseren en een verzendklare factuur te produceren.
Claude-app
Claude Skills leven in je Claude-app en worden automatisch geactiveerd wanneer een verzoek bij hun doel past. Je schakelt ze eenmalig in via Instellingen en beschrijft dan gewoon het resultaat dat je wilt. Claude laadt de juiste Skill en toont die in de redeneerweergave.

Bron: Claude Blog
Aan de slag:
- Log in of maak een account aan met je Google-account en ga naar Instellingen.
- Zoek onder Capabilities het tabblad Skills en upload een Skill- (meestal een SKILL.md-)bestand dat in elke chat gebruikt kan worden.

Bron: Claude Blog
Let op: deze functie is alleen beschikbaar voor Pro-, Max-, Team- en Enterprise-gebruikers.
Je vindt ook een lijst met Skills zoals algorithmic-art, artifacts-builder, brand-guidelines, canvas-design, internal-comms, mcp-builder, slack-gif-creator, enz. om aan je gesprekken met Claude toe te voegen.
Klik op upload skill en upload een zip-bestand dat het SKILL.md-bestand bevat. Dit zijn de bestandsvereisten.
-
ZIP-bestand met precies één SKILL.md-bestand op rootniveau
-
SKILL.mdbevat een skillnaam en beschrijving geformatteerd in YAML
Het SKILL.md-bestand dat in deze tutorial wordt gebruikt, ziet er zo uit:
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."

Zodra je de Skill uploadt, herkent Claude die automatisch en opent een nieuwe chat waarin hij klaarstaat voor gebruik.

Wat betekent dit voor gebruik in chats?
- Je hoeft een Skill niet in elk gesprek “aan te zetten”. Wanneer relevant roept Claude ingeschakelde Skills automatisch aan en toont ze in de redeneerweergave voor transparantie.
- Als je een Skill uitschakelt, wordt die in geen enkele chat meer meegenomen totdat je hem weer inschakelt.
- Als je organisatie een canonieke set Skills vastzet, zoals merktemplates, rapportage, enz., zorgt ze ingeschakeld houden voor elke keer consistente output.


Vervolgens gaf ik mijn eigen urenstaat door en vroeg ik Claude om een bewerkbare factuur te maken.

Claude herkende de Skill zelf, las het Excel-bestand en leverde een bewerkbare Word-factuur op, die ook naar PDF of Word kan worden geëxporteerd. De lay-out van de factuur is strak en subtotaal en totaal kloppen ook. Het hield zich aan de prompt, en zowel de bewerkbare DOCX als de opgemaakte PDF waren in lijn met de merkinstellingen van de Skill.
Laten we nu hetzelfde voorbeeld via de API draaien.
Claude Developer Platform (API)
Claude-skills zijn ook toegankelijk via de Claude API. In dit deel bekijken we hoe we de Claude-appinterface kunnen nabootsen via de Claude API.
Stap 1: Vereisten
Begin met het installeren van alle runtime-afhankelijkheden:
-
Anthropicvoor de Claude Messages API -
pandasenopenpyxlom urenstaten uit Excel te lezen -
reportlabom lokaal een fallback-PDF-factuur te genereren
!pip -q install anthropic pandas openpyxl reportlab
Nu hebben we alle afhankelijkheden geïnstalleerd. Vervolgens kunnen we onze API-sleutel configureren.
Stap 2: Configureer je API-sleutel
Voordat we de Messages API kunnen aanroepen, moeten we authenticeren. Deze stap maakt één herbruikbare anthropic.Client met de API-sleutel.
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 op Anthropic Console en ga naar het tabblad API keys onder Instellingen. Klik op Create Key en kopieer je Anthropic API-sleutel.

Bron: Anthropic API
Let op: gebruik je een privé-notebook voor persoonlijk gebruik, voeg dan je API-sleutel toe. Gebruik anders een beveiligde omgevingsvariabele, zodat je sleutel niet zichtbaar is in de notebook of logs.
De bovenstaande code initialiseert de Anthropic SDK-client en zet de omgeving op. Het clientobject wordt vervolgens hergebruikt voor alle volgende Messages API-calls.
Stap 3: Data preprocessen
In deze stap zetten we onze timesheet.xlsx-sheet om naar een schoon, voorspelbaar JSON-object dat de Skill kan gebruiken. Dit houdt logica stroomafwaarts eenvoudig en voorkomt kwetsbare 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
}
De functie load_invoice_from_timesheet zet een geüpload Excel-bestand om in een genormaliseerde factuur-JSON-payload.
-
Leest de Excel met pandas en normaliseert kolomnamen.
-
Leidt vervolgens
invoice_periodaf uit de eersteDate-rij (indien aanwezig) met een regex en maandparsing. -
Detecteert tenslotte kolomnamen voor artikeltitel, bedrag en topic, hoofdletterongevoelig.
-
Dit levert twee structuren op:
-
line_items: wordt gebruikt voor de factuurcalculatie (hier vast tarief per artikel). -
timesheet: een vlakke lijst met{title, topic}-items voor een optionele bijlage. -
De regex
(\d{2})\s+(\w+)\s+(\d{4})verwacht formaten als01 Oct 2025; pas hem aan als je sheet een ander formaat gebruikt. -
Ontbrekende kolommen/waarden worden afgehandeld door lege rijen over te slaan, of breid de code uit om desgewenst snel te falen.
Onze input is nu klaar. Vervolgens roepen we de Claude Skill aan via de API om de verwerkte urenstaat om te zetten in een bewerkbare factuur.
Stap 4: Helperfuncties
In dit deel definiëren we twee helperfuncties die de toolexecutie simuleren die Claude aanvraagt tijdens een 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)}"
De functie execute_bash_tool simuleert een veilige bashtool voor Skill-gedreven acties. Eerst blokkeert die gevaarlijke patronen zoals rm -rf /, sudo, chmod, mkfs enz. als veiligheidsmaatregel en voert het commando uit met subprocess.run() in de tijdelijke OS-map met een time-out van 30 seconden.
De functie execute_text_editor_tool biedt een minimale tekstbewerkingsinterface die door Skills wordt gebruikt. Hij ondersteunt drie commando’s: create (om een nieuw bestand te schrijven met file_text), view (om tot 1.000 tekens van een bestand terug te geven) en str_replace (vervangt in-place door new_str). Hij maakt ook automatisch bovenliggende mappen aan voor create, controleert op aanwezigheid van bestanden voor view en str_replace, en schrijft updates terug naar schijf.
Met deze helpers kun je lokaal een tool-use-loop afronden met vangrails. Zo kan de Skill tijdens het genereren van de factuur bestandsbewerkingen of shellacties aanvragen zonder je systeem te riskeren.
Stap 5: Roep de Skill aan via de API
Deze stap regelt de end-to-end-factuurgeneratie via de Claude Messages API. Hij stuurt een gestructureerd verzoek, schakelt toolexecutie in, itereert door alle tool-calls die Claude aanvraagt en verzamelt ten slotte alle gegenereerde PDF-bestanden uit de werk- en tijdelijke mappen.
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()
De functie generate_invoice_with_claude stuurt de genormaliseerde factuur naar de Claude API en regelt de Skill-executie van begin tot eind. Dit doet de code:
-
Bouwt een duidelijke
user_textdie klant, periode, valuta, voorwaarden, line items en timesheet-items bevat, plus expliciete outputvereisten zoals header, tabellen, totalen en bestandsnaam. -
Schakelt vervolgens toolexecutie in door een
bash_numberentext_editor_numberte declareren met een tool-bewuste message-loop die het verzoek post,stop_reasoninspecteert en bijtool_usedispatcht naar lokale helpers entool_result-blokken terugstuurt om de uitwisseling voort te zetten. -
Stopt tenslotte bij
end_turnof wanneer de looplimiet is bereikt, scant dan de werkmap en de tijdelijke OS-map op bestanden die overeenkomen metinvoice*.pdf. Kopieert eventuele tijdelijke artefacten terug naar de projectmap en retourneert de lijst met gevonden PDF’s. -
De functie
mainbiedt een eenvoudige CLI-opdracht:python app.py <timesheet.xlsx>, die het invoerpad valideert en vervolgens de pijplijn draait:load_invoice_from_timesheet(...)om de payload te bouwen engenerate_invoice_with_claude(...)om de Skill aan te roepen en PDF’s te verzamelen.
Met dit alles op zijn plek zou de API-call een factuur opleveren die klaar is voor de klant. Als er geen bestand verschijnt, bekijk dan de tool_result-logs en de Skill-configuratie en probeer het opnieuw met een kleine testsheet.

Conclusie
Je hebt nu een werkende Auto-Invoice Generator op basis van Claude Skills, en de API kan ruwe spreadsheets omzetten in facturen die klaar zijn voor de klant. Deze demo laat zien waar Skills in uitblinken: draagbare setup, auto-activatie, deterministische code-executie en consistente resultaten in de app en API zonder in elke thread prompts te hoeven recreëren. Claude Skills leveren consistente output over contexten heen: herbruikbare domeinkennis met code-ondersteuning. Hoewel Skills code kunnen uitvoeren, moet je nog steeds de beveiliging afdwingen door alleen vertrouwde Skills in te schakelen, org-brede controles te gebruiken en updates te reviewen vóór uitrol.
Wil je verder gaan, bekijk dan Anthropic’s Skills-documentatie en de Console voor versiebeheer en upgrades, en begin vervolgens meerdere Skills te componeren om end-to-end-workflows te bouwen.
Ik ben een Google Developers Expert in ML (Gen AI), een Kaggle 3x Expert en een Women Techmakers Ambassador met meer dan 3 jaar ervaring in tech. In 2020 heb ik een healthtech-startup mee opgericht en ik volg een master computer science aan Georgia Tech, met als specialisatie machine learning.

