Skip to main content
Developers

API + webhook reference (planned).

The public REST API and outbound webhooks are planned and not available to accounts yet; API keys cannot be created today. This page describes the planned interface so developers can see what is coming.
Aptoria
On this page
Introduction
01
Canonical BLOCKED floor
02
Authentication
03
Errors & rate limits
04
Endpoints
05
Webhooks
06
Verifying signatures
07
SDKs & examples
08
Support
09
Webhooks
Real-time
Real-time
1
Register endpoints
Register endpoints under Settings → Developer.
2
Each delivery is a POST
Each delivery is a POST with the event payload and a signature header.
3
Exponential backoff
We retry failed deliveries with exponential backoff for ~48 hours.
4
Auto-disabled
After 10 consecutive failures the endpoint is auto-disabled and the owner gets an email.
API + webhook reference (planned).
REST
Base URL
app.aptoria.ai
Auth
Bearer apt_ key
Rate limit
60 / min · 1000 / hr
Webhook events
13 signed kinds
Webhooks
Real-time
rent.paid
rent.late
application.submitted
application.approved
application.denied
lease.signed
lease.terminated
work_order.created
work_order.completed
tenant.added
tenant.removed
inspection.completed
payment_plan.proposed
Verifying signatures
Every webhook POST includes X-Aptoria-Signature
v1 is an HMAC-SHA256 of t.body using your endpoint's signing secret
Reject deliveries where the timestamp is more than 5 minutes old (replay protection)
Reject deliveries where the HMAC doesn't match
Endpoints
REST
List delinquent rent
/api/ar-aging
Post an expense
/api/expenses
List work orders for a unit
/api/work-orders
Trigger an AI helper
https://app.aptoria.ai/api/ai-vendor-summary
Base URL
app.aptoria.ai
Auth
Bearer apt_ key
Rate limit
60 / min · 1000 / hr
Webhook events
13 signed kinds
On this page
Start here

Introduction

The Aptoria REST API uses JSON over HTTPS. Base URL: https://app.aptoria.ai. All authenticated endpoints live under /api/* and accept JSON bodies on POST/PATCH.
Planned: once the public API is available, endpoints will be scoped by the permissions on each API key.
Policy boundary

Canonical BLOCKED floor

Registry version 2026-09-06.1. API keys, approvals and customer thresholds cannot enable prohibited actions. Human review requirements do not activate a workflow. Unreviewed applicability remains unavailable.
Terminate a lease
Ending a tenancy is a judicial-process decision with statutory notice timing — always yours.
Human review required
Maximum maturity: approval_only
Legal activation: unreviewed
File or advance an eviction
Possession actions require jurisdiction-specific process and must never be automated from an unsupported legal conclusion.
Human review required
Maximum maturity: approval_only
Legal activation: unreviewed
Deny an applicant
FCRA adverse-action rules require human sign-off and proper notice.
Human review required
Maximum maturity: approval_only
Legal activation: unreviewed
Deny an accommodation request
Fair Housing Act — only the landlord may decide, with a documented reason.
Human review required
Maximum maturity: approval_only
Legal activation: unreviewed
Deduct from a security deposit
Deposit deductions run on statutory clocks with itemization duties — always yours.
Human review required
Maximum maturity: approval_only
Legal activation: unreviewed
Serve a rent-increase notice
Notice timing is tiered by tenancy length under state law — served by you, on your decision.
Human review required
Maximum maturity: approval_only
Legal activation: unreviewed
Send a formal collection demand
Formal dunning carries consumer-protection formatting duties — never autonomous.
Human review required
Maximum maturity: approval_only
Legal activation: unreviewed
Pool your data to set rents
Your numbers never feed a shared pricing model. Hardcoded — no setting can switch it on.
Prohibited in Aptoria
Maximum maturity: disabled
Legal activation: unreviewed
Set rent from external data
Aptoria policy prohibits automated rent setting from external data, including public-data inputs.
Prohibited in Aptoria
Maximum maturity: disabled
Legal activation: unreviewed
Report a resident to a credit bureau
Aptoria does not report residents to credit bureaus today. If it is ever offered, each report would need an authorized person’s decision; no setting, provider, or agent can send one automatically.
Human review required
Maximum maturity: approval_only
Legal activation: unreviewed
Downgrade or close a habitability concern
A model cannot reduce urgency or close a habitability concern without accountable human review.
Prohibited in Aptoria
Maximum maturity: disabled
Legal activation: unreviewed
Override a deposit deadline
Deposit deadlines cannot be extended or suppressed by an agent, workspace setting, or model.
Prohibited in Aptoria
Maximum maturity: disabled
Legal activation: unreviewed
Impose a mandatory fee
The agent cannot create or impose a mandatory fee; enabled fee workflows require separately reviewed authority.
Prohibited in Aptoria
Maximum maturity: disabled
Legal activation: unreviewed
Score a resident’s risk
Proprietary resident risk scores are permanently prohibited. Review source facts and make required decisions as a human.
Prohibited in Aptoria
Maximum maturity: disabled
Legal activation: unreviewed
Keys & scopes

Authentication

Issue a key from Settings → Developer. Keys start with apt_ and are shown exactly once — store them securely.
Send the key as a Bearer token in the Authorization header on every request:
bash
Copy
curl https://app.aptoria.ai/api/dashboard \ -H "Authorization: Bearer apt_a1b2c3d4_BASE64URL_SECRET"
Each key is scoped (e.g., read:tenants, write:work_orders). A request that hits an endpoint your scopes don't cover returns 403.
Reliability

