Qlynic API for teams

Integrate faster, with keys made for partners

Create per‑partner API clients, control PHI access, allow safe status updates, and audit every request. Qlynic’s API is designed for secure clinic operations.

Qlynic API for teams

Integrate faster, with keys made for partners

Create per‑partner API clients, control PHI access, allow safe status updates, and audit every request. Qlynic’s API is designed for secure clinic operations.

Partner avatar
Webhook: appointment created
Partner A · 4 days ago
Partner avatar
GET /api/appointments?status=confirmed
Partner B · 3 days ago
Qlynic
Partner avatar
Status: cancelled (trusted client)
Partner C · 2 days ago
Partner avatar
POST /api/appointments (created)
Partner D · 1 day ago

Use the Qlynic API

Authenticate with your partner API key, call endpoints with precise filters, and receive predictable JSON. Examples below show both GET and POST flows with expected results.

Quick start

  • Base URL: /api/appointments
  • Auth: HTTP header X-Api-Key: <your_key>
  • Status filter: status=booked,confirmed,cancelled (defaults to booked,confirmed)
  • Date range: ?from=YYYY-MM-DD&to=YYYY-MM-DD
  • Doctor scope: ?doctorId=<id> (must belong to your clinic)
Keys are per-partner. Permissions control PHI exposure and allowed status updates.

Best practices

  • Log the request id and timestamp client-side for traceability.
  • Use status filters to minimize payload size.
  • Cache non-sensitive results briefly to reduce calls.
  • Honor the clinic’s time zone in UI; server returns a ClinicLocalDate field.

Endpoints & examples

GET appointments (range)
curl -H "X-Api-Key: YOUR_KEY" \
  "https://your-host/api/appointments?from=2025-12-01&to=2025-12-31&doctorId=20&status=booked,confirmed"
Sample response (array)
[
  {
    "appointmentId": 56,
    "clinicId": 9,
    "doctorId": 20,
    "startUtc": "2025-12-01T14:00:00Z",
    "endUtc": "2025-12-01T14:30:00Z",
    "clinicLocalDate": "2025-12-01",
    "patientName": "John Doe",
    "patientEmail": "john@example.com",
    "patientPhone": "+1403...",
    "reason": "Checkup",
    "status": "booked",
    "createdAt": "2025-11-25T12:20:00Z"
  }
]
GET appointment by id
curl -H "X-Api-Key: YOUR_KEY" \
  "https://your-host/api/appointments/56"
Sample response (object)
{
  "appointmentId": 56,
  "clinicId": 9,
  "doctorId": 20,
  "startUtc": "2025-12-01T14:00:00Z",
  "endUtc": "2025-12-01T14:30:00Z",
  "clinicLocalDate": "2025-12-01",
  "patientName": "John Doe",
  "patientEmail": "john@example.com",
  "patientPhone": "+1403...",
  "reason": "Checkup",
  "status": "booked",
  "createdAt": "2025-11-25T12:20:00Z"
}
POST create
curl -X POST -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_KEY" \
  -d '{
    "doctorId": 20,
    "startUtc": "2025-12-01T14:00:00Z",
    "endUtc": "2025-12-01T14:30:00Z",
    "patientName": "John Doe",
    "patientEmail": "john@example.com",
    "patientPhone": "+1403...",
    "reason": "Checkup"
  }' "https://your-host/api/appointments"
Sample result
{
  "ok": true,
  "appointmentId": 89,
  "status": "booked",
  "message": "Created"
}
POST change status
curl -X POST -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_KEY" \
  -d '{ "status": "cancelled" }' \
  "https://your-host/api/appointments/89/status"
Sample result
{
  "ok": true,
  "appointmentId": 89,
  "status": "cancelled"
}

Responses & errors

Common error payloads

// 401 Unauthorized (missing/invalid key)
{ "message": "Invalid or missing API key." }

// 400 Bad request (validation)
{ "ok": false, "message": "startUtc and endUtc are required." }

// 409 Conflict (overlap)
{ "ok": false, "message": "The selected time overlaps an existing appointment." }

