API
What is the AppsFolio API
The AppsFolio API provides programmatic access to your application portfolio data. It enables integrations with external tools and AI coding agents, allowing you to query, export, create, update, and delete apps and metadata outside the AppsFolio web interface.
The API uses API Key authentication via request headers, and all data access respects tenant isolation — you can only access data belonging to your organization.
Interactive API Documentation
Full interactive API documentation is available at:
https://yourorg.appsfol.io/app/api/docs
This Swagger UI lets you explore all endpoints, view request/response schemas, and try out API calls directly in your browser. Use the Authorize button in Swagger to enter your API key headers before making requests.
Base URL
All API endpoints use the following base URL, where yourorg is your organization's tenant ID:
https://yourorg.appsfol.io/app/api
For example, to call the schema endpoint: GET https://yourorg.appsfol.io/app/api/apps/schema
Authentication
The API supports two authentication methods: User API Keys for individual access, and Organization API Keys for shared or automated access.
Personal API Keys
Authenticate with your personal email and API key:
| Header | Description |
|---|---|
X-User-Id |
Your email address |
X-API-Key |
Your personal API key |
Where to find your personal API key
- Click on your profile picture in the top navigation
- Select Profile from the dropdown menu
- Your API key is displayed in the API Key section, along with the required headers
- Click Show to reveal the key, then use the copy button to copy it to your clipboard
Warning
Your API key is a secret. Do not share it or commit it to source control. If you believe your key has been compromised, click the regenerate button on your profile page to create a new one.
Organization API Keys
Organization API keys provide access at the organization level, without being tied to a specific user. They are ideal for CI/CD pipelines, automated integrations, and shared tooling.
| Header | Description |
|---|---|
X-Org-Id |
Your organization's tenant ID |
X-API-Key |
An organization API key |
Organization API keys have full read and write access to all apps in the organization. They bypass individual user permission checks.
Organization admins can create and manage org API keys from the Organization Settings page under the API Keys section. The X-Org-Id header value (your tenant ID) is also displayed on that page.
Where to find organization API keys
- Go to Organization Settings from the sidebar
- Scroll to the API Keys section
- Your Tenant ID is displayed at the top of the settings page — use this as the
X-Org-Idheader value - Click Create API Key and enter a descriptive name (e.g., "CI/CD Pipeline", "External Integration")
- Copy the generated key immediately — it will only be shown once
Warning
Organization API keys are shown only once when created or rotated. Store them securely. If you lose a key, rotate it to generate a new one.
From the API Keys table you can:
- Rotate — generate a new key value (the old key stops working immediately)
- Deactivate / Activate — temporarily disable a key without deleting it
- Delete — permanently remove a key
Rate limiting
The API is rate limited to 100 requests per minute per user or organization key. If you exceed this limit, requests will return a 429 Too Many Requests response.
Read Endpoints
Get Application Schema
Returns a JSONSchema document describing the expected shape of application payloads for your organization. The schema reflects your tenant's configured content types (definitions) and their validation rules.
GET /app/api/apps/schema
Response: A JSONSchema object.
curl -H "X-User-Id: you@company.com" -H "X-API-Key: your-api-key" \
"https://yourorg.appsfol.io/app/api/apps/schema"
Use this endpoint to understand what metadata fields are available and what values they accept before creating or updating apps. The schema includes validation constraints such as allowed choices, required fields, and value formats.
Tip
The schema is cached for performance and automatically invalidated when your organization's content type definitions change.
Export Portfolio as Markdown
Returns your entire application portfolio as a Markdown document optimized for AI agents and LLMs. This is the recommended way to provide your portfolio data to AI coding assistants.
GET /app/api/apps/export/markdown
Response: Plain text Markdown document (Content-Type: text/markdown).
curl -H "X-User-Id: you@company.com" -H "X-API-Key: your-api-key" \
"https://yourorg.appsfol.io/app/api/apps/export/markdown"
Filtering
You can filter the export using query parameters:
| Parameter | Description | Example |
|---|---|---|
app |
Comma-separated app shortnames | ?app=salesforce,slack |
section |
Comma-separated section names | ?section=Security,Compliance |
# Export only specific apps
curl -H "X-User-Id: you@company.com" -H "X-API-Key: your-api-key" \
"https://yourorg.appsfol.io/app/api/apps/export/markdown?app=salesforce,jira"
# Export only specific sections
curl -H "X-User-Id: you@company.com" -H "X-API-Key: your-api-key" \
"https://yourorg.appsfol.io/app/api/apps/export/markdown?section=Compliance"
# Combine both filters
curl -H "X-User-Id: you@company.com" -H "X-API-Key: your-api-key" \
"https://yourorg.appsfol.io/app/api/apps/export/markdown?app=salesforce§ion=Compliance"
Output format
The Markdown export is structured for easy LLM consumption:
- H1 — Portfolio title with tenant name
- H2 — One heading per application (with shortname)
- H3 — One heading per section
- H4 — One heading per metadata field
- App-level details (summary, owner, editors, dates) appear as bullet points under each app heading
- Empty metadata entries are omitted to reduce noise
- Horizontal rules separate applications for clear boundaries
All metadata types are rendered in a readable format — choices appear as bulleted lists, contacts as key-value pairs, links as Markdown links, notes with HTML stripped to plain text, and so on.
Write Endpoints
Write endpoints allow you to create, update, and delete applications and their metadata via the API.
Permissions
| Operation | User API Key | Organization API Key |
|---|---|---|
| Create | Requires app manager role | Full access |
| Update | Must be owner, editor, or tenant admin of the app | Full access |
| Delete | Must be owner or tenant admin | Full access |
Create Application
Creates a new application in your organization.
POST /app/api/apps
Request body:
{
"app": {
"name": "Salesforce",
"shortname": "salesforce",
"summary": "CRM platform",
"description": "Our primary CRM for sales and customer management"
},
"metadata": [
{
"definition": {"shortname": "vendor_contact"},
"payload": {
"name": "Salesforce Support",
"email": "support@salesforce.com"
}
},
{
"definition": {"shortname": "app_category"},
"payload": {
"selections": ["CRM", "Sales"]
}
}
]
}
| Field | Required | Description |
|---|---|---|
app.name |
Yes | Application display name |
app.shortname |
Yes | Unique identifier (alphanumerics, underscores, dashes only) |
app.summary |
No | Short summary (max 80 characters) |
app.description |
No | Detailed description |
metadata |
No | List of metadata entries (same shape as GET response) |
Response: 201 Created with the app and its metadata.
curl -X POST \
-H "X-Org-Id: your-tenant-id" -H "X-API-Key: your-org-key" \
-H "Content-Type: application/json" \
-d '{"app": {"name": "Salesforce", "shortname": "salesforce"}}' \
"https://yourorg.appsfol.io/app/api/apps"
Update Application (Full)
Updates an application's fields and upserts provided metadata entries. Metadata entries not included in the request are left unchanged.
PUT /app/api/apps/{app_id}
Request body:
{
"app": {
"name": "Salesforce CRM",
"summary": "Updated CRM platform"
},
"metadata": [
{
"definition": {"shortname": "vendor_contact"},
"payload": {
"name": "Salesforce Enterprise Support",
"email": "enterprise@salesforce.com"
}
}
]
}
Response: 200 OK with the updated app and all its metadata.
Update Application (Partial)
Updates only the provided fields. Use metadata to upsert specific entries and remove_metadata to soft-delete entries by definition shortname.
PATCH /app/api/apps/{app_id}
Request body:
{
"app": {
"summary": "Updated summary only"
},
"metadata": [
{
"definition": {"shortname": "app_category"},
"payload": {
"selections": ["CRM"]
}
}
],
"remove_metadata": ["deprecated_field"]
}
| Field | Description |
|---|---|
app.name |
Update the app name |
app.summary |
Update the summary |
app.description |
Update the description |
metadata |
List of metadata entries to upsert (same shape as GET response) |
remove_metadata |
List of definition shortnames to remove |
Response: 200 OK with the updated app and all its metadata.
Delete Application
Soft-deletes an application and all its associated metadata.
DELETE /app/api/apps/{app_id}
Response: 200 OK with a confirmation message.
curl -X DELETE \
-H "X-Org-Id: your-tenant-id" -H "X-API-Key: your-org-key" \
"https://yourorg.appsfol.io/app/api/apps/APP_UUID_HERE"
Working with metadata
The metadata field in create and update requests is a list of entries matching the same shape returned by GET /app/api/apps/{app_id}. Each entry has a definition object (with at least a shortname) and a payload object. This makes the API roundtrip-friendly — you can GET an app, modify values, and PUT/POST it back.
Use the schema endpoint to discover available definitions and their expected payload formats.
Example: If your organization has a "vendor_contact" definition of type contact, the metadata entry would look like:
{
"metadata": [
{
"definition": {"shortname": "vendor_contact"},
"payload": {
"name": "Jane Smith",
"email": "jane@vendor.com",
"phone": "+1-555-0100"
}
}
]
}
Extra fields in the definition object (like id, name, metadata_type) are accepted but ignored — this allows you to pass the full definition object from a GET response without stripping fields.
Payloads are validated against the definition's type. Invalid payloads return a 422 response with per-field error details.
Error Handling
The API returns standard HTTP status codes with JSON error bodies.
| Status | Meaning |
|---|---|
400 |
Bad request (malformed input) |
401 |
Authentication failed or insufficient permissions |
404 |
Resource not found |
409 |
Conflict (e.g., duplicate shortname) |
422 |
Validation failed (with detailed error messages) |
429 |
Rate limit exceeded |
Validation errors
When a write request fails validation, the response includes detailed error information:
{
"error": "Validation failed",
"details": [
{"field": "app", "message": "Shortname must contain only alphanumerics, underscores, and dashes"}
],
"metadata_errors": {
"vendor_contact": ["email: Not a valid email address"]
}
}
details— app-level validation errorsmetadata_errors— per-definition errors, keyed by definition shortname
Using the API with AI Agents
The Markdown export endpoint is designed specifically for feeding portfolio data to AI coding agents like Claude, ChatGPT, Copilot, or similar tools.
Example: Providing context to an AI agent
Save the export to a file and reference it in your AI agent's context:
# Download your portfolio as Markdown
curl -H "X-User-Id: you@company.com" -H "X-API-Key: your-api-key" \
"https://yourorg.appsfol.io/app/api/apps/export/markdown" \
-o portfolio.md
# Use the file as context for an AI agent
# (varies by tool — check your AI agent's documentation)
Example: Asking questions about your portfolio
Once an AI agent has your portfolio Markdown, you can ask questions like:
- "Who owns Salesforce?"
- "What apps are in the Finance category?"
- "Which applications have contracts expiring this year?"
- "List all apps with their vendor contact information"
Why Markdown instead of JSON?
The JSON API endpoints (/app/api/apps) are ideal for programmatic integrations. The Markdown export is optimized for a different use case — providing context to large language models:
- Structured headings make it easy for LLMs to locate specific apps and sections
- Plain text is more token-efficient than nested JSON
- Human-readable formatting means LLMs can reason about the data naturally
- Filtering lets you provide only relevant context, staying within token limits