Using Skywalk API with AI Agents
AI coding agents like Claude Code, OpenAI Codex CLI, Cursor, and Windsurf can interact with REST APIs on your behalf. Skywalk API's simple JSON endpoints and single-header authentication make it straightforward to connect an AI agent to your AppFolio data.
This guide covers how to set up an AI agent to use Skywalk API, with tool definitions and practical examples.
Prerequisites
- A Skywalk API account with a connected AppFolio project — Get started
- An API key from your Skywalk API dashboard
- An AI agent — any of the following work: Claude Code, OpenAI Codex, Cursor, Windsurf, or other AI coding tools
Quick Start: Pointing Your Agent at the API
The fastest way to get started is to give your AI agent the Skywalk API description file:
https://api.skywalkapi.com/llms.txt
Most AI agents can fetch and read this URL. It contains everything the agent needs: base URL, authentication method, available endpoints, response format, and the async polling pattern.
Alternative: Manual Context
If your agent cannot fetch URLs, paste the following into your agent's context or system prompt:
Skywalk API — REST API for AppFolio Property Manager data.
Base URL: https://api.skywalkapi.com/v1/
Auth: X-API-Key header with your API key.
Rate limit: 1 req/sec.
Endpoints return JSON with { meta, links, data } structure.
meta.status can be "ok", "stale", or "updating".
If "updating", poll every 10 seconds until "ok".
Key GET endpoints:
/v1/properties, /v1/units, /v1/tenants, /v1/lease-history,
/v1/cash-flow, /v1/balance-sheet, /v1/rent-roll,
/v1/work-orders, /v1/vendors, /v1/tenant-ledger,
/v1/guest-cards, /v1/rental-applications
POST endpoints:
/v1/tenant-charge — create a tenant charge
Tool Definitions
If you're building a custom AI agent using the Claude API or OpenAI API with tool use (function calling), here are ready-to-use tool definitions.
Claude Tool Use Format
[
{
"name": "skywalk_get_endpoint",
"description": "Call a Skywalk API GET endpoint to retrieve AppFolio property management data. Returns JSON with meta, links, and data fields. If meta.status is 'updating', the caller should wait 10 seconds and retry.",
"input_schema": {
"type": "object",
"properties": {
"endpoint": {
"type": "string",
"description": "The endpoint path, e.g. '/v1/properties', '/v1/tenants', '/v1/cash-flow'"
},
"query_params": {
"type": "object",
"description": "Optional query parameters as key-value pairs, e.g. { \"propertyIds\": \"123,456\" }"
}
},
"required": ["endpoint"]
}
},
{
"name": "skywalk_create_tenant_charge",
"description": "Create a tenant charge in AppFolio via Skywalk API. Posts a charge to the /v1/tenant-charge endpoint.",
"input_schema": {
"type": "object",
"properties": {
"occupancy_id": {
"type": "string",
"description": "The AppFolio occupancy ID for the tenant"
},
"gl_account_id": {
"type": "string",
"description": "The GL account ID for the charge"
},
"amount": {
"type": "number",
"description": "Charge amount in dollars"
},
"description": {
"type": "string",
"description": "Description of the charge"
},
"charge_date": {
"type": "string",
"description": "Date of the charge in YYYY-MM-DD format"
}
},
"required": ["occupancy_id", "gl_account_id", "amount", "description", "charge_date"]
}
}
]
OpenAI Function Calling Format
[
{
"type": "function",
"function": {
"name": "skywalk_get_endpoint",
"description": "Call a Skywalk API GET endpoint to retrieve AppFolio property management data. Returns JSON with meta, links, and data fields. If meta.status is 'updating', the caller should wait 10 seconds and retry.",
"parameters": {
"type": "object",
"properties": {
"endpoint": {
"type": "string",
"description": "The endpoint path, e.g. '/v1/properties', '/v1/tenants', '/v1/cash-flow'"
},
"query_params": {
"type": "object",
"description": "Optional query parameters as key-value pairs"
}
},
"required": ["endpoint"]
}
}
},
{
"type": "function",
"function": {
"name": "skywalk_create_tenant_charge",
"description": "Create a tenant charge in AppFolio via Skywalk API.",
"parameters": {
"type": "object",
"properties": {
"occupancy_id": { "type": "string", "description": "AppFolio occupancy ID" },
"gl_account_id": { "type": "string", "description": "GL account ID for the charge" },
"amount": { "type": "number", "description": "Charge amount in dollars" },
"description": { "type": "string", "description": "Description of the charge" },
"charge_date": { "type": "string", "description": "Date in YYYY-MM-DD format" }
},
"required": ["occupancy_id", "gl_account_id", "amount", "description", "charge_date"]
}
}
}
]
Handling the Async Polling Pattern
Skywalk API uses an async pattern: when data needs to be refreshed from AppFolio, the API returns immediately with meta.status: "updating" rather than making you wait. Your tool implementation should handle this with a simple poll loop.
Here's a reference implementation in Python:
import requests
import time
API_BASE = "https://api.skywalkapi.com"
API_KEY = "your-api-key"
def call_skywalk(endpoint, query_params=None):
"""Call a Skywalk API endpoint, handling the async polling pattern."""
headers = {"X-API-Key": API_KEY}
url = f"{API_BASE}{endpoint}"
while True:
response = requests.get(url, headers=headers, params=query_params)
data = response.json()
status = data.get("meta", {}).get("status")
if status == "ok":
return data
elif status in ("updating", "stale"):
time.sleep(10)
else:
return data # error or unexpected status
And in JavaScript/TypeScript:
async function callSkywalk(endpoint, queryParams = {}) {
const url = new URL(endpoint, "https://api.skywalkapi.com");
Object.entries(queryParams).forEach(([k, v]) => url.searchParams.set(k, v));
while (true) {
const res = await fetch(url, {
headers: { "X-API-Key": "your-api-key" },
});
const data = await res.json();
if (data.meta?.status === "ok") return data;
if (data.meta?.status === "updating" || data.meta?.status === "stale") {
await new Promise((r) => setTimeout(r, 10000));
continue;
}
return data;
}
}
Example Prompts
Once your AI agent is configured, here are practical things you can ask:
| Prompt | What it does |
|---|---|
| "List all my properties and how many units each has" | Calls /v1/properties and summarizes the results |
| "Show me vacant units across all properties" | Calls /v1/unit-vacancies and formats the output |
| "Pull the cash flow statement for last month" | Calls /v1/cash-flow with date parameters |
| "Who are my tenants with leases expiring in the next 60 days?" | Calls /v1/lease-history and filters by expiration date |
| "Show me open work orders sorted by priority" | Calls /v1/work-orders and sorts the results |
| "What's my current rent roll?" | Calls /v1/rent-roll and summarizes totals |
| "Create a $45 utility charge for occupancy ID 12345" | Calls POST /v1/tenant-charge with the provided details |
| "Compare balance sheets for my top 3 properties" | Calls /v1/balance-sheet for each property and compares |
Best Practices
Security
- Never expose your API key in frontend code or public repositories
- Use scoped API keys with minimal permissions for agent integrations
- Consider read-only keys for agents that only need to query data
Performance
- Respect the 1 request/second rate limit
- Use the default
X-API-MAX-AGEheader to accept cached data when freshness isn't critical - Use pagination cursors (
links.next) to retrieve large result sets
Agent Tips
- Point your agent to
https://api.skywalkapi.com/llms.txtfor an always up-to-date API reference - When building custom tool implementations, always handle the
"updating"status with a retry loop - Use
X-API-PROCESSING-MODE: longerfor complex endpoints like financial reports that may need more time