// 403 Forbidden (status rules)
{ "message": "External clients are not allowed to set status to 'confirmed'." }

PHI visibility

Depending on partner permissions, fields like patient email/phone/reason may be hidden. If PHI is restricted, reason may be omitted and contact fields may be null.

Time zones & clinic day

Server computes ClinicLocalDate based on the clinic’s configured time zone. Always display and filter dates in clinic time for consistent results.

Security, limits, and client management

Qlynic protects patient data with per‑partner API keys, permission scopes, and exhaustive request logging. Use the guidance below to keep integrations secure and performant.

Security & PHI

  • Per‑partner keys via API Clients. Keys are shown only once at creation/regeneration.
  • PHI controls: fields like patientEmail, patientPhone, and reason may be hidden depending on partner permissions.
  • Status updates: external clients can set cancelled only if authorized; confirmed is blocked for external clients.
  • Auditing: all requests are logged with method, path, query, IP, timestamp, and message.
Avoid sharing keys. Rotate periodically and track usage in your dashboard.

Rate limits & performance

  • Prefer range queries with status filters to reduce payload size.
  • Cache non‑sensitive reads briefly; avoid polling too frequently.
  • Respect clinic time zones using ClinicLocalDate for calendar grouping.
  • Batch POSTs responsibly; check overlaps before create requests.
Suggested GET pacing≤ 10/min per client
Suggested POST pacing≤ 30/min bursts

API Client management

Manage clients from Settings → API: create, reveal/regenerate keys, toggle active, or delete. Below are example payloads.

Create client

// UI action (Settings → API → New API client)
// Result shows key once (copy and store securely).
{
  "ok": true,
  "apiKey": "••••••••••••••••••••••••••••••••",
  "client": {
    "apiClientId": 101,
    "name": "Partner Alpha",
    "canSeePHI": true,
    "canManageStatus": false,
    "isActive": true,
    "createdAt": "2025-12-13 12:40"
  }
}

Regenerate / toggle

// Regenerate: invalidates old key; new key shown once.
// Toggle active: blocks all requests for that client until re-enabled.
{
  "ok": true,
  "apiKey": "••••••••••••••••••••••••••••••••",
  "message": "Regenerated"
}
{
  "ok": true,
  "message": "Updated" // after toggle active
}

Delete client

// Deletes the client; requests using that key will fail.
{
  "ok": true,
  "message": "Deleted"
}

Webhooks (optional)

If enabled for your clinic, Qlynic can POST event payloads to your endpoint (e.g., appointment created/cancelled). Validate source with a shared secret or signature header.

Webhook example

POST https://partner.example.com/webhooks/qlynic
Headers:
  X-Qlynic-Signature: sha256=...

Body:
{
  "event": "appointment.created",
  "clinicId": 9,
  "appointmentId": 89,
  "doctorId": 20,
  "startUtc": "2025-12-01T14:00:00Z",
  "endUtc": "2025-12-01T14:30:00Z",
  "status": "booked"
}

Verify signature (pseudo)

// Pseudo-code: verify header signature with shared secret
const sig = req.headers['x-qlynic-signature']; // "sha256=..."
const body = rawBody(req);
const ok = verifyHmacSha256(sig, body, SHARED_SECRET);
if (!ok) return res.status(401).send('Invalid signature');

SDKs, integration patterns, and FAQ

Use these snippets and patterns to integrate securely and reliably. Copy, adapt, and ship fast with Qlynic’s API.

Lightweight SDK snippets

JavaScript (Node/Browser)

const BASE = "https://your-host/api/appointments";
const KEY  = process.env.QLYNIC_API_KEY || "your-key";

// GET range with filters
async function listAppointments({ from, to, doctorId, status = "booked,confirmed" }){
  const url = new URL(BASE);
  if (from) url.searchParams.set("from", from);
  if (to) url.searchParams.set("to", to);
  if (doctorId) url.searchParams.set("doctorId", String(doctorId));
  if (status) url.searchParams.set("status", status);
  const res = await fetch(url.toString(), {
    headers: { "X-Api-Key": KEY }
  });
  if (!res.ok) throw new Error("HTTP " + res.status);
  return res.json();
}

