Overview
DebtStack is designed for AI agents. The API returns structured JSON that’s easy for LLMs to parse, supports field selection to minimize context usage, and provides atomic primitives that compose naturally.New to DebtStack? Start with our interactive demo scenarios to see the API in action before building agents.
Design Principles
1. Atomic Primitives
Each endpoint does one thing well. Agents can compose primitives to answer complex questions:Copy
"Which MAG7 company has the highest leverage?"
→ search.companies(ticker=MAG7, sort=-net_leverage)
"Who guarantees RIG's 8% 2027 notes?"
→ resolve.bond(q="RIG 8% 2027")
→ traverse.entities(bond_id, relationship=guarantees)
2. Field Selection
Request only what you need to minimize token usage:Copy
# Good: Specific fields
requests.get("/v1/companies?ticker=AAPL&fields=ticker,name,net_leverage_ratio")
# Avoid: All fields (wastes tokens)
requests.get("/v1/companies?ticker=AAPL")
3. Structured Errors
Errors include actionable suggestions:Copy
{
"error": {
"code": "INVALID_TICKER",
"message": "Company 'APPL' not found",
"details": {
"suggestion": "Did you mean 'AAPL'?"
}
}
}
Agent Architecture
ReAct Pattern
DebtStack works well with ReAct (Reasoning + Acting) agents:Copy
TOOLS = """
You have access to these DebtStack tools:
1. search_companies(filters, fields) - Find companies by leverage, sector, debt metrics
2. search_bonds(filters, fields) - Find bonds by yield, seniority, maturity
3. resolve_bond(query) - Map bond description to CUSIP
4. get_pricing(cusips) - Get YTM, spread, last price
5. traverse_entities(start, relationship) - Find guarantors, subsidiaries
6. search_documents(query, section_type) - Search SEC filings
"""
PROMPT = f"""
{TOOLS}
When answering credit analysis questions:
1. Think about what data you need
2. Use the appropriate tool(s)
3. Analyze the results
4. Provide a clear answer
Question: {{question}}
"""
Tool Definitions
Copy
def search_companies_tool(ticker=None, sector=None, min_leverage=None, fields=None):
"""
Search for companies in the DebtStack database.
Args:
ticker: Comma-separated tickers (e.g., "AAPL,MSFT")
sector: Filter by sector (e.g., "Technology")
min_leverage: Minimum leverage ratio
fields: Fields to return (e.g., "ticker,name,net_leverage_ratio")
Returns:
List of companies matching the criteria
"""
params = {}
if ticker:
params["ticker"] = ticker
if sector:
params["sector"] = sector
if min_leverage:
params["min_leverage"] = min_leverage
if fields:
params["fields"] = fields
response = requests.get(f"{BASE_URL}/companies", params=params, headers=HEADERS)
return response.json()["data"]
def traverse_entities_tool(bond_cusip=None, company_ticker=None, relationship="guarantees"):
"""
Traverse entity relationships to find guarantors, subsidiaries, etc.
Args:
bond_cusip: CUSIP of bond to find guarantors for
company_ticker: Company to traverse from
relationship: "guarantees", "subsidiaries", "parents", "debt"
Returns:
List of related entities
"""
if bond_cusip:
start = {"type": "bond", "id": bond_cusip}
else:
start = {"type": "company", "id": company_ticker}
response = requests.post(
f"{BASE_URL}/entities/traverse",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"start": start,
"relationships": [relationship],
"direction": "inbound" if relationship == "guarantees" else "outbound",
"fields": ["name", "entity_type", "jurisdiction", "is_guarantor"]
}
)
return response.json()["data"]["traversal"]["entities"]
Example: Credit Analysis Agent
Copy
from openai import OpenAI
client = OpenAI()
API_KEY = "ds_xxxxx"
HEADERS = {"X-API-Key": API_KEY}
BASE_URL = "https://api.debtstack.ai/v1"
# Define tools
tools = [
{
"type": "function",
"function": {
"name": "search_companies",
"description": "Search for companies by leverage, sector, or debt metrics",
"parameters": {
"type": "object",
"properties": {
"ticker": {"type": "string", "description": "Comma-separated tickers"},
"sector": {"type": "string", "description": "Business sector"},
"min_leverage": {"type": "number", "description": "Minimum leverage ratio"},
"max_leverage": {"type": "number", "description": "Maximum leverage ratio"},
"fields": {"type": "string", "description": "Fields to return"}
}
}
}
},
{
"type": "function",
"function": {
"name": "search_bonds",
"description": "Search for bonds by yield, seniority, or maturity",
"parameters": {
"type": "object",
"properties": {
"ticker": {"type": "string", "description": "Company ticker"},
"min_ytm": {"type": "number", "description": "Minimum yield to maturity"},
"seniority": {"type": "string", "description": "senior_secured, senior_unsecured, subordinated"},
"fields": {"type": "string", "description": "Fields to return"}
}
}
}
},
{
"type": "function",
"function": {
"name": "get_guarantors",
"description": "Find which entities guarantee a specific bond",
"parameters": {
"type": "object",
"properties": {
"cusip": {"type": "string", "description": "Bond CUSIP"},
},
"required": ["cusip"]
}
}
}
]
def execute_tool(name, args):
"""Execute a tool and return results."""
if name == "search_companies":
params = {k: v for k, v in args.items() if v}
response = requests.get(f"{BASE_URL}/companies", params=params, headers=HEADERS)
return response.json()["data"]
elif name == "search_bonds":
params = {k: v for k, v in args.items() if v}
if "min_ytm" in params:
params["has_pricing"] = True
response = requests.get(f"{BASE_URL}/bonds", params=params, headers=HEADERS)
return response.json()["data"]
elif name == "get_guarantors":
response = requests.post(
f"{BASE_URL}/entities/traverse",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"start": {"type": "bond", "id": args["cusip"]},
"relationships": ["guarantees"],
"direction": "inbound",
"fields": ["name", "entity_type", "is_guarantor"]
}
)
return response.json()["data"]["traversal"]["entities"]
def chat(question):
"""Run the agent."""
messages = [
{"role": "system", "content": "You are a credit analyst assistant. Use the tools to answer questions about corporate debt."},
{"role": "user", "content": question}
]
while True:
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools
)
message = response.choices[0].message
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
result = execute_tool(
tool_call.function.name,
json.loads(tool_call.function.arguments)
)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
else:
return message.content
# Example usage
answer = chat("Which of the MAG7 companies has the highest leverage?")
print(answer)
Best Practices
Minimize Token Usage
- Use field selection - Only request fields you need
- Use batch operations - Combine multiple calls
- Limit results - Use
limitparameter
Handle Errors Gracefully
Copy
def safe_search(query):
try:
response = requests.get(f"{BASE_URL}/bonds/resolve", params={"q": query}, headers=HEADERS)
data = response.json()
if "error" in data:
return f"Could not find bond: {data['error']['details'].get('suggestion', query)}"
return data["data"]["matches"]
except Exception as e:
return f"Error: {str(e)}"
Cache Frequently Used Data
Copy
from functools import lru_cache
@lru_cache(maxsize=100)
def get_company_info(ticker):
response = requests.get(
f"{BASE_URL}/companies",
params={"ticker": ticker, "fields": "ticker,name,sector,total_debt,net_leverage_ratio"},
headers=HEADERS
)
return response.json()["data"][0] if response.json()["data"] else None

