2026-06-23 20:36:07 -04:00
|
|
|
import Link from "next/link"
|
2026-07-02 13:42:34 -04:00
|
|
|
import { Code2, Lock, Zap, BookOpen, Webhook } from "lucide-react"
|
|
|
|
|
import { WEBHOOK_EVENTS } from "@/lib/webhooks/events"
|
2026-06-23 20:36:07 -04:00
|
|
|
|
|
|
|
|
export const metadata = {
|
2026-07-02 13:42:34 -04:00
|
|
|
title: "API Docs",
|
2026-06-23 20:36:07 -04:00
|
|
|
description: "Property Management Network REST API documentation for developers.",
|
2026-07-02 13:42:34 -04:00
|
|
|
alternates: { canonical: "/api-docs" },
|
2026-06-23 20:36:07 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-02 13:42:34 -04:00
|
|
|
// The real, deployed origin. Falls back to a placeholder only when the env var
|
|
|
|
|
// isn't set (e.g. local docs previews).
|
|
|
|
|
const BASE_URL = `${process.env.NEXT_PUBLIC_APP_URL ?? "https://your-app-url"}/api/v1`
|
|
|
|
|
|
2026-06-23 20:36:07 -04:00
|
|
|
const ENDPOINTS = [
|
2026-07-02 13:42:34 -04:00
|
|
|
{ method: "GET", path: "/properties", desc: "List all properties for the authenticated account" },
|
|
|
|
|
{ method: "POST", path: "/properties", desc: "Create a new property" },
|
|
|
|
|
{ method: "GET", path: "/tenants", desc: "List tenants with their unit and lease status" },
|
|
|
|
|
{ method: "GET", path: "/payments", desc: "List rent payments (filter by status, tenant_id, from/to date range)" },
|
|
|
|
|
{ method: "POST", path: "/payments", desc: "Record a rent payment" },
|
|
|
|
|
{ method: "GET", path: "/maintenance", desc: "List maintenance requests (filter by status, priority, property_id)" },
|
|
|
|
|
{ method: "POST", path: "/maintenance", desc: "Create a maintenance request" },
|
|
|
|
|
{ method: "PATCH", path: "/maintenance/:id", desc: "Update a maintenance request's status or fields" },
|
|
|
|
|
{ method: "GET", path: "/webhooks", desc: "List webhook subscriptions" },
|
|
|
|
|
{ method: "POST", path: "/webhooks", desc: "Create a webhook subscription (Zapier REST Hook subscribe)" },
|
|
|
|
|
{ method: "DELETE", path: "/webhooks/:id", desc: "Delete a webhook subscription (Zapier REST Hook unsubscribe)" },
|
2026-06-23 20:36:07 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const METHOD_COLORS: Record<string, string> = {
|
|
|
|
|
GET: "text-emerald-400 bg-emerald-500/10",
|
|
|
|
|
POST: "text-blue-400 bg-blue-500/10",
|
|
|
|
|
PATCH: "text-amber-400 bg-amber-500/10",
|
|
|
|
|
DELETE: "text-red-400 bg-red-500/10",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function ApiDocsPage() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="bg-[#09090b] text-white min-h-screen">
|
|
|
|
|
{/* Hero */}
|
|
|
|
|
<div className="relative overflow-hidden pt-32 pb-16">
|
|
|
|
|
<div className="absolute top-0 left-1/2 -translate-x-1/2 h-[400px] w-[600px] rounded-full bg-violet-600/12 blur-[100px] -z-10" />
|
|
|
|
|
<div className="mx-auto max-w-4xl px-6">
|
|
|
|
|
<div className="inline-flex items-center gap-2 rounded-full border border-violet-500/30 bg-violet-500/10 px-4 py-1.5 text-xs font-medium text-violet-300 mb-6">
|
|
|
|
|
<Code2 className="h-3.5 w-3.5" />
|
|
|
|
|
REST API · v1
|
|
|
|
|
</div>
|
|
|
|
|
<h1 className="text-4xl font-bold sm:text-5xl mb-4">API Documentation</h1>
|
|
|
|
|
<p className="text-lg text-white/50 max-w-2xl leading-relaxed">
|
|
|
|
|
Build on top of Property Management Network. Automate your workflows, sync with external tools, or build custom dashboards using our REST API.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="mt-6 inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/[0.04] px-4 py-2.5">
|
|
|
|
|
<span className="text-xs font-mono text-white/40">Base URL:</span>
|
2026-07-02 13:42:34 -04:00
|
|
|
<code className="text-xs font-mono text-indigo-300">{BASE_URL}</code>
|
2026-06-23 20:36:07 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mx-auto max-w-4xl px-6 pb-24 space-y-12">
|
|
|
|
|
{/* Auth */}
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
|
|
|
|
<Lock className="h-5 w-5 text-indigo-400" /> Authentication
|
|
|
|
|
</h2>
|
|
|
|
|
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-6">
|
|
|
|
|
<p className="text-sm text-white/60 mb-4 leading-relaxed">
|
2026-07-02 13:42:34 -04:00
|
|
|
All API requests require a Bearer API key in the Authorization header. Keys look like{" "}
|
|
|
|
|
<code className="text-indigo-300 font-mono text-xs">pmn_live_…</code> and are generated from{" "}
|
|
|
|
|
<Link href="/login" className="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Settings → API keys</Link>{" "}
|
|
|
|
|
inside your dashboard. The plaintext key is shown only once at creation, so store it securely.
|
2026-06-23 20:36:07 -04:00
|
|
|
</p>
|
|
|
|
|
<div className="rounded-xl bg-[#0a0a12] border border-white/[0.06] p-4 font-mono text-xs text-emerald-300">
|
|
|
|
|
<p className="text-white/30 mb-1"># Example request</p>
|
2026-07-02 13:42:34 -04:00
|
|
|
<p>curl {BASE_URL}/properties \</p>
|
|
|
|
|
<p className="pl-4">-H "Authorization: Bearer pmn_live_..."</p>
|
2026-06-23 20:36:07 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Endpoints */}
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
|
|
|
|
<Zap className="h-5 w-5 text-amber-400" /> Endpoints
|
|
|
|
|
</h2>
|
|
|
|
|
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
|
|
|
|
|
{ENDPOINTS.map((ep, i) => (
|
|
|
|
|
<div key={i} className={`flex items-start gap-4 px-6 py-4 ${i !== ENDPOINTS.length - 1 ? "border-b border-white/[0.04]" : ""}`}>
|
|
|
|
|
<span className={`shrink-0 rounded-md px-2.5 py-1 text-[11px] font-bold font-mono ${METHOD_COLORS[ep.method]}`}>
|
|
|
|
|
{ep.method}
|
|
|
|
|
</span>
|
|
|
|
|
<div>
|
|
|
|
|
<code className="text-xs font-mono text-white/80">{ep.path}</code>
|
|
|
|
|
<p className="text-xs text-white/40 mt-0.5">{ep.desc}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Response format */}
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
|
|
|
|
<BookOpen className="h-5 w-5 text-blue-400" /> Response Format
|
|
|
|
|
</h2>
|
|
|
|
|
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-6">
|
2026-07-02 13:42:34 -04:00
|
|
|
<p className="text-sm text-white/60 mb-4">
|
|
|
|
|
All responses are JSON. List endpoints return a <code className="text-indigo-300 font-mono text-xs">data</code> array
|
|
|
|
|
with a <code className="text-indigo-300 font-mono text-xs">count</code>. Single-record and create responses return a{" "}
|
|
|
|
|
<code className="text-indigo-300 font-mono text-xs">data</code> object (create returns HTTP 201). Errors return an{" "}
|
|
|
|
|
<code className="text-red-300 font-mono text-xs">error</code> object with a numeric <code className="text-red-300 font-mono text-xs">code</code> and a <code className="text-red-300 font-mono text-xs">message</code>.
|
|
|
|
|
</p>
|
2026-06-23 20:36:07 -04:00
|
|
|
<div className="rounded-xl bg-[#0a0a12] border border-white/[0.06] p-4 font-mono text-xs leading-relaxed">
|
2026-07-02 13:42:34 -04:00
|
|
|
<p className="text-white/30">{"// Success (list)"}</p>
|
2026-06-23 20:36:07 -04:00
|
|
|
<p className="text-emerald-300">{"{"} "data": [...], "count": 12 {"}"}</p>
|
|
|
|
|
<br />
|
2026-07-02 13:42:34 -04:00
|
|
|
<p className="text-white/30">{"// Success (single / create)"}</p>
|
|
|
|
|
<p className="text-emerald-300">{"{"} "data": {"{"} ... {"}"} {"}"}</p>
|
|
|
|
|
<br />
|
|
|
|
|
<p className="text-white/30">{"// Error"}</p>
|
2026-06-23 20:36:07 -04:00
|
|
|
<p className="text-red-300">{"{"} "error": {"{"} "code": 401, "message": "Unauthorized" {"}"} {"}"}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-07-02 13:42:34 -04:00
|
|
|
{/* Webhooks */}
|
|
|
|
|
<div id="webhooks" className="scroll-mt-24">
|
|
|
|
|
<h2 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
|
|
|
|
<Webhook className="h-5 w-5 text-emerald-400" /> Webhooks
|
|
|
|
|
</h2>
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-6">
|
|
|
|
|
<p className="text-sm text-white/60 leading-relaxed">
|
|
|
|
|
Subscribe to real-time events instead of polling. Add endpoints in{" "}
|
|
|
|
|
<Link href="/login" className="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Settings → Webhooks</Link>{" "}
|
|
|
|
|
(or via the <code className="text-indigo-300 font-mono text-xs">/webhooks</code> API), and we'll POST a
|
|
|
|
|
signed JSON payload the moment something happens. This is the same mechanism that powers our{" "}
|
|
|
|
|
<span className="text-white/80 font-medium">Zapier</span> integration — Zapier subscribes and unsubscribes
|
|
|
|
|
through the <code className="text-indigo-300 font-mono text-xs">POST /webhooks</code> and{" "}
|
|
|
|
|
<code className="text-indigo-300 font-mono text-xs">DELETE /webhooks/:id</code> endpoints (the REST Hook pattern).
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Events */}
|
|
|
|
|
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
|
|
|
|
|
<div className="border-b border-white/[0.06] px-6 py-3">
|
|
|
|
|
<p className="text-xs font-semibold uppercase tracking-widest text-white/40">Available events</p>
|
|
|
|
|
</div>
|
|
|
|
|
{WEBHOOK_EVENTS.map((ev, i) => (
|
|
|
|
|
<div
|
|
|
|
|
key={ev.id}
|
|
|
|
|
className={`flex items-start gap-4 px-6 py-3 ${i !== WEBHOOK_EVENTS.length - 1 ? "border-b border-white/[0.04]" : ""}`}
|
|
|
|
|
>
|
|
|
|
|
<code className="shrink-0 rounded-md bg-emerald-500/10 px-2.5 py-1 text-[11px] font-bold font-mono text-emerald-400">
|
|
|
|
|
{ev.id}
|
|
|
|
|
</code>
|
|
|
|
|
<p className="text-xs text-white/50 mt-0.5">{ev.description}</p>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Payload + signature */}
|
|
|
|
|
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] p-6">
|
|
|
|
|
<p className="text-sm font-semibold text-white mb-2">Payload & signature</p>
|
|
|
|
|
<p className="text-sm text-white/60 mb-4 leading-relaxed">
|
|
|
|
|
Each request body is a JSON envelope. Every delivery carries an{" "}
|
|
|
|
|
<code className="text-indigo-300 font-mono text-xs">X-PMN-Signature</code> header —{" "}
|
|
|
|
|
<code className="text-indigo-300 font-mono text-xs">t=<unix>,v1=<hex></code> — where{" "}
|
|
|
|
|
<code className="text-indigo-300 font-mono text-xs">v1</code> is the HMAC-SHA256 of{" "}
|
|
|
|
|
<code className="text-indigo-300 font-mono text-xs">{"`${t}.${rawBody}`"}</code> keyed with your endpoint's
|
|
|
|
|
signing secret. Recompute it and compare in constant time; reject if the timestamp is stale.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="rounded-xl bg-[#0a0a12] border border-white/[0.06] p-4 font-mono text-xs leading-relaxed text-white/70">
|
|
|
|
|
<p className="text-white/30">{"// POST body"}</p>
|
|
|
|
|
<p className="text-emerald-300">{"{"}</p>
|
|
|
|
|
<p className="pl-4">"id": "evt_9f2c…",</p>
|
|
|
|
|
<p className="pl-4">"event": "tenant.created",</p>
|
|
|
|
|
<p className="pl-4">"created_at": "2026-07-02T12:00:00.000Z",</p>
|
|
|
|
|
<p className="pl-4">"data": {"{"} "tenant": {"{"} … {"}"} {"}"}</p>
|
|
|
|
|
<p className="text-emerald-300">{"}"}</p>
|
|
|
|
|
<p className="mt-2 text-white/30">{"# Headers"}</p>
|
|
|
|
|
<p>X-PMN-Event: tenant.created</p>
|
|
|
|
|
<p>X-PMN-Delivery: <delivery id></p>
|
|
|
|
|
<p>X-PMN-Signature: t=1751457600,v1=1a2b3c…</p>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-white/40 mt-4">
|
|
|
|
|
Respond with any <span className="text-white/70">2xx</span> to acknowledge. Non-2xx or timeouts are retried
|
|
|
|
|
with exponential backoff (up to 5 attempts); a test event is available from the dashboard.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* SDK note */}
|
|
|
|
|
<div className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6 text-center">
|
|
|
|
|
<p className="text-xs text-white/40">
|
|
|
|
|
Official client SDKs are not yet available. Call the endpoints directly over HTTP with any language or HTTP client.
|
|
|
|
|
</p>
|
2026-06-23 20:36:07 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|