Skip to main content

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:

  1. 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
  2. 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 typeNotes
image/jpegPreferred for photos.
image/png
image/webp
application/pdfFor 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/jpeg and is fine. Only a client uploading the raw camera asset would send HEIC — convert it to JPEG first.

Uploading the bytes. Send the returned headers verbatim and do not change Content-Type — the value is part of the presigned signature, so a mismatch is rejected by object storage with an opaque SignatureDoesNotMatch. The URL is short-lived (expiresIn, ~15 minutes), so upload promptly after minting. maxBytes is 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:

HTTPidMeaningWhat to do
400InvalidArgumentsThe 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.
401UnauthorizedMissing or invalid API key.Check the x-api-key header.
403TelemedicineProviderNotActiveYour provider exists but is not active.Contact Cannaflow; not retryable by you.
404NotFoundAn addressed resource does not exist (e.g. the questionnaire slug).Inspect payload.kind / payload.entities. Re-fetch the questionnaire list.
409QuestionnaireVersionMismatchThe 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.
422QuestionnaireNotApplicableThis 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.
422DisqualifyingAnswerAn 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.
422PatientUnderMinimumAgeThe 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.
422PrescriptionItemLimitExceededMore medication items than the provider allows per consultation (see Limits & Policy).Reduce requestedMedications. payload carries limit, requested.
422MonthlyPrescribedQuantityLimitExceededIssuing 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.
422IdentityRequiredIdentity 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.
500InternalErrorUnexpected 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:

  1. Questionnaire exists (NotFound)
  2. Pinned version is current (QuestionnaireVersionMismatch)
  3. Questionnaire is applicable to the patient (QuestionnaireNotApplicable)
  4. Answers are structurally valid and not disqualifying (InvalidArguments, DisqualifyingAnswer)
  5. Patient meets the provider's minimum age (PatientUnderMinimumAge)
  6. Medications fit the per-consultation item cap and the patient's monthly quantity cap (PrescriptionItemLimitExceeded, MonthlyPrescribedQuantityLimitExceeded)
  7. 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 }
}