Public developer reference Base URL https://bespoke-consults.designingit.co/v1 Updated Oct 11, 2026

Questionnaires

Intake questions live in a shared catalog. Load them, show a later question only when an earlier answer matches, then submit the full set on the consult. A visit can be opened first and the questionnaire attached later. Once you send questionnaire, every question that applies must be answered or the API returns 422 and stores nothing.

Load the form

GET https://bespoke-consults.designingit.co/v1/question-categories lists categories. Send the category slug as questionnaire_type on the visit. GLP-1 is glp-1. glp1 is accepted as the same category.

curl "https://bespoke-consults.designingit.co/v1/questions?category=glp-1" \
  -H "X-Api-Key: $BESPOKE_KEY" \
  -H "X-Api-Secret: $BESPOKE_SECRET" \
  -H "Accept: application/json"

Each question includes key, label, type, options, visible_when, validation, and config. Use key as the answer id.

Which questionnaire

A category can contain more than one form. ED has sildenafil and tadalafil. GLP-1 has one form, weight.

curl "https://bespoke-consults.designingit.co/v1/questions?category=ed&questionnaire=tadalafil" \
  -H "X-Api-Key: $BESPOKE_KEY" \
  -H "X-Api-Secret: $BESPOKE_SECRET" \
  -H "Accept: application/json"

When you submit, send questionnaire.code if the category has more than one form. A category with a single form does not need code.

Answer types

TypeWhat to collect
yes_no, single_selectOne value from options.
multi_selectAn array of one or more values from options.
yes_no_with_answerOne option. If config.requires_note_for matches, also send the note.
short_text, long_text, number, date, weightA typed value. date is YYYY-MM-DD. weight is pounds.
height{"feet": 5, "inches": 10} or 5'10.
infoShow the label. Do not collect or send an answer.

Conditional questions

visible_when is null on questions you always show. When it is an object, show that question only after the rule matches an earlier answer. Hide it otherwise, and do not send an answer for it. The API does not require a hidden question. It does require it once the rule matches.

FieldMeaning
question_keyThe earlier question key.
operatorHow to compare. See below.
valueThe answer to compare against.
height_keyUsed by bmi_below. Usually height.
OperatorShow the question when
equalsThe earlier answer equals value. For a multi-select, any selected value may match.
not_equalsThe earlier answer does not equal value.
containsA multi-select includes value, or a text answer contains it.
inThe answer is one of the values in value (an array).
not_inThe answer is not in that array.
answeredThe earlier question has any answer.
not_answeredThe earlier question has no answer.
bmi_belowBMI from the weight answer and height_key is below value.

A rule may also be {"all": [ ...rules ]} (every rule matches) or {"any": [ ...rules ]} (one rule matches). BMI is pounds × 703 / inches², rounded to one decimal. Height in the BMI check is the height answer already collected.

GLP-1 follow-ups

The GLP-1 form (questionnaire_type: glp-1, code weight) always includes the health-history questions. Three questions appear only after an earlier answer:

QuestionShow it when
highest_weight BMI from pounds and height is under 30. The answer itself must still produce a BMI of at least 30 (validation.min_bmi). Otherwise the API rejects it with “Your BMI should be 30 or higher”.
glp1_allergy_contact glp1_allergy is Yes. Collect a phone number.
glp1_current_medication glp1_current_use is Yes, currently taking. Show the weekly milligram doses for the medication chosen in weight_product. Semaglutide: 0.25, 0.5, 1, 1.7, 2.5 mg. Tirzepatide: 2.25, 4.5, 6.75, 9, 11.25, 15 mg. Always include the “I'm not sure…” option from config.extra_options. config.options_by_answer is the same map.
If current BMI is 30 or higher, glp1_allergy is No, and glp1_current_use is No, leave those three questions off the payload. Sending them is not required. Omitting a question that should be on screen is rejected.

list_medications is always shown. config.skip_answer is I don't take any medications. That exact string is a complete answer.

weight_product is semaglutide or tirzepatide. info screens (weight_loss_intro, weight_projection, qualified_for_treatment) are display only.

Extra text on an option

Some options set reveals_input. When the patient chooses that option and required is true, collect the extra field (often “Please specify”) and send it with the answer. On GLP-1, what_do_you_want does this for “I have another goal not listed above”.

{
  "id": "what_do_you_want",
  "answer": {
    "values": ["Lose weight", "I have another goal not listed above"],
    "inputs": {
      "I have another goal not listed above": "Keep muscle while losing fat"
    }
  }
}

A single choice with extra text can be {"value": "Yes", "input": "the explanation"}. A yes_no_with_answer question uses the same shape when config.requires_note_for matches the choice. note is accepted in place of input.

Submit answers

Send the questionnaire on POST https://bespoke-consults.designingit.co/v1/visits or later with PUT https://bespoke-consults.designingit.co/v1/visits/{id}/questionnaire. POST to that same path is an alias. Replacing overwrites the previous answers.

curl -X PUT "https://bespoke-consults.designingit.co/v1/visits/consult-1001/questionnaire" \
  -H "X-Api-Key: $BESPOKE_KEY" \
  -H "X-Api-Secret: $BESPOKE_SECRET" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "questionnaire_type": "glp-1",
    "questionnaire": {
      "code": "weight",
      "type": "glp-1",
      "answers": [
        { "id": "weight_product", "answer": "semaglutide" },
        { "id": "looking_to_lose_2", "answer": "21-50 lbs" },
        { "id": "weight_loss_barrier", "answer": "Low energy/fatigue" },
        { "id": "height", "answer": { "feet": 5, "inches": 10 } },
        { "id": "pounds", "answer": "220" },
        { "id": "what_do_you_want", "answer": ["Lose weight"] }
      ]
    }
  }'

The snippet above is not a full GLP-1 form. Every other non-info question that is visible must be in answers as well. With height 5'10" and 220 lb, BMI is about 31.6, so highest_weight stays hidden. glp1_allergy: "No" hides the phone question. glp1_current_use: "No" hides the dose question.

Required answers

The API accepts the questionnaire only when all of the following are true:

  • Every visible question except info has an answer.
  • A select answer is one of that question’s options (and, for the GLP-1 dose, a dose for the chosen medication or the “not sure” option).
  • A follow-up that visible_when has turned on is present.
  • Required reveals_input text is present when that option is chosen.
  • Numeric, height, date, and validation rules pass, including highest_weight minimum BMI.

You can still POST /visits without a questionnaire and attach it later. A payload that is a medication cart (requests or items) is not checked against the intake catalog.

Error response

A missing or invalid answer returns HTTP 422 with error.code validation_failed. The consult is not updated. error.fields["questionnaire.answers"] lists the missing keys. Each questionnaire.answers.{key} field says what is wrong with that answer.

{
  "error": {
    "code": "validation_failed",
    "message": "The submission could not be processed because some fields are invalid.",
    "fields": {
      "questionnaire.answers": [
        "Answer every question that applies before submitting. Still missing: height, pounds, glp1_allergy_contact."
      ],
      "questionnaire.answers.height": ["This question is required."],
      "questionnaire.answers.pounds": ["This question is required."],
      "questionnaire.answers.glp1_allergy_contact": ["This question is required."]
    }
  }
}

glp1_allergy_contact appears in that list only when glp1_allergy is Yes. Fix the listed questions and submit the full questionnaire again.