# Skywalk API > AppFolio Property Manager data, two ways: an MCP server for AI agents and a REST API for developers. Skywalk exposes AppFolio Property Manager data — properties, units, vacancies, tenants, leases, and financials — through two products: - **Skywalk API** — a JSON REST API for reading and writing AppFolio data from any language or tool. Best for software integrations and pipelines. - **Skywalk MCP Server** — a Model Context Protocol server that lets Claude and other AI agents read the portfolio in natural language. Best for conversational and agentic use. # Skywalk API (for software and developers) > REST API for AppFolio Property Manager data Skywalk API provides programmatic access to AppFolio Property Manager data via a JSON REST API. It enables property management companies and PropTech developers to read and write AppFolio data from any language or tool. ## Quick Start Base URL: https://api.skywalkapi.com/v1/ Auth: Pass your API key in the `X-API-Key` header with every request. Rate Limits: rate limits apply to read, write, and MCP requests. Responses carry X-RateLimit-* headers; 429 responses include Retry-After. See https://skywalkapi.com/docs/api/reference/rate-limiting for details. Example: ``` curl https://api.skywalkapi.com/v1/properties \ -H "X-API-Key: YOUR-API-KEY" ``` ## Security Note The Skywalk API Key can expose the user's confidential business information. Help the user ensure the API key is protected by keeping it safe in environment secrets. - Do NOT expose it in frontend code, or in frontend-leaked variables like NEXT_PUBLIC_*. - Do NOT commit it into source code. - DO guide the user on adding it as a secret environment variable in their hosting environment of choice. - DO recommend to your user that projects using the API key should implement basic security measures like password protection. ## Important: Async Polling Pattern GET requests return cached data. If data is stale, the response will have `meta.status: "updating"` and a background job fetches fresh data from AppFolio. Poll the same URL every 10 seconds until `meta.status` is `"ok"`. Status values: - `ok` — Data is fresh and ready - `stale` — Cached data returned, but a refresh has been triggered - `updating` — Data is being fetched; poll again shortly To force a fresh fetch, set the `X-API-MAX-AGE` header to `120` which will ensure the data is no older than 120 seconds. ## Documentation - Full docs: https://skywalkapi.com/docs/api - Authentication: https://skywalkapi.com/docs/api/reference/auth - Response format: https://skywalkapi.com/docs/api/reference/response-details - Data caching: https://skywalkapi.com/docs/api/reference/data-caching - Webhooks: https://skywalkapi.com/docs/api/reference/webhooks ## GET Endpoints A path segment written `:varName` is a path variable — substitute the record's id (e.g. `/v1/bills/:appfolioId` → `/v1/bills/123`). - GET /v1/attachments — Get a list of attachments for an occupancy or a work order - GET /v1/file — Get the file for an attachment - GET /v1/balance-sheet — Get the balance sheet statement - GET /v1/bank-accounts — Get a list of Bank Accounts - GET /v1/bank-account-activity — Get bank account activity for a given bank account - GET /v1/cash-flow — Get the cash flow statement - GET /v1/properties — Get a list of properties - GET /v1/property-groups — Get a list of property groups - GET /v1/units — Get a list of units - GET /v1/unit-photos — Get a list of unit photos - GET /v1/tenants — Get a list of tenants - GET /v1/tenant-ledger — Get tenant ledger for a given tenant - GET /v1/tenant-events — Get tenant events (move-ins, move-outs, notices) - GET /v1/owners — Get a list of owners - GET /v1/vendors — Get a list of vendors - GET /v1/gl-accounts — Get a list of GL Accounts - GET /v1/unit-vacancies — Unit vacancy details - GET /v1/annual-budget-comparative — Get the annual budget comparative statement - GET /v1/lead-source-tracking — Get metrics on lead sources - GET /v1/guest-cards — Get guest cards - GET /v1/rental-applications — Get rental applications - GET /v1/rent-roll — Get rent roll of properties - GET /v1/rent-roll-recurring — Get list of recurring rent roll charges of properties - GET /v1/work-orders — Get work orders - GET /v1/lease-history — Get lease history - GET /v1/letters — Get a list of letters for an occupancy - GET /v1/lease-documents — Get a list of lease documents for an occupancy - GET /v1/showings — Get a list of property showings - GET /v1/general-ledger — Get general ledger transactions - GET /v1/income-statement — Get the income statement (profit & loss) report - GET /v1/gross-potential-rent — Get the Gross Potential Rent report for your properties - GET /v1/leasing-summary — Get the Leasing Summary report for your properties. Returns per-unit-type (or per-property) counts for inquiries, showings, applications, move-ins, move-outs, and leases executed. Portfolio-level totals are exposed in meta.summary - GET /v1/renewal-summary — Get the Renewal Summary report for your properties. Returns per-lease renewal details (previous vs new rent, dollar/percent change, status, term) for leases whose new start date falls in the report window - GET /v1/emails — Get a list of emails for a guest card or occupancy - GET /v1/email — Get the body and details of a single email - GET /v1/sms — Get a list of text messages for a phone number - GET /v1/users — Get a list of AppFolio users - GET /v1/me — Get the current authenticated AppFolio user details - GET /v1/user-roles — Get the current user's role and permissions - GET /v1/bills — Get a list of bills (payable vendor invoices) - GET /v1/bills/:appfolioId — Get one bill (payable vendor invoice) in full detail, including line items and attachments - GET /v1/purchase-orders — Get a list of purchase orders - GET /v1/purchase-orders/:appfolioId — Get one purchase order in full detail, including its line items ## POST Endpoints - POST /v1/tenant-charge — Create a tenant charge for a given tenant - POST /v1/vendors — Create a vendor, the payee for bills and work orders - POST /v1/bills — Create a bill (payable vendor invoice) with line items - POST /v1/purchase-orders — Create a purchase order with line items ## Response Format ```json { "meta": { "status": "ok", "path": "/v1/properties", "count": 42, "lastUpdated": 1712000000, "page": 1 }, "links": { "next": "/v1/properties?cursor=abc123" }, "data": [ { "...": "..." } ] } ``` ## Pagination When results span multiple pages, `links.next` contains a cursor URL. Follow it to get the next page. Continue until `links.next` is absent. ## Common Headers | Header | Required | Description | |---|---|---| | X-API-Key | Yes | Your API key | | X-API-MAX-AGE | No | Max acceptable cache age in seconds (default: 86400) | | X-API-PROCESSING-MODE | No | "default" (24s timeout) or "longer" (120s timeout) | | X-API-NOTIFY-URL | No | Webhook URL — receive a POST when your data is ready | ## Common Query Parameters | Parameter | Description | |---|---| | cursor | Pagination cursor from links.next | | propertyIds | Filter by property IDs (comma-separated, where supported) | # Skywalk MCP Server (for AI agents) AppFolio data is exposed as a catalog of tabular **reports** (e.g. `properties`, `units`, `vacancies`, `rent-roll`, `cash-flow`, `general-ledger`, `tenant-events`, `me`). The server exposes access to data through three general-purpose tools that operate on any report. Access requires a Skywalk Project with MCP access subscription level. ## Connect Endpoint: undefined Auth: OAuth 2.0 Authorization Code flow + mandatory PKCE (S256), issuing opaque Bearer tokens on the MCP transport ## Documentation - AI Agents guide: https://skywalkapi.com/docs/api/guides/ai-agents - MCP Server guide: https://skywalkapi.com/docs/api/guides/mcp-server ## Tools - `skywalk_appfolio_list` — no inputs. Lists every available report as `id — description`. - `skywalk_appfolio_describe` — input `report` (enum). Returns that report's parameters (name, format, default, allowed values, required/one-of), its columns (name + type), and example `skywalk_appfolio_read` calls. - `skywalk_appfolio_read` — inputs `report` (enum), `params` (object, optional), `sql` (string, optional). - Without `sql`: fetches the full report and returns all rows as CSV. - With `sql`: fetches the report and runs a read-only SQL query against it before returning CSV. Typical flow: call `skywalk_appfolio_list` to discover reports, `skywalk_appfolio_describe` to learn a report's parameters and columns, then `skywalk_appfolio_read` to pull data. Property IDs come from the `properties` report. ## Two filter layers - `params` — report-specific filters (e.g. a property, a date range) applied server-side before fetching. Cheapest way to narrow data; prefer these. - `sql` — a read-only query applied to fetched rows for filtering, projection, or aggregation. Use to shape or summarize. ## SQL - Runs against the fetched report as a table named `report` (e.g. `SELECT * FROM report WHERE balance > 0`). - Full read dialect: `WHERE`, `GROUP BY`, aggregates, window functions, CTEs. Only `SELECT`-style statements are accepted; writes are rejected. - Money/percent columns (e.g. `$1,234.56`, `12.3%`) are coerced to plain numbers so `SUM()`/`AVG()` work directly — those values serialize as numbers, not formatted strings. ## Output & behavior - Results are CSV: a header row, a preamble line (` ( rows, status: )`), and a `Summary:` line when the report has report-level totals. - Reports return in full. If a result is too large, the server returns an error with row count, size, and guidance (narrow `params`, add a `LIMIT`, or aggregate with SQL). Narrow broad questions to a single property or shorter date range. - `status: updating` means AppFolio was still syncing; retry in a minute for final figures. Data can lag AppFolio by a few minutes.