API and Data Model
API conventions
All product APIs are Next.js App Router route handlers under src/app/api (82 route groups). Public surface: Swagger UI at /api-docs, OpenAPI 3.1.0 JSON at /api/docs/openapi.
Authenticated handler contract
const ctx = await getCurrentUser(request.headers); return401if missing.- Enforce role checks on admin-only routes (
403if insufficient). - Scope every DB read/write by
ctx.firmId(tenant client orwhereclause). selectonly the fields the caller needs;noCacheHeaders()on sensitive GETs.- Never return keys, tokens, signed URLs, or provider secrets — return
readiness booleans, hostnames, or redacted labels instead.
Status codes: 200 read/action · 201 created · 400 invalid input · 401 unauthenticated · 403 forbidden · 404 tenant-scoped not found · 409 conflict · 500 unexpected (secret-safe logging).
Mutations
- Validate before touching the DB (Zod or narrowing from
unknown). - Tenant-safe deletes/updates: prefer
updateMany/deleteManywithfirmId - Emit activity/audit rows for security-sensitive actions.
- Return enough data for the caller to update without a refetch.
so cross-firm records silently miss instead of throwing.
Public developer API
Scoped API keys (/api/settings/api-keys), usage reporting (/api/settings/api-usage), registry in src/lib/developer-portal/registry.ts. External-key routes must validate key hash, active status, expiry, firm ownership, scope, and rate-limit/usage accounting. When adding a public endpoint: update the registry/OpenAPI source, document auth + scopes with success/failure examples, add a route test, and confirm /api/docs/openapi stays valid.
Full handler detail: api-reference.
Data model (102 models, prisma/schema.prisma)
| Domain | Models | |---|---| | Tenant + team | Firm, User (+ roles), Invitation | | Case core | Case, CaseStatusHistory, CaseParty, CaseAssignment, Note, Task | | Intake | Lead, IntakeSubmission, ReferralSource | | Contacts | Contact, OpposingCounsel, ExpertWitness | | Medical | MedicalRecord, MedicalProvider, Treatment, MedicalBill | | Legal | InsurancePolicy, InsuranceClaim, SubrogationLien, NegotiationEntry, Deposition, JurisdictionStatute, DiscoveryRequest, Evidence | | Financial | Invoice, Payment, TimeEntry, TrustTransaction, TrustAccount, Expense, MileageEntry | | Comms | CommunicationRecord, SmsConsent, SmsMessage, EmailLog | | AI / automation | AgentConfig, AgentRun, DocumentChunk (pgvector), Workflow, WorkflowRun, WorkflowRunStep, Template | | Documents / media | Document, Recording, KnowledgeArticle | | Scheduling | CalendarEvent | | Config | CustomFieldDefinition/Value, AlertRule, WebhookEndpoint, ApiKey, SavedReport, ChecklistTemplate/Item | | Signatures | SignatureRequest, SignatureSigner | | Ops | Activity (audit trail), Notification |
Key enums: CaseStatus (LEAD → … → CLOSED/REJECTED), UserRole (SUPER_ADMIN, ADMIN, ATTORNEY, PARALEGAL, INTAKE_SPECIALIST, CLIENT, READONLY), CaseType, ContactType, TaskStatus, TaskPriority.
Multi-tenancy rule
Firm-scoped models carry firmId; use getTenantPrisma(firmId) so scoping is automatic. With the raw client, filter firmId manually. Unscoped models (e.g. Note) inherit scoping through parents (Note → Case → Firm).
Migrations
npx prisma migrate dev --name your-change # author (commits SQL under prisma/migrations/)
npx prisma migrate deploy # apply (production runs this at boot)
npx prisma migrate status
Forward-only: revert with a new migration, never delete history. Expand/contract procedure: database-migrations.