// POST create
async function createAppointment(payload){
  const res = await fetch(BASE, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Api-Key": KEY },
    body: JSON.stringify(payload)
  });
  if (!res.ok) throw new Error("HTTP " + res.status);
  return res.json();
}

// POST change status (cancelled only for external clients)
async function setStatus(id, status){
  const res = await fetch(`${BASE}/${id}/status`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Api-Key": KEY },
    body: JSON.stringify({ status })
  });
  if (!res.ok) throw new Error("HTTP " + res.status);
  return res.json();
}

C# (HttpClient)

using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var BASE = "https://your-host/api/appointments";
var KEY  = Environment.GetEnvironmentVariable("QLYNIC_API_KEY") ?? "your-key";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("X-Api-Key", KEY);

// GET range
async Task<JsonDocument> GetRange(string from, string to, int? doctorId, string status = "booked,confirmed")
{
    var url = $"{BASE}?from={from}&to={to}&status={status}" + (doctorId.HasValue ? $"&doctorId={doctorId.Value}" : "");
    var res = await http.GetAsync(url);
    res.EnsureSuccessStatusCode();
    var body = await res.Content.ReadAsStringAsync();
    return JsonDocument.Parse(body);
}

// POST create
async Task<JsonDocument> Create(dynamic payload)
{
    var json = JsonSerializer.Serialize(payload);
    var res = await http.PostAsync(BASE, new StringContent(json, Encoding.UTF8, "application/json"));
    res.EnsureSuccessStatusCode();
    var body = await res.Content.ReadAsStringAsync();
    return JsonDocument.Parse(body);
}

// POST status
async Task<JsonDocument> SetStatus(int id, string status)
{
    var json = JsonSerializer.Serialize(new { status });
    var res = await http.PostAsync($"{BASE}/{id}/status", new StringContent(json, Encoding.UTF8, "application/json"));
    res.EnsureSuccessStatusCode();
    var body = await res.Content.ReadAsStringAsync();
    return JsonDocument.Parse(body);
}

Integration patterns

Retry & backoff (JS)

async function retry(fn, attempts = 4){
  let delay = 400; // ms
  for (let i = 0; i < attempts; i++){
    try { return await fn(); }
    catch(e){
      if (i === attempts - 1) throw e;
      await new Promise(r => setTimeout(r, delay));
      delay = Math.min(2000, delay * 1.6);
    }
  }
}

Signature verify (Node)

import crypto from "crypto";

function verifyHmacSha256(signatureHeader, rawBody, secret){
  if (!signatureHeader || !signatureHeader.startsWith("sha256=")) return false;
  const target = signatureHeader.slice("sha256=".length).trim();
  const h = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(h), Buffer.from(target));
}

Overlap pre-check (server)

// Pseudo SQL: prevent double booking (booked/confirmed)
SELECT COUNT(1)
FROM Appointments
WHERE DoctorId = @D
  AND Status IN ('booked','confirmed')
  AND StartUtc < @EndUtc
  AND EndUtc   > @StartUtc;

Environments & secrets

Sandbox vs production

  • Use separate keys and endpoints for sandbox and production.
  • Mask PHI in sandbox fixtures; avoid live patient data in tests.
  • Throttle sandbox to simulate realistic traffic.

Secret rotation

  • Rotate API keys periodically; reveal keys once, store securely.
  • Prefer environment variables over source‑code constants.
  • Monitor usage logs; revoke compromised keys immediately.

FAQ & troubleshooting

Why do I get 401?

Check X-Api-Key header spelling and that the client is active. Rotate keys if in doubt.

Why status won’t set to confirmed?

External clients cannot set confirmed. Only trusted internal flows can do so.

Timezone confusion?

Use ClinicLocalDate for UI grouping. Convert startUtc/endUtc using the clinic’s timezone.