Formisoft API
Build integrations with the Formisoft platform. Create and manage forms, patients, appointments, submissions, and webhooks programmatically using a simple REST API.
Capabilities
Authentication
All API requests require authentication via an API key. Keys are scoped to an organization and carry full access to that organization's data.
Create an API Key
- Go to Settings → API Keys in your dashboard
- Click "Create API Key" and give it a name
- Copy the key immediately — it is only shown once
Use Your Key
Include your key in the Authorization header:
Authorization: Bearer fsk_your_api_key_here
Keep your API keys secret. Do not expose them in client-side code, public repositories, or browser requests. API keys have full access to your organization's data including PHI.
Quick Start
List forms
curl https://formisoft.com/api/forms \ -H "Authorization: Bearer fsk_your_api_key_here"
Create a patient
curl -X POST https://formisoft.com/api/patients \
-H "Authorization: Bearer fsk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"dateOfBirth": "1990-01-15"
}'Book an appointment
curl -X POST https://formisoft.com/api/appointments \
-H "Authorization: Bearer fsk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"patientId": "clx...",
"scheduledAt": "2025-03-01T14:00:00Z",
"duration": 30,
"type": "initial_consultation"
}'Forms
/api/formsList all forms for your organization. Returns forms sorted by last updated.
Response
[
{
"id": "clx...",
"name": "New Patient Intake",
"description": "Standard intake form",
"category": "intake",
"status": "published",
"pages": [...],
"settings": {...},
"branding": {...},
"_count": { "submissions": 42 },
"createdAt": "2025-01-15T10:00:00.000Z",
"updatedAt": "2025-01-20T14:30:00.000Z"
}
]/api/formsCreate a new form.
Request Body
{
"name": "New Patient Intake", // required
"description": "Standard intake", // optional
"category": "intake", // optional, default: "intake"
"pages": [...], // optional, default: empty page
"settings": {}, // optional
"branding": {} // optional
}Response
{
"id": "clx...",
"name": "New Patient Intake",
"status": "draft",
"createdAt": "2025-01-15T10:00:00.000Z",
...
}Submissions
/api/submissionsList submissions. Filter by form or status.
Query Parameters
formId Filter by form ID (optional) status Filter by status: "submitted" | "reviewed" | "flagged" (optional) limit Max results, default 100, max 1000, or "all"
Response
[
{
"id": "clx...",
"formId": "clx...",
"status": "submitted",
"data": { "field_id": "value", ... },
"form": { "id": "clx...", "name": "Intake Form" },
"patient": { "id": "clx...", "firstName": "Jane", "lastName": "Doe" },
"reviewer": null,
"createdAt": "2025-01-20T09:15:00.000Z"
}
]/api/submissionsUpdate a submission's review status.
Request Body
{
"submissionId": "clx...", // required
"status": "reviewed", // "submitted" | "reviewed" | "flagged"
"reviewNotes": "Looks good" // optional
}Response
{
"id": "clx...",
"status": "reviewed",
"reviewedAt": "2025-01-20T10:00:00.000Z",
"reviewer": { "id": "clx...", "name": "Dr. Smith" },
...
}/api/submissions/:idUpdate a submission by ID. Same behavior as the batch endpoint above.
Request Body
{
"status": "reviewed", // "submitted" | "reviewed" | "flagged"
"reviewNotes": "Approved" // optional
}Response
{
"id": "clx...",
"status": "reviewed",
"reviewedAt": "2025-01-20T10:00:00.000Z",
...
}/api/submissions/:idDelete a submission permanently.
Response
{ "success": true }Patients
/api/patientsList patients. Supports search across name, email, and phone.
Query Parameters
search Search term for name, email, or phone (optional) status Filter by status (optional)
Response
[
{
"id": "clx...",
"patientId": "PAT-000001",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"phone": "+15551234567",
"dateOfBirth": "1990-01-15T00:00:00.000Z",
"gender": "female",
"status": "active",
"_count": { "appointments": 3, "submissions": 5 },
"createdAt": "2025-01-10T08:00:00.000Z"
}
]/api/patientsCreate a new patient record.
Request Body
{
"firstName": "Jane", // required
"lastName": "Doe", // required
"email": "jane@example.com", // optional
"phone": "+15551234567", // optional
"dateOfBirth": "1990-01-15", // optional, ISO date
"gender": "female", // optional
"address": { ... }, // optional, JSON object
"insuranceCarrier": "Aetna", // optional
"insurancePolicyNumber": "ABC123", // optional
"insuranceGroupNumber": "GRP456", // optional
"insuranceSubscriber": "Jane Doe", // optional
"emergencyContactName": "John Doe", // optional
"emergencyContactPhone": "+15559876543", // optional
"emergencyContactRelationship": "Spouse" // optional
}Response
{
"id": "clx...",
"patientId": "PAT-000042",
"firstName": "Jane",
"lastName": "Doe",
"status": "active",
"createdAt": "2025-01-15T10:00:00.000Z",
...
}/api/patients/:idGet a single patient with medical data, recent appointments, submissions, and documents.
Response
{
"id": "clx...",
"firstName": "Jane",
"lastName": "Doe",
"allergies": [...],
"medications": [...],
"conditions": [...],
"appointments": [...],
"submissions": [...],
"documents": [...],
"payments": [...],
"_count": { "appointments": 3, "submissions": 5, "documents": 2 },
...
}/api/patients/:idUpdate a patient record. Only include fields you want to change.
Request Body
{
"firstName": "Janet", // any patient field
"email": "janet@example.com",
"status": "inactive",
"insuranceCarrier": "BlueCross"
// ... any field from the create endpoint
}Response
{
"id": "clx...",
"firstName": "Janet",
"email": "janet@example.com",
"status": "inactive",
...
}/api/patients/:idDelete a patient record permanently.
Response
{ "success": true }Appointments
/api/appointmentsList appointments for your organization, sorted by most recent.
Response
[
{
"id": "clx...",
"patientId": "clx...",
"providerId": "clx...",
"scheduledAt": "2025-02-01T14:00:00.000Z",
"duration": 30,
"type": "initial_consultation",
"status": "scheduled",
"patient": {
"id": "clx...",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com"
},
"provider": { "user": { "name": "Dr. Smith" } },
"forms": [{ "form": { "id": "clx...", "name": "Intake Form" } }]
}
]/api/appointmentsCreate an appointment. Checks for scheduling conflicts automatically.
Request Body
{
"patientId": "clx...", // required
"scheduledAt": "2025-02-01T14:00:00Z", // required, ISO datetime
"providerId": "clx...", // optional
"duration": 30, // optional, minutes (default: 30)
"type": "initial_consultation", // optional
"notes": "First visit", // optional
"formIds": ["clx..."], // optional, attach intake forms
"autoSendIntake": true // optional, email forms to patient
}Response
{
"id": "clx...",
"status": "scheduled",
"scheduledAt": "2025-02-01T14:00:00.000Z",
"duration": 30,
"patient": { "id": "clx...", "firstName": "Jane", "lastName": "Doe" },
...
}/api/appointments/:idGet a single appointment with patient, provider, and form details.
Response
{
"id": "clx...",
"scheduledAt": "2025-02-01T14:00:00.000Z",
"duration": 30,
"status": "scheduled",
"patient": { ... },
"provider": { "user": { "name": "Dr. Smith" } },
"forms": [...],
"organization": { "name": "Acme Clinic", "timezone": "America/New_York" }
}/api/appointments/:idUpdate an appointment. Supports rescheduling (with conflict detection) and status transitions.
Request Body
{
"scheduledAt": "2025-02-02T10:00:00Z", // reschedule
"duration": 45, // update duration
"status": "confirmed", // status transition
"type": "follow_up", // update type
"notes": "Rescheduled",
"cancelReason": "Patient unavailable" // when status = "cancelled"
}Response
{
"id": "clx...",
"status": "confirmed",
"scheduledAt": "2025-02-02T10:00:00.000Z",
...
}/api/appointments/:idDelete an appointment permanently.
Response
{ "success": true }Status Transitions
Appointments follow a state machine. Invalid transitions return 422.
scheduled → confirmed, checked_in, cancelled, completed, no_show confirmed → scheduled, checked_in, cancelled, completed, no_show checked_in → in_progress, cancelled, completed, no_show in_progress → completed, cancelled cancelled → scheduled no_show → scheduled completed → (terminal)
Webhooks
/api/webhooksList all webhook configurations for your organization.
Response
[
{
"id": "clx...",
"url": "https://example.com/webhook",
"events": ["submission.created", "patient.created"],
"active": true,
"_count": { "deliveries": 15 },
"createdAt": "2025-01-10T08:00:00.000Z"
}
]/api/webhooksCreate a new webhook. Returns the signing secret (shown once).
Request Body
{
"url": "https://example.com/webhook", // required, HTTPS recommended
"events": ["submission.created"], // required, array of event types
// OR for single event:
"event": "submission.created" // alternative to events array
}Response
{
"id": "clx...",
"secret": "a1b2c3d4e5f6..." // HMAC signing secret — shown once
}/api/webhooks/:idUpdate a webhook configuration.
Request Body
{
"url": "https://example.com/new-hook", // optional
"events": ["submission.created", "patient.created"], // optional
"active": false // optional, disable/enable
}Response
{
"id": "clx...",
"url": "https://example.com/new-hook",
"events": ["submission.created", "patient.created"],
"active": false,
...
}/api/webhooks/:idDelete a webhook configuration and all its delivery history.
Response
{ "success": true }Analytics
/api/analyticsGet form analytics data (view locations).
Query Parameters
formId The form ID to get analytics for (required)
Response
{
"data": [
{
"latitude": 40.7128,
"longitude": -74.006,
"city": "New York",
"region": "NY",
"country": "US"
}
]
}Webhook Events
When events occur in Formisoft, we send a POST request to your webhook URL with a JSON payload. Deliveries time out after 10 seconds.
Supported Events
submission.createdA new form submission is receivedsubmission.reviewedA submission is marked as reviewed or flaggedappointment.createdA new appointment is bookedappointment.updatedAn appointment is updated, rescheduled, or cancelledpatient.createdA new patient record is createdpatient.updatedA patient record is updatedform.publishedA form is publishedPayload Format
Every webhook payload includes the event field plus event-specific data:
// submission.created
{
"event": "submission.created",
"formId": "clx...",
"submissionId": "clx...",
"patientId": "clx...",
"patientName": "Jane Doe"
}
// appointment.updated
{
"event": "appointment.updated",
"appointmentId": "clx...",
"status": "confirmed",
"scheduledAt": "2025-02-01T14:00:00.000Z"
}
// patient.created
{
"event": "patient.created",
"patientId": "clx...",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com"
}Signature Verification
Each delivery includes an X-Webhook-Signature header — an HMAC-SHA256 hex digest of the JSON body signed with your webhook secret. Verify it to ensure the request came from Formisoft:
const crypto = require("crypto");
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(JSON.stringify(body))
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Usage in Express
app.post("/webhook", (req, res) => {
const sig = req.headers["x-webhook-signature"];
if (!verifyWebhook(req.body, sig, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process event...
res.sendStatus(200);
});Rate Limits
API requests are rate-limited per IP address using a sliding window. If you exceed the limit, you'll receive a 429 Too Many Requests response.
Retry with exponential backoff. Most endpoints allow 60 requests per minute.
Errors
All errors return a JSON object with an error field and an appropriate HTTP status code:
{ "error": "Unauthorized" }| Status | Meaning |
|---|---|
| 400 | Bad request — missing or invalid parameters |
| 401 | Unauthorized — invalid or missing API key |
| 403 | Forbidden — key does not have access to this resource |
| 404 | Not found — resource does not exist |
| 409 | Conflict — scheduling conflict (appointments) |
| 422 | Unprocessable — invalid state transition |
| 429 | Too many requests — rate limit exceeded |
| 500 | Internal server error |
Ready to integrate?
Create your API key in the dashboard and start building. Full access to all endpoints with every plan.