Scheduling an administrative meeting
Book an internal meeting — a staff huddle, a vendor call, a blocked planning hour — as a FHIR R4 Appointment that has no patient. An administrative meeting is an appointment against a service whose appointment type is administrative; it carries a title, may run all day, and may invite external email attendees who are not Huli users. The scopes are the booking ones: system/Appointment.cru for the write and system/Practitioner.rs for the practitioner wiring.
The shape differs from a clinical booking in three load-bearing ways, and the server enforces all three: an administrative meeting requires a title, rejects a patient participant, and may carry external invitees — while a clinical appointment rejects the title and invitees and expects a patient. Pick the right service and the rest follows.
Audience
You build an internal scheduling tool, a calendar sync, or an operations integration that books non-clinical time on a practitioner's calendar. You have already booked a clinical appointment — this recipe reuses that recipe's discovery (service, practitioner, room, slot) and changes only the create body.
You'll need
- A bearer token from HuliPractice (Practice Settings → Integrations → API Keys), or a SMART Backend Services access token. See Bearer Tokens and
POST /auth/token. - These two scopes on that token:
- system/Appointment.cru — create
Appointment(and the gated discoveryresourcesHealthcareService,Location,Schedule,Slot). - system/Practitioner.rs — read + search
PractitionerandPractitionerRole.
- system/Appointment.cru — create
- A service configured as an administrative meeting type, plus a practitioner and a free slot — discovered exactly as in Booking an appointment end-to-end (steps 2–5 there). The one difference is the service: pick one whose appointment type is
administrative. A room (Location) participant is optional for administrative meetings — clinical bookings require one, administrative meetings do not. curl, or Node, Python, Java, or Go.
End state
You hold a 201 Created whose body is the stored Appointment — no patient participant, a huli-appointment-title extension carrying the meeting name, the practitioner participant (plus a room participant if you sent one), and (if you sent them) huli-appointment-external-attendee extensions for the email invitees. It is searchable with appointment-type=administrative.
Steps
1. Export the token
export HULI_TOKEN="<paste your bearer token here>"
2. Find the administrative service and a slot
Discover the bookable values exactly as in Booking an appointment end-to-end: the serviceType coding (from HealthcareService.type), a practitioner and their room (PractitionerRole), and a free Slot. Pick a service whose appointment type is administrative.
To confirm a service is the administrative one, list existing administrative meetings — the appointment-type token filters on the service's derived type:
/fhir/R4/Appointment?appointment-type=administrative&_count=20curl "https://api.huli.ai/fhir/R4/Appointment?appointment-type=administrative&_count=20" \
-H "Authorization: Bearer $HULI_TOKEN" \
-H "Accept: application/fhir+json"
Each match carries its serviceType coding and its huli-appointment-title extension — copy the serviceType of one to book another meeting against the same service.
3. POST the administrative Appointment
Assemble the discovered serviceType, the slot's start/end, and the practitioner + room participants — with no Patient participant. Add the required huli-appointment-title extension, an optional huli-appointment-all-day boolean, and one huli-appointment-external-attendee extension per email invitee (each nests a required email and an optional displayName; up to 30).
/fhir/R4/Appointmentcurl -i -X POST https://api.huli.ai/fhir/R4/Appointment \
-H "Authorization: Bearer $HULI_TOKEN" \
-H "Content-Type: application/fhir+json" \
-H "Accept: application/fhir+json" \
-d '{
"resourceType": "Appointment",
"status": "booked",
"description": "Revisión mensual de operaciones",
"serviceType": [
{
"coding": [
{
"system": "https://fhir.huli.ai/r4/CodeSystem/org-service",
"code": "01965e2a-8c4d-7000-9010-0000000000fa",
"display": "Reunión administrativa"
}
]
}
],
"start": "2026-06-18T15:00:00.000-06:00",
"end": "2026-06-18T16:00:00.000-06:00",
"participant": [
{
"actor": { "reference": "Practitioner/01965e2a-8c4d-7000-9001-0000000000c1" },
"status": "accepted"
},
{
"actor": { "reference": "Location/01965e2a-8c4d-7000-9020-0000000000a1" },
"status": "accepted"
}
],
"extension": [
{
"url": "https://fhir.huli.ai/r4/StructureDefinition/huli-appointment-title",
"valueString": "Revisión mensual de operaciones"
},
{
"url": "https://fhir.huli.ai/r4/StructureDefinition/huli-appointment-all-day",
"valueBoolean": false
},
{
"url": "https://fhir.huli.ai/r4/StructureDefinition/huli-appointment-external-attendee",
"extension": [
{ "url": "email", "valueString": "proveedor@ejemplo.com" },
{ "url": "displayName", "valueString": "Proveedor Externo" }
]
}
]
}'
const ext = 'https://fhir.huli.ai/r4/StructureDefinition';
const meeting = {
resourceType: 'Appointment',
status: 'booked',
description: 'Revisión mensual de operaciones',
serviceType: [
{
coding: [
{
system: 'https://fhir.huli.ai/r4/CodeSystem/org-service',
code: '01965e2a-8c4d-7000-9010-0000000000fa', // an administrative-type service
display: 'Reunión administrativa',
},
],
},
],
start: '2026-06-18T15:00:00.000-06:00',
end: '2026-06-18T16:00:00.000-06:00',
participant: [
// No Patient participant — an administrative meeting rejects one.
{
actor: { reference: 'Practitioner/01965e2a-8c4d-7000-9001-0000000000c1' },
status: 'accepted',
},
{ actor: { reference: 'Location/01965e2a-8c4d-7000-9020-0000000000a1' }, status: 'accepted' },
],
extension: [
{ url: `${ext}/huli-appointment-title`, valueString: 'Revisión mensual de operaciones' }, // required
{ url: `${ext}/huli-appointment-all-day`, valueBoolean: false }, // optional
{
// optional — repeat per invitee, up to 30
url: `${ext}/huli-appointment-external-attendee`,
extension: [
{ url: 'email', valueString: 'proveedor@ejemplo.com' }, // required within the block
{ url: 'displayName', valueString: 'Proveedor Externo' }, // optional
],
},
],
};
const resp = await fetch('https://api.huli.ai/fhir/R4/Appointment', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HULI_TOKEN}`,
'Content-Type': 'application/fhir+json',
Accept: 'application/fhir+json',
},
body: JSON.stringify(meeting),
});
if (resp.status === 201) {
console.log('booked', (await resp.json()).id);
} else {
const outcome = (await resp.json()) as { issue: { diagnostics: string }[] };
console.log(resp.status, outcome.issue[0].diagnostics);
}
import os
import requests
ext = "https://fhir.huli.ai/r4/StructureDefinition"
meeting = {
"resourceType": "Appointment",
"status": "booked",
"description": "Revisión mensual de operaciones",
"serviceType": [
{
"coding": [
{
"system": "https://fhir.huli.ai/r4/CodeSystem/org-service",
"code": "01965e2a-8c4d-7000-9010-0000000000fa", # an administrative-type service
"display": "Reunión administrativa",
}
]
}
],
"start": "2026-06-18T15:00:00.000-06:00",
"end": "2026-06-18T16:00:00.000-06:00",
"participant": [
# No Patient participant — an administrative meeting rejects one.
{"actor": {"reference": "Practitioner/01965e2a-8c4d-7000-9001-0000000000c1"}, "status": "accepted"},
{"actor": {"reference": "Location/01965e2a-8c4d-7000-9020-0000000000a1"}, "status": "accepted"},
],
"extension": [
{"url": f"{ext}/huli-appointment-title", "valueString": "Revisión mensual de operaciones"}, # required
{"url": f"{ext}/huli-appointment-all-day", "valueBoolean": False}, # optional
{
# optional — repeat per invitee, up to 30
"url": f"{ext}/huli-appointment-external-attendee",
"extension": [
{"url": "email", "valueString": "proveedor@ejemplo.com"}, # required within the block
{"url": "displayName", "valueString": "Proveedor Externo"}, # optional
],
},
],
}
resp = requests.post(
"https://api.huli.ai/fhir/R4/Appointment",
headers={
"Authorization": f"Bearer {os.environ['HULI_TOKEN']}",
"Content-Type": "application/fhir+json",
"Accept": "application/fhir+json",
},
json=meeting,
timeout=30,
)
print(resp.status_code, resp.json().get("id") or resp.json()["issue"][0]["diagnostics"])
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class AdminMeeting {
public static void main(String[] args) throws Exception {
// No Patient participant; the title extension is required; all-day and
// external-attendee extensions are optional. Hand-built JSON keeps this
// dependency-free; a real client would use a JSON library.
String ext = "https://fhir.huli.ai/r4/StructureDefinition/";
String body = "{"
+ "\"resourceType\":\"Appointment\",\"status\":\"booked\","
+ "\"description\":\"Revisión mensual de operaciones\","
+ "\"serviceType\":[{\"coding\":[{"
+ "\"system\":\"https://fhir.huli.ai/r4/CodeSystem/org-service\","
+ "\"code\":\"01965e2a-8c4d-7000-9010-0000000000fa\"}]}],"
+ "\"start\":\"2026-06-18T15:00:00.000-06:00\","
+ "\"end\":\"2026-06-18T16:00:00.000-06:00\","
+ "\"participant\":["
+ "{\"actor\":{\"reference\":\"Practitioner/01965e2a-8c4d-7000-9001-0000000000c1\"},\"status\":\"accepted\"},"
+ "{\"actor\":{\"reference\":\"Location/01965e2a-8c4d-7000-9020-0000000000a1\"},\"status\":\"accepted\"}],"
+ "\"extension\":["
+ "{\"url\":\"" + ext + "huli-appointment-title\",\"valueString\":\"Revisión mensual de operaciones\"},"
+ "{\"url\":\"" + ext + "huli-appointment-external-attendee\",\"extension\":["
+ "{\"url\":\"email\",\"valueString\":\"proveedor@ejemplo.com\"},"
+ "{\"url\":\"displayName\",\"valueString\":\"Proveedor Externo\"}]}]}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.huli.ai/fhir/R4/Appointment"))
.header("Authorization", "Bearer " + System.getenv("HULI_TOKEN"))
.header("Content-Type", "application/fhir+json")
.header("Accept", "application/fhir+json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
// No Patient participant; the title extension is required; all-day and
// external-attendee extensions are optional.
body := []byte(`{
"resourceType": "Appointment",
"status": "booked",
"description": "Revisión mensual de operaciones",
"serviceType": [{"coding": [{
"system": "https://fhir.huli.ai/r4/CodeSystem/org-service",
"code": "01965e2a-8c4d-7000-9010-0000000000fa"
}]}],
"start": "2026-06-18T15:00:00.000-06:00",
"end": "2026-06-18T16:00:00.000-06:00",
"participant": [
{"actor": {"reference": "Practitioner/01965e2a-8c4d-7000-9001-0000000000c1"}, "status": "accepted"},
{"actor": {"reference": "Location/01965e2a-8c4d-7000-9020-0000000000a1"}, "status": "accepted"}
],
"extension": [
{"url": "https://fhir.huli.ai/r4/StructureDefinition/huli-appointment-title", "valueString": "Revisión mensual de operaciones"},
{"url": "https://fhir.huli.ai/r4/StructureDefinition/huli-appointment-all-day", "valueBoolean": false},
{"url": "https://fhir.huli.ai/r4/StructureDefinition/huli-appointment-external-attendee", "extension": [
{"url": "email", "valueString": "proveedor@ejemplo.com"},
{"url": "displayName", "valueString": "Proveedor Externo"}
]}
]
}`)
req, err := http.NewRequest(http.MethodPost,
"https://api.huli.ai/fhir/R4/Appointment", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("HULI_TOKEN"))
req.Header.Set("Content-Type", "application/fhir+json")
req.Header.Set("Accept", "application/fhir+json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Printf("%d\n%s\n", resp.StatusCode, out)
}
A 201 Created returns the stored meeting. The huli-appointment-title and (when true) huli-appointment-all-day extensions round-trip on every read; the huli-appointment-external-attendee extensions round-trip on a single-resource GET, create, and update — but never on a search, because invitee emails are PII held off the search keyset path.
4. Edit invitees and flip a meeting later
A PUT is a full replace. Supplying huli-appointment-external-attendee extension(s) replaces the invitee set; omitting them leaves it unchanged. Because a PUT replaces everything, you can flip a meeting between administrative and clinical: omit the title (clears it) and add a Patient participant to turn a meeting into a clinical appointment against a clinical service, or the reverse.
What to verify
- HTTP status is
201. 201 Created The responseresourceTypeisAppointmentwith a server-assignedid. - There is no
Patientparticipant, and the practitioner + room participants are present. - The
huli-appointment-titleextension round-trips with your meeting name. - The meeting appears in
GET /fhir/R4/Appointment?appointment-type=administrative. - A single-resource
GETshows thehuli-appointment-external-attendeeextensions; the same resource in a search result does not.
What can go wrong
All errors return a FHIR OperationOutcome — {severity, code, diagnostics}, no details object. Structural problems surface as HPB-00101; the booking preconditions (practitioner, room, slot conflict) surface the practice-layer HP-008xx codes documented in Booking an appointment end-to-end.
administrative service requires huli-appointment-title; omitting it is rejected with issue[0].code required and an expression of Appointment.extension(huli-appointment-title). Add the title extension.
huli-appointment-title or huli-appointment-external-attendee against a clinical service is rejected with a value issue — those fields belong only to administrative meetings. Either drop them or book against an administrative service.
meeting must not name a Patient participant. Remove it (administrative meetings are internal — the external-attendee extensions carry guests instead).
booking apply: the practitioner must have the room in their assigned locations, the slot must be free (409 HP-00803), and a multi-specialty service still needs an Appointment.specialty selection (HPB-00115/HPB-00116). See the booking recipe's What can go wrong.
Next recipes
- Booking an appointment end-to-end — the clinical counterpart, with the full discovery flow this recipe reuses.
- Wiring a read-only partner — consume the appointment feed read-only, filtering administrative vs clinical with
appointment-type.