Submitting a Consultation
A consultation is created with a single call:
POST /telehealth/consultations
Authenticate with your API key via the x-api-key header. The provider is
resolved from the key — a providerId in the body is never trusted. On success
the consultation is queued pending for a doctor and the response carries its
id and status.
See the endpoint reference for the full request and response schema. This guide covers the two things the schema alone does not make obvious: how identity is passed, and what each error means and how to react to it.
How identity is passed
Identity is derived from the patient, not from the request body. You do not attach document ids when creating a consultation.
Identity verification is required by default. A provider can make it optional for new Partner API consultations in the provider portal settings. The active policy is saved on each consultation when it is created, so changing the setting does not alter consultations already in progress.
When identity verification is optional, you can submit the consultation without an identity document or prior verification. Existing identity uploads are still accepted and retained, but the doctor can approve the consultation without recording an identity verdict.
When identity verification is required, the following rules apply.
The patient is addressed by patientReferenceId (your own stable id for the
person). At create time the identity gate passes when either holds:
- The patient has at least one uploaded ID document on file — minted earlier
via the upload-url endpoint
under the same
patientReferenceId, with its bytes successfully uploaded to the presigned URL; or - The patient was doctor-verified on a prior consultation — that verdict is carried forward automatically, so a returning patient needs no new document.
If neither holds, the request fails with IdentityRequired (see
Error cases).
The flow for a new patient
Step 3 is the recommended pre-check when identity verification is required: it
reports identified (the predicate the create gate enforces in that mode) and
lists each document with whether its upload has landed. Use it to avoid a failed
submission, especially right after a large upload. See
Get patient identity.
Documents minted before the patient first appears on a consultation are linked retroactively, so you may upload an ID and create the patient's first consultation in either order.
Allowed document types
contentType on the upload-url request must be one of:
| Content type | Notes |
|---|---|
image/jpeg | Preferred for photos. |
image/png | |
image/webp | |
application/pdf | For scanned documents. |
Anything else is rejected 415 UnsupportedMediaType. The match is exact and
case-sensitive — send image/jpeg, not image/jpg, IMAGE/JPEG, or
image/jpeg; charset=….
iPhone photos. Photos taken natively on iOS are HEIC/HEIF, which are not accepted. Almost every file picker and share sheet transcodes HEIC to JPEG on the way out, so a normal upload arrives as
image/jpegand is fine. Only a client uploading the raw camera asset would send HEIC — convert it to JPEG first.
Uploading the bytes. Send the returned
headersverbatim and do not changeContent-Type— the value is part of the presigned signature, so a mismatch is rejected by object storage with an opaqueSignatureDoesNotMatch. The URL is short-lived (expiresIn, ~15 minutes), so upload promptly after minting.maxBytesis the size limit you must keep the upload under.
Error cases
Every error response carries a stable id — branch on id, not on the
HTTP status or the human-readable message. Most also carry a payload with
the specifics. The create endpoint can return:
| HTTP | id | Meaning | What to do |
|---|---|---|---|
| 400 | InvalidArguments | The body failed validation. | Fix the request. payload.issues lists the offending paths for schema-level failures; otherwise payload.message carries the reason. Do not retry unchanged. |
| 401 | Unauthorized | Missing or invalid API key. | Check the x-api-key header. |
| 403 | TelemedicineProviderNotActive | Your provider exists but is not active. | Contact Cannaflow; not retryable by you. |
| 404 | NotFound | An addressed resource does not exist (e.g. the questionnaire slug). | Inspect payload.kind / payload.entities. Re-fetch the questionnaire list. |
| 409 | QuestionnaireVersionMismatch | The pinned questionnaire.version is no longer the approved one. | Re-fetch the questionnaire, re-render, and resubmit with the current version. payload carries pinned and current. |
| 422 | QuestionnaireNotApplicable | This questionnaire can't be submitted for this patient yet (e.g. a follow-up before any completed consultation). | Submit the applicable questionnaire first. payload carries questionnaireId, expected, reason. |
| 422 | DisqualifyingAnswer | An answer disqualifies the patient from the requested treatment type. The consultation is rejected without a doctor. | Surface to the patient; do not resubmit the same answers. payload carries questionId, answerId, treatmentType. |
| 422 | PatientUnderMinimumAge | The patient is younger than the provider's minimum age (see Limits & Policy). | Do not submit until the patient meets the minimum. payload carries patientAge, minimumAge. |
| 422 | PrescriptionItemLimitExceeded | More medication items than the provider allows per consultation (see Limits & Policy). | Reduce requestedMedications. payload carries limit, requested. |
| 422 | MonthlyPrescribedQuantityLimitExceeded | Issuing would push the patient past the provider's monthly quantity cap (see Limits & Policy). | Reduce the quantity or wait for the next period. payload carries limit, alreadyPrescribed, requested. |
| 422 | IdentityRequired | Identity is required for this consultation, but there is no uploaded ID on file and no prior doctor-verified identity. | Complete the identity flow for payload.patientReferenceId, then resubmit. |
| 500 | InternalError | Unexpected server error. | Safe to retry with backoff. payload.id is a correlation id — include it when contacting support. |
Gate order
The create gates run in order and the call fails on the first one that does not pass:
- Questionnaire exists (
NotFound) - Pinned version is current (
QuestionnaireVersionMismatch) - Questionnaire is applicable to the patient (
QuestionnaireNotApplicable) - Answers are structurally valid and not disqualifying (
InvalidArguments,DisqualifyingAnswer) - Patient meets the provider's minimum age (
PatientUnderMinimumAge) - Medications fit the per-consultation item cap and the patient's monthly
quantity cap (
PrescriptionItemLimitExceeded,MonthlyPrescribedQuantityLimitExceeded) - Identity is satisfied when the provider's current policy requires it
(
IdentityRequired)
Because the checks short-circuit, fixing one error can surface the next — fix them in this order to converge quickly.
Example: IdentityRequired response
{
"id": "IdentityRequired",
"message": "An identity document is required for this patient.",
"payload": { "patientReferenceId": "tp-patient-4471" }
}
Example: QuestionnaireVersionMismatch response
{
"id": "QuestionnaireVersionMismatch",
"message": "The pinned questionnaire version is no longer current.",
"payload": { "pinned": 3, "current": 4 }
}