If you're migrating 200 therapists from a spreadsheet into your platform, clicking through a dashboard for each one isn't an option. Orita's REST API lets you create providers, set their availability, and configure event types programmatically β so you can onboard your entire network in a single script run.
This guide covers the complete onboarding flow: creating a professional, setting availability, handling the professional.created webhook, and bulk-importing a provider list with proper rate limiting.
Scenario: A healthcare platform migrating 200 therapists from a CSV export into Orita.
When to use programmatic onboarding
Use the API instead of the dashboard when you:
- Have an existing provider database β spreadsheet, CRM, or legacy system β and need to import it without manual data entry.
- Run a marketplace or multi-tenant platform β providers self-register through your UI and you create their Orita profile on the fly.
- Need bulk setup β onboarding dozens or hundreds of providers at launch.
- Want deterministic configuration β scripted setup is reproducible and auditable; dashboard clicks are not.
If you're adding one or two providers by hand, the dashboard is faster. For everything else, use the API.
Step 1: Create a professional
POST /api/v1/professionals creates a provider profile and returns their professionalId and a dedicated apiKey they can use for their own calendar operations.
curl:
curl -X POST https://orita.online/api/v1/professionals \
-H "Authorization: Bearer $ORITA_PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Laura",
"lastname": "MΓ©ndez",
"email": "laura.mendez@clinica.com",
"profession": "therapist",
"specialty": "anxiety",
"language": "es",
"modality": "online",
"location": "Valencia, ES"
}'
Response:
{
"professionalId": "prov_abc123",
"apiKey": "sk_prov_xxxxxxxxxxxx",
"email": "laura.mendez@clinica.com",
"status": "active"
}
JavaScript:
const response = await fetch("https://orita.online/api/v1/professionals", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ORITA_PLATFORM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Laura",
lastname: "MΓ©ndez",
email: "laura.mendez@clinica.com",
profession: "therapist",
specialty: "anxiety",
language: "es",
modality: "online",
location: "Valencia, ES",
}),
});
const { professionalId, apiKey } = await response.json();
Store both professionalId and apiKey in your database immediately β you'll need them for the next steps, and for the provider to manage their own availability later.
Step 2: Set availability
Once the provider exists, define their weekly schedule with POST /api/v1/availability. Use the professionalId from Step 1.
curl -X POST https://orita.online/api/v1/availability \
-H "Authorization: Bearer $ORITA_PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"professionalId": "prov_abc123",
"schedule": [
{
"day": "monday",
"slots": [
{ "start": "09:00", "end": "13:00" },
{ "start": "15:00", "end": "18:00" }
]
},
{
"day": "wednesday",
"slots": [
{ "start": "10:00", "end": "14:00" }
]
},
{
"day": "friday",
"slots": [
{ "start": "09:00", "end": "12:00" }
]
}
],
"timezone": "Europe/Madrid"
}'
JavaScript:
await fetch("https://orita.online/api/v1/availability", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ORITA_PLATFORM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
professionalId,
schedule: [
{ day: "monday", slots: [{ start: "09:00", end: "13:00" }, { start: "15:00", end: "18:00" }] },
{ day: "wednesday", slots: [{ start: "10:00", end: "14:00" }] },
{ day: "friday", slots: [{ start: "09:00", end: "12:00" }] },
],
timezone: "Europe/Madrid",
}),
});
Always include timezone. Orita stores availability in UTC internally β omitting timezone means slots may appear at the wrong time for patients.
Step 3: Create event types
Event types define the kinds of appointments this provider offers β duration, name, and modality. Use the professionalId to associate them with the right provider.
curl -X POST https://orita.online/api/v1/event-types \
-H "Authorization: Bearer $ORITA_PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"professionalId": "prov_abc123",
"title": "SesiΓ³n individual",
"duration": 50,
"modality": "online",
"description": "Individual therapy session via video call"
}'
Response:
{
"eventTypeId": "evt_xyz789",
"title": "SesiΓ³n individual",
"duration": 50,
"modality": "online"
}
Store eventTypeId if you need to reference this event type in booking flows or analytics. You can create multiple event types per provider (e.g., a 50-min individual session and a 90-min couples session).
Step 4: Verify the provider
After creating the provider, availability, and event types, confirm everything looks right:
curl -X GET "https://orita.online/api/v1/professionals?language=es&modality=online" \
-H "Authorization: Bearer $ORITA_PLATFORM_API_KEY"
Using the SDK:
import { OritaClient } from "orita-sdk";
const orita = new OritaClient({ apiKey: process.env.ORITA_PLATFORM_API_KEY });
const providers = await orita.getProfessionals({
language: "es",
modality: "online",
specialty: "anxiety",
});
const laura = providers.find(p => p.providerId === "prov_abc123");
console.log(laura);
// { providerId: "prov_abc123", providerName: "Dra. Laura MΓ©ndez", modality: "online", language: "es", ... }
getProfessionals accepts language, modality, specialty, profession, and location as filters. If your newly created provider doesn't appear, check that the modality and language fields you set in Step 1 match what you're filtering for.
Handling the professional.created webhook
Every successful POST /api/v1/professionals fires a professional.created webhook to your configured endpoint. Use this to persist the professionalId to your own database without relying on the synchronous response alone (helpful when running bulk imports where you want async confirmation).
Register your webhook URL in the Orita dashboard under Settings β Webhooks, or via POST /api/v1/webhooks.
Express handler:
import express from "express";
import crypto from "crypto";
const app = express();
app.use(express.json());
app.post("/webhooks/orita", (req, res) => {
// Verify signature
const signature = req.headers["x-orita-signature"];
const expected = crypto
.createHmac("sha256", process.env.ORITA_WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
if (signature !== `sha256=${expected}`) {
return res.status(401).json({ error: "Invalid signature" });
}
const { event, data } = req.body;
if (event === "professional.created") {
const { professionalId, email, apiKey } = data;
// Persist to your DB
await db.professionals.upsert({
where: { email },
update: { oritaProfessionalId: professionalId, oritaApiKey: apiKey },
create: { email, oritaProfessionalId: professionalId, oritaApiKey: apiKey },
});
console.log(`Provider onboarded: ${professionalId} (${email})`);
}
res.json({ received: true });
});
Always verify the x-orita-signature header before processing. Orita signs every webhook payload with your webhook secret using HMAC-SHA256.
Bulk onboarding pattern
For importing many providers at once, loop over your provider list with a small delay between requests to stay within rate limits:
async function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function onboardProvider(provider: ProviderRecord) {
// 1. Create professional
const res = await fetch("https://orita.online/api/v1/professionals", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ORITA_PLATFORM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: provider.name,
lastname: provider.lastname,
email: provider.email,
profession: provider.profession,
specialty: provider.specialty,
language: provider.language,
modality: provider.modality,
location: provider.location,
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Failed to create ${provider.email}: ${err.message}`);
}
const { professionalId, apiKey } = await res.json();
// 2. Set availability
await fetch("https://orita.online/api/v1/availability", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ORITA_PLATFORM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
professionalId,
schedule: provider.schedule,
timezone: provider.timezone,
}),
});
// 3. Create event type
await fetch("https://orita.online/api/v1/event-types", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ORITA_PLATFORM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
professionalId,
title: provider.eventTypeTitle,
duration: provider.eventTypeDuration,
modality: provider.modality,
}),
});
return { professionalId, apiKey };
}
async function bulkOnboard(providers: ProviderRecord[]) {
const results = [];
for (const provider of providers) {
try {
const result = await onboardProvider(provider);
results.push({ email: provider.email, ...result, success: true });
console.log(`β Onboarded ${provider.email} β ${result.professionalId}`);
} catch (err) {
results.push({ email: provider.email, success: false, error: err.message });
console.error(`β Failed ${provider.email}: ${err.message}`);
}
await sleep(100); // 100ms delay between providers
}
return results;
}
The 100ms delay keeps you well within Orita's default rate limits. If you're importing thousands of providers, increase the delay to 200β500ms and consider running the script during off-peak hours.
Full script: onboard 3 providers end-to-end
import "dotenv/config";
const BASE_URL = "https://orita.online/api/v1";
const headers = {
"Authorization": `Bearer ${process.env.ORITA_PLATFORM_API_KEY}`,
"Content-Type": "application/json",
};
const providers = [
{
name: "Laura", lastname: "MΓ©ndez", email: "laura.mendez@clinica.com",
profession: "therapist", specialty: "anxiety", language: "es",
modality: "online", location: "Valencia, ES", timezone: "Europe/Madrid",
schedule: [
{ day: "monday", slots: [{ start: "09:00", end: "13:00" }] },
{ day: "wednesday", slots: [{ start: "10:00", end: "14:00" }] },
],
eventTypeTitle: "SesiΓ³n individual", eventTypeDuration: 50,
},
{
name: "Carlos", lastname: "Ruiz", email: "carlos.ruiz@clinica.com",
profession: "psychologist", specialty: "depression", language: "en",
modality: "in-person", location: "Madrid, ES", timezone: "Europe/Madrid",
schedule: [
{ day: "tuesday", slots: [{ start: "08:00", end: "12:00" }] },
{ day: "thursday", slots: [{ start: "14:00", end: "18:00" }] },
],
eventTypeTitle: "Individual Session", eventTypeDuration: 60,
},
{
name: "Sofia", lastname: "Torres", email: "sofia.torres@clinica.com",
profession: "therapist", specialty: "trauma", language: "es",
modality: "online", location: "Barcelona, ES", timezone: "Europe/Madrid",
schedule: [
{ day: "monday", slots: [{ start: "15:00", end: "19:00" }] },
{ day: "friday", slots: [{ start: "09:00", end: "13:00" }] },
],
eventTypeTitle: "SesiΓ³n terapΓ©utica", eventTypeDuration: 50,
},
];
async function post(path: string, body: object) {
const res = await fetch(`${BASE_URL}${path}`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(`POST ${path} failed (${res.status}): ${err.message ?? res.statusText}`);
}
return res.json();
}
async function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function main() {
console.log(`Starting bulk onboard for ${providers.length} providers...\n`);
const results = [];
for (const p of providers) {
try {
// 1. Create professional
const { professionalId, apiKey } = await post("/professionals", {
name: p.name, lastname: p.lastname, email: p.email,
profession: p.profession, specialty: p.specialty,
language: p.language, modality: p.modality, location: p.location,
});
// 2. Set availability
await post("/availability", {
professionalId,
schedule: p.schedule,
timezone: p.timezone,
});
// 3. Create event type
const { eventTypeId } = await post("/event-types", {
professionalId,
title: p.eventTypeTitle,
duration: p.eventTypeDuration,
modality: p.modality,
});
results.push({ email: p.email, professionalId, apiKey, eventTypeId, success: true });
console.log(`β ${p.name} ${p.lastname} β ${professionalId}`);
} catch (err) {
results.push({ email: p.email, success: false, error: err.message });
console.error(`β ${p.email}: ${err.message}`);
}
await sleep(100);
}
console.log("\n--- Results ---");
console.table(results.map(r => ({
email: r.email,
status: r.success ? "β" : "β",
professionalId: r.professionalId ?? "β",
error: r.error ?? "β",
})));
}
main();
Run it:
ORITA_PLATFORM_API_KEY=your_key npx ts-node onboard.ts
Output:
Starting bulk onboard for 3 providers...
β Laura MΓ©ndez β prov_abc123
β Carlos Ruiz β prov_def456
β Sofia Torres β prov_ghi789
--- Results ---
βββββββββββββββββββββββββββββββ¬βββββββββ¬βββββββββββββββββ¬ββββββββ
β email β status β professionalId β error β
βββββββββββββββββββββββββββββββΌβββββββββΌβββββββββββββββββΌββββββββ€
β laura.mendez@clinica.com β β β prov_abc123 β β β
β carlos.ruiz@clinica.com β β β prov_def456 β β β
β sofia.torres@clinica.com β β β prov_ghi789 β β β
βββββββββββββββββββββββββββββββ΄βββββββββ΄βββββββββββββββββ΄ββββββββ
After the script runs, all three providers are immediately discoverable via resolveScheduling β patients can book with them right away.
What's next
- Run
resolveSchedulingβ now that your providers are in Orita, see Resolve and Book Across Your Provider Network to start matching patients. - Provider self-service β share each provider's
apiKeyso they can update their own availability viaPOST /api/v1/availabilitywithout platform admin involvement. - Scaling to 200+ β for very large imports, batch providers into groups of 50 and increase the sleep to 200ms. Monitor the
professional.createdwebhook to track completion asynchronously.