Errors & rate limits

Errors return JSON with an error field and a 4xx/5xx status:
json
Copy
{ "error": "Rate limit exceeded — try again in 47 seconds." }
Defaults: 60 requests/minute per key, 1000/hour. Heavy AI endpoints have tighter per-key quotas (e.g., 10/h for delinquency prioritizer). The Retry-After response header tells you when to retry.
REST

Endpoints

List delinquent rent

bash
Copy
curl https://app.aptoria.ai/api/ar-aging \ -H "Authorization: Bearer apt_..."
json
Copy
{ "rows": [ { "payment_id": "p_01H...", "tenant_name": "Maya R.", "property_name": "127 Pine", "unit_label": "1B", "amount": 1825, "due_date": "2026-04-01", "days_late": 54 } ], "totals": { "0-30": 1825, "31-60": 1825, "61-90": 0, "90+": 0, "total": 3650 } }

Post an expense

bash
Copy
curl -X POST https://app.aptoria.ai/api/expenses \ -H "Authorization: Bearer apt_..." \ -H "Content-Type: application/json" \ -d '{ "action": "add_entry", "property_id": "5f...", "category_id": "repairs", "label": "Snow removal — driveway", "amount_usd": 380, "paid_on": "2026-02-14", "vendor": "Twin Cities Plow Co." }'

List work orders for a unit

bash
Copy
curl "https://app.aptoria.ai/api/work-orders?unit_id=5f..." \ -H "Authorization: Bearer apt_..."

Trigger an AI helper

bash
Copy
curl -X POST https://app.aptoria.ai/api/ai-vendor-summary \ -H "Authorization: Bearer apt_..." \ -H "Content-Type: application/json" \ -d '{ "vendor_id": "v_01H..." }'
Real-time

Webhooks

Register endpoints under Settings → Developer. Each delivery is a POST with the event payload and a signature header. Thirteen event kinds today:
rent.paid
rent.late
application.submitted
application.approved
application.denied
lease.signed
lease.terminated
work_order.created
work_order.completed
tenant.added
tenant.removed
inspection.completed
payment_plan.proposed

Example payload

json
Copy
{ "event": "rent.paid", "delivered_at": "2026-05-25T15:42:18.224Z", "organization_id": "org_01H...", "payload": { "payment_id": "p_01H...", "lease_id": "l_01H...", "amount_usd": 1825, "period_label": "2026-05", "paid_at": "2026-05-25T15:42:14Z" } }
We retry failed deliveries with exponential backoff for ~48 hours. After 10 consecutive failures the endpoint is auto-disabled and the owner gets an email.
Security

Verifying signatures

Every webhook POST includes X-Aptoria-Signature:
shell
Copy
X-Aptoria-Signature: t=1716659938,v1=4f1c2b...
t is the unix-second timestamp; v1 is an HMAC-SHA256 of t.body using your endpoint's signing secret. Reject deliveries where the timestamp is more than 5 minutes old (replay protection) or where the HMAC doesn't match.
javascript
Copy
// Node — express import crypto from 'crypto'; app.post('/webhooks/aptoria', express.text({ type: '*/*' }), (req, res) => { const sig = req.header('X-Aptoria-Signature') || ''; const parts = Object.fromEntries(sig.split(',').map((p) => p.split('='))); const t = Number(parts.t), v1 = parts.v1; if (!t || !v1) return res.status(400).end(); if (Math.abs(Date.now() / 1000 - t) > 300) return res.status(400).end(); const mac = crypto.createHmac('sha256', process.env.APTORIA_WEBHOOK_SECRET) .update(`${t}.${req.body}`) .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(v1))) { return res.status(401).end(); } const event = JSON.parse(req.body); // ...handle event res.status(200).end(); });
python
Copy
# Python — Flask import hmac, hashlib, os, time, json from flask import request, abort @app.post('/webhooks/aptoria') def aptoria(): sig = request.headers.get('X-Aptoria-Signature', '') parts = dict(p.split('=') for p in sig.split(',')) t = int(parts.get('t', 0)); v1 = parts.get('v1', '') if abs(time.time() - t) > 300: abort(400) body = request.get_data(as_text=True) expected = hmac.new( os.environ['APTORIA_WEBHOOK_SECRET'].encode(), f'{t}.{body}'.encode(), hashlib.sha256, ).hexdigest() if not hmac.compare_digest(expected, v1): abort(401) event = json.loads(body) # ...handle event return ('', 200)
Copy-paste

SDKs & examples

No official SDK yet — the API is small enough that a thin fetch wrapper handles most use cases. Drop-in helper:
typescript
Copy
// aptoria.ts const BASE = 'https://app.aptoria.ai'; export async function aptoria<T = unknown>( path: string, init?: RequestInit & { json?: unknown } ): Promise<T> { const { json, headers, ...rest } = init || {}; const res = await fetch(`${BASE}${path}`, { ...rest, headers: { Authorization: `Bearer ${process.env.APTORIA_API_KEY!}`, ...(json ? { 'Content-Type': 'application/json' } : {}), ...(headers || {}), }, body: json ? JSON.stringify(json) : (rest as any).body, }); if (!res.ok) throw new Error(`Aptoria ${res.status}: ${await res.text()}`); return res.json(); } // Usage: const aging = await aptoria('/api/ar-aging'); await aptoria('/api/expenses', { method: 'POST', json: { action: 'add_entry', ... } });
Stuck?

Support

Hit a wall? Email developers@aptoria.ai with the request ID from the response (header X-Request-Id) and we'll dig in.