{"openapi":"3.2.0","$self":"https://ihgiz.net/api/engine/v0/documents/openapi.json","info":{"title":"IHGIZ Event Engine","version":"0.1.0","summary":"One Event, defined once, for every IHGIZ template.","description":"The engine underneath every IHGIZ product. Whatever a template calls it —\na reservation in Events, Autos and Homes, an appointment, a registration —\nthe root is one **Event**: something that happens at a time and a place,\nwith people taking part, that changes as it moves through its stages.\n\n* **The Event** is shaped on JSCalendar (RFC 8984) and converts to and from\n  iCalendar (RFC 5545) without loss. Engine properties that JSCalendar does\n  not define carry the vendor prefix `ihgiz.net:`, as RFC 8984 §3.3 requires.\n* **Participants** are people (or groups, places and resources such as a car\n  or a service bay) with standard JSCalendar roles and answers. The template\n  names what each one is to it — guest, member, client, driver — in\n  `ihgiz.net:role`, and every person points at one platform record,\n  `ihgiz.net:party`.\n* **Every change is a transition.** Rescheduling, moving, adding or removing\n  people, confirming, cancelling: each is a named transition in the\n  template's state graph (`/schemas/state-graph.schema.json`), applied by\n  the lifecycle engine, which increments `sequence`, writes one history\n  entry, and raises the messages and webhooks it causes in the same\n  transaction. There is no other way to change an Event.\n* **Changes follow iTIP (RFC 5546).** A transition that participants are told\n  about carries its method — REQUEST to invite or reschedule, REPLY for an\n  answer, COUNTER for a proposed new time, CANCEL.\n\nStatus: served at https://ihgiz.net/api/engine/v0 since Phase 2.2\n(`docs/phase-2.md`); decision records in `docs/decisions/`.\nVersioning: Semantic Versioning on `info.version`; a change that breaks a\nclient is a new major version and a new `/v{major}` base path.\n","contact":{"name":"IHGIZ","url":"https://ihgiz.net"},"license":{"name":"Proprietary","identifier":"LicenseRef-IHGIZ-Proprietary"}},"servers":[{"url":"https://ihgiz.net/api/engine/v0","description":"Served since Phase 2.2."},{"url":"https://api.ihgiz.net/engine/v0","description":"Planned, its own host."}],"tags":[{"name":"Events","summary":"Read and create Events."},{"name":"Transitions","summary":"The one way an Event changes."},{"name":"Participants","summary":"Answers from the people taking part."},{"name":"Calendar","summary":"iCalendar views of an Event."},{"name":"Templates","summary":"What each template declares."},{"name":"Entities","summary":"Things that are not Events but have a lifecycle — a membership, a deal, an order."},{"name":"Webhooks","summary":"Where the engine posts each change (asyncapi.yaml)."}],"security":[{"bearerAuth":[]}],"paths":{"/events":{"get":{"tags":["Events"],"operationId":"listEvents","summary":"List Events","description":"Events of the caller's tenant, newest start first, filtered by template, type, phase, time window or person.","security":[{"bearerAuth":["events:read"]}],"parameters":[{"name":"template","in":"query","schema":{"$ref":"#/components/schemas/Key"}},{"name":"eventType","in":"query","schema":{"$ref":"#/components/schemas/Key"}},{"name":"phase","in":"query","style":"form","explode":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/Phase"}}},{"name":"startsAfter","in":"query","description":"UTC; Events whose start is at or after this instant.","schema":{"$ref":"#/components/schemas/UTCDateTime"}},{"name":"startsBefore","in":"query","description":"UTC; Events whose start is before this instant.","schema":{"$ref":"#/components/schemas/UTCDateTime"}},{"name":"party","in":"query","description":"Only Events this person takes part in.","schema":{"type":"string"}},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"A page of Events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["Events"],"operationId":"createEvent","summary":"Create an Event","description":"Creates an Event in its graph's initial state, or runs one named\ntransition from it in the same call (for instance a booking that\nconfirms itself). The history records who created it.\n","security":[{"bearerAuth":["events:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventRequest"}}}},"responses":{"201":{"description":"Created.","headers":{"Location":{"description":"The new Event's URL.","schema":{"type":"string","format":"uri-reference"}},"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitionResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Refused"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/events/{uid}":{"parameters":[{"$ref":"#/components/parameters/Uid"}],"get":{"tags":["Events"],"operationId":"getEvent","summary":"Get an Event","security":[{"bearerAuth":["events:read"]}],"responses":{"200":{"description":"The Event. Its ETag is its sequence; send it back in If-Match to change it.","headers":{"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/events/{uid}/transitions":{"parameters":[{"$ref":"#/components/parameters/Uid"}],"get":{"tags":["Transitions"],"operationId":"listAvailableTransitions","summary":"Transitions the caller may fire now","description":"The transitions of the Event's graph that start from its current state and that the caller's actor kind may fire, with guards evaluated. Consoles render only these.","security":[{"bearerAuth":["events:read"]}],"parameters":[{"name":"participant","in":"query","description":"For participation graphs, the participant whose part is meant.","schema":{"$ref":"#/components/schemas/Id"}}],"responses":{"200":{"description":"Available transitions.","content":{"application/json":{"schema":{"type":"object","required":["state","phase","transitions"],"properties":{"state":{"$ref":"#/components/schemas/Key"},"phase":{"$ref":"#/components/schemas/Phase"},"transitions":{"type":"array","items":{"$ref":"#/components/schemas/AvailableTransition"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Transitions"],"operationId":"applyTransition","summary":"Change an Event","description":"The one write path. Applies a named transition from the template's\ngraph, with the changes it allows (new time, place, people), and\nreturns the Event as it now stands with the history entry, the\nmessages and the iTIP method it raised. Refused with 422 and the name\nof the guard that said no; with 412 when If-Match is not the current\nsequence.\n","security":[{"bearerAuth":["events:transition"]}],"parameters":[{"$ref":"#/components/parameters/IfMatch"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitionRequest"}}}},"responses":{"200":{"description":"Applied.","headers":{"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitionResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Refused"},"428":{"$ref":"#/components/responses/PreconditionRequired"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/events/{uid}/history":{"parameters":[{"$ref":"#/components/parameters/Uid"}],"get":{"tags":["Transitions"],"operationId":"getEventHistory","summary":"Everything that happened to an Event","description":"Append-only, oldest first. Answers \"who changed this, from what, to what, and why\" from the record alone.","security":[{"bearerAuth":["events:read"]}],"parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"History entries.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/HistoryEntry"}},"next":{"$ref":"#/components/schemas/NextCursor"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/events/{uid}/participants/{participantId}/reply":{"parameters":[{"$ref":"#/components/parameters/Uid"},{"name":"participantId","in":"path","required":true,"schema":{"$ref":"#/components/schemas/Id"}}],"post":{"tags":["Participants"],"operationId":"replyAsParticipant","summary":"A participant answers","description":"A participant accepts, declines or answers tentatively, optionally\nproposing a new time (iTIP COUNTER). This is the reply transition of\nthe Event's graph, fired as that participant; it is authorised by the\nparticipant's own reply token, so a guest needs no account.\n","security":[{"replyToken":[]},{"bearerAuth":["events:transition"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyRequest"}}}},"responses":{"200":{"description":"Recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitionResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Refused"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/events/{uid}/calendar":{"parameters":[{"$ref":"#/components/parameters/Uid"}],"get":{"tags":["Calendar"],"operationId":"getEventCalendar","summary":"The Event as an iCalendar file","description":"RFC 5545 VCALENDAR with one VEVENT (or the series), with the iTIP method requested. The same UID and SEQUENCE as every message about the Event.","security":[{"bearerAuth":["events:read"]},{"replyToken":[]}],"parameters":[{"name":"method","in":"query","schema":{"type":"string","enum":["PUBLISH","REQUEST"],"default":"PUBLISH"}},{"name":"participant","in":"query","description":"With a reply token, the participant it was given to.","schema":{"$ref":"#/components/schemas/Id"}}],"responses":{"200":{"description":"The calendar object.","content":{"text/calendar":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/entities":{"get":{"tags":["Entities"],"operationId":"listEntities","summary":"A page of the tenant's entities, newest first","security":[{"bearerAuth":["entities:read"]}],"parameters":[{"name":"template","in":"query","schema":{"$ref":"#/components/schemas/Key"}},{"name":"entityType","in":"query","schema":{"$ref":"#/components/schemas/Key"}},{"name":"subject","in":"query","description":"The product record it stands for.","schema":{"type":"string"}},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"A page of entities.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Entity"}},"next":{"$ref":"#/components/schemas/NextCursor"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Entities"],"operationId":"createEntity","summary":"Create an entity in its graph's initial state","security":[{"bearerAuth":["entities:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEntityRequest"}}}},"responses":{"201":{"description":"Created.","headers":{"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Refused"}}}},"/entities/{uid}":{"get":{"tags":["Entities"],"operationId":"getEntity","summary":"An entity, with where it stands in each region","security":[{"bearerAuth":["entities:read"]}],"parameters":[{"$ref":"#/components/parameters/Uid"}],"responses":{"200":{"description":"The entity.","headers":{"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Entity"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/entities/{uid}/transitions":{"get":{"tags":["Entities"],"operationId":"listEntityTransitions","summary":"What the caller may do to the entity now, region by region","security":[{"bearerAuth":["entities:read"]}],"parameters":[{"$ref":"#/components/parameters/Uid"}],"responses":{"200":{"description":"Where it stands and the transitions open to the caller, guards asked.","content":{"application/json":{"schema":{"type":"object","required":["states","transitions"],"properties":{"states":{"$ref":"#/components/schemas/RegionStates"},"transitions":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/AvailableTransition"},{"type":"object","required":["region"],"properties":{"region":{"type":"string"}}}]}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Entities"],"operationId":"applyEntityTransition","summary":"Move the entity by a named transition","security":[{"bearerAuth":["entities:write"]}],"parameters":[{"$ref":"#/components/parameters/Uid"},{"$ref":"#/components/parameters/IfMatch"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityTransitionRequest"}}}},"responses":{"200":{"description":"Applied.","headers":{"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Refused"},"428":{"$ref":"#/components/responses/PreconditionRequired"}}}},"/entities/{uid}/history":{"get":{"tags":["Entities"],"operationId":"getEntityHistory","summary":"Every change to the entity, oldest first","security":[{"bearerAuth":["entities:read"]}],"parameters":[{"$ref":"#/components/parameters/Uid"}],"responses":{"200":{"description":"The history.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EntityHistoryEntry"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks":{"get":{"tags":["Webhooks"],"operationId":"listWebhooks","summary":"The tenant's webhook endpoints","security":[{"bearerAuth":["webhooks:manage"]}],"responses":{"200":{"description":"Endpoints, oldest first; removed ones are not listed.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Webhooks"],"operationId":"createWebhook","summary":"Add an endpoint","description":"The response carries the endpoint's signing secret, the only time it is\nshown. The URL must be https and reach a public address — checked now\nand again at every delivery.\n","security":[{"bearerAuth":["webhooks:manage"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateRequest"}}}},"responses":{"201":{"description":"Added.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","description":"Signs each delivery (asyncapi.yaml). Shown once."}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Refused"}}}},"/webhooks/{webhookId}":{"delete":{"tags":["Webhooks"],"operationId":"removeWebhook","summary":"Remove an endpoint","description":"Deliveries still queued for it stop.","security":[{"bearerAuth":["webhooks:manage"]}],"parameters":[{"name":"webhookId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Removed."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/templates":{"get":{"tags":["Templates"],"operationId":"listTemplates","summary":"Templates switched on for the tenant","security":[{"bearerAuth":["templates:read"]}],"responses":{"200":{"description":"Template summaries.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","version"],"properties":{"id":{"$ref":"#/components/schemas/Key"},"version":{"type":"string"},"domain":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/templates/{templateId}":{"get":{"tags":["Templates"],"operationId":"getTemplate","summary":"A template's manifest","description":"The manifest as data — names, roles, event types, entities and state graphs — valid against /schemas/template-manifest.schema.json.","security":[{"bearerAuth":["templates:read"]}],"parameters":[{"name":"templateId","in":"path","required":true,"schema":{"$ref":"#/components/schemas/Key"}}],"responses":{"200":{"description":"The manifest.","content":{"application/json":{"schema":{"$ref":"template-manifest.schema.json"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"webhooks":{"eventChanged":{"post":{"summary":"An Event changed","description":"One delivery per history entry, as a CloudEvents 1.0 structured JSON\nmessage. Signed with the endpoint's secret: `x-ihgiz-signature` is the\nhex HMAC-SHA256 of `${x-ihgiz-timestamp}.${body}`. Retried with backoff;\n`id` stays the same across retries, so receivers de-duplicate on it.\nThe full catalogue is in asyncapi.yaml.\n","parameters":[{"name":"x-ihgiz-signature","in":"header","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}},{"name":"x-ihgiz-timestamp","in":"header","required":true,"schema":{"type":"string","pattern":"^[0-9]+$"}}],"requestBody":{"required":true,"content":{"application/cloudevents+json":{"schema":{"$ref":"#/components/schemas/EventChangedCloudEvent"}}}},"responses":{"2XX":{"description":"Received."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A tenant API key. Keys carry scopes, enforced on every call; the scopes\neach operation needs are listed in its security requirement:\nevents:read, events:write, events:transition, entities:read,\nentities:write, templates:read, webhooks:manage.\n"},"replyToken":{"type":"apiKey","in":"header","name":"x-ihgiz-reply-token","description":"The participant's own token, sent to them with the invitation. Good only for that participant on that Event."}},"headers":{"ETag":{"description":"The Event's sequence as a strong entity tag, e.g. \"7\".","schema":{"type":"string","pattern":"^\"[0-9]+\"$"}}},"parameters":{"Uid":{"name":"uid","in":"path","required":true,"description":"The Event's JSCalendar uid.","schema":{"type":"string","minLength":1,"maxLength":255}},"Cursor":{"name":"cursor","in":"query","description":"Opaque cursor from the previous page's `next`.","schema":{"type":"string"}},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","description":"A unique value per intended change; a retry with the same key and body returns the first result instead of acting twice.","schema":{"type":"string","minLength":8,"maxLength":255}},"IfMatch":{"name":"If-Match","in":"header","required":true,"description":"The ETag (sequence) the caller last read. Stale values are refused with 412, so two people cannot overwrite each other's change.","schema":{"type":"string","pattern":"^\"[0-9]+\"$"}}},"responses":{"BadRequest":{"description":"The request is malformed or fails its schema.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unauthorized":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Forbidden":{"description":"The credential lacks the scope, or the actor may not fire this transition.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"No such Event or template for this tenant.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Conflict":{"description":"The same Idempotency-Key is in flight, or reused with a different body.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"PreconditionFailed":{"description":"If-Match is not the Event's current sequence; read it again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"PreconditionRequired":{"description":"If-Match is missing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Refused":{"description":"The transition is not available from the current state, a guard refused it, or it would change something it may not.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"TooManyRequests":{"description":"Rate limited.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":0}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"schemas":{"Id":{"description":"A JSCalendar Id (RFC 8984 §1.4.1).","type":"string","pattern":"^[A-Za-z0-9_-]{1,255}$"},"Key":{"type":"string","pattern":"^[a-z][a-z0-9]*(-[a-z0-9]+)*$","maxLength":64},"Phase":{"description":"The engine's fixed vocabulary; every state of every graph maps to one.\ndraft — not yet offered; pending — requested or awaiting something\n(approval, payment, an answer); confirmed — agreed; active — under way;\ncompleted — happened; cancelled — called off; expired — lapsed unanswered\nor unpaid; failed — did not take place for another reason (a no-show).\n","type":"string","enum":["draft","pending","confirmed","active","completed","cancelled","expired","failed"]},"UTCDateTime":{"description":"RFC 8984 §1.4.4 UTCDateTime, e.g. 2026-10-23T09:30:00Z.","type":"string","format":"date-time","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$"},"LocalDateTime":{"description":"RFC 8984 §1.4.5 LocalDateTime, interpreted in the Event's timeZone.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$"},"Duration":{"description":"RFC 8984 §1.4.6 Duration (ISO 8601 subset), e.g. PT45M.","type":"string","pattern":"^P(?!$)(\\d+W)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+S)?)?$"},"TimeZoneId":{"description":"An IANA Time Zone Database name, e.g. Asia/Dubai.","type":"string","pattern":"^[A-Za-z_]+(/[A-Za-z0-9_+-]+)*$"},"StateRef":{"type":"object","required":["state","phase"],"properties":{"state":{"$ref":"#/components/schemas/Key"},"phase":{"$ref":"#/components/schemas/Phase"}}},"TemplateRef":{"type":"object","required":["id","version"],"properties":{"id":{"$ref":"#/components/schemas/Key"},"version":{"type":"string"}}},"Event":{"description":"A JSCalendar Event (RFC 8984 §5.1) with the engine's own properties\nunder the `ihgiz.net:` prefix. The Event is what the template calls a\nreservation, an appointment or a registration.\n","type":"object","required":["@type","uid","updated","sequence","title","start","duration","status","ihgiz.net:tenant","ihgiz.net:template","ihgiz.net:eventType","ihgiz.net:state","ihgiz.net:phase"],"properties":{"@type":{"const":"Event"},"uid":{"type":"string","minLength":1,"maxLength":255},"created":{"$ref":"#/components/schemas/UTCDateTime"},"updated":{"$ref":"#/components/schemas/UTCDateTime"},"sequence":{"description":"Counts every change participants are told about (iCalendar SEQUENCE).","type":"integer","minimum":0},"title":{"type":"string"},"description":{"type":"string"},"descriptionContentType":{"type":"string","default":"text/plain"},"locale":{"description":"BCP 47 language tag.","type":"string"},"start":{"$ref":"#/components/schemas/LocalDateTime"},"timeZone":{"oneOf":[{"$ref":"#/components/schemas/TimeZoneId"},{"type":"null"}]},"duration":{"$ref":"#/components/schemas/Duration"},"showWithoutTime":{"type":"boolean","default":false},"recurrenceRules":{"type":"array","items":{"$ref":"#/components/schemas/RecurrenceRule"}},"status":{"description":"JSCalendar status (iCalendar STATUS).","type":"string","enum":["tentative","confirmed","cancelled"]},"freeBusyStatus":{"type":"string","enum":["free","busy"],"default":"busy"},"privacy":{"type":"string","enum":["public","private","secret"],"default":"public"},"replyTo":{"description":"Where participants' replies go, by method, e.g. {\"imip\":\"mailto:x7f2a@reply.ihgiz.net\"}.","type":"object","additionalProperties":{"type":"string","format":"uri"}},"locations":{"type":"object","propertyNames":{"$ref":"#/components/schemas/Id"},"additionalProperties":{"$ref":"#/components/schemas/Location"}},"virtualLocations":{"type":"object","propertyNames":{"$ref":"#/components/schemas/Id"},"additionalProperties":{"$ref":"#/components/schemas/VirtualLocation"}},"participants":{"type":"object","propertyNames":{"$ref":"#/components/schemas/Id"},"additionalProperties":{"$ref":"#/components/schemas/Participant"}},"ihgiz.net:tenant":{"description":"The tenant that owns the Event.","type":"string"},"ihgiz.net:template":{"$ref":"#/components/schemas/TemplateRef"},"ihgiz.net:eventType":{"description":"One of the template's event types.","$ref":"#/components/schemas/Key"},"ihgiz.net:state":{"description":"The current state in the type's event graph.","$ref":"#/components/schemas/Key"},"ihgiz.net:phase":{"$ref":"#/components/schemas/Phase"},"ihgiz.net:regions":{"description":"For graphs with parallel regions, the state of each region (e.g. fulfilment and payment).","type":"object","additionalProperties":{"$ref":"#/components/schemas/StateRef"}},"ihgiz.net:capacity":{"description":"Places offered","when the Event has more than one participant slot.":null,"type":"integer","minimum":1},"ihgiz.net:fields":{"description":"Template fields, valid against the event type's schema in the manifest.","type":"object"}}},"Participant":{"description":"A JSCalendar Participant (RFC 8984 §4.4.6).","type":"object","required":["@type","roles"],"properties":{"@type":{"const":"Participant"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"description":{"type":"string"},"sendTo":{"description":"How to send this participant scheduling messages, by method, e.g. {\"imip\":\"mailto:guest@example.com\"}.","type":"object","additionalProperties":{"type":"string","format":"uri"}},"kind":{"type":"string","enum":["individual","group","location","resource"]},"roles":{"type":"object","minProperties":1,"propertyNames":{"enum":["owner","attendee","optional","informational","chair","contact"]},"additionalProperties":{"const":true}},"locationId":{"$ref":"#/components/schemas/Id"},"participationStatus":{"type":"string","enum":["needs-action","accepted","declined","tentative","delegated"],"default":"needs-action"},"participationComment":{"type":"string"},"expectReply":{"type":"boolean","default":false},"scheduleSequence":{"type":"integer","minimum":0},"scheduleUpdated":{"$ref":"#/components/schemas/UTCDateTime"},"ihgiz.net:party":{"description":"The platform's one record of this person; absent for a place or a resource.","type":"string"},"ihgiz.net:role":{"description":"What the template calls this participant: one of its role keys (guest, member, client, driver, car).","$ref":"#/components/schemas/Key"},"ihgiz.net:state":{"description":"The participant's state in the type's participation graph","if it has one.":null,"$ref":"#/components/schemas/Key"}}},"Location":{"description":"A JSCalendar Location (RFC 8984 §4.2.5).","type":"object","required":["@type"],"properties":{"@type":{"const":"Location"},"name":{"type":"string"},"description":{"type":"string"},"locationTypes":{"type":"object","additionalProperties":{"const":true}},"timeZone":{"$ref":"#/components/schemas/TimeZoneId"},"coordinates":{"description":"A geo: URI (RFC 5870).","type":"string","pattern":"^geo:"},"links":{"type":"object","additionalProperties":{"type":"object","required":["href"],"properties":{"href":{"type":"string","format":"uri"}}}}}},"VirtualLocation":{"description":"A JSCalendar VirtualLocation (RFC 8984 §4.2.6) — a meeting link.","type":"object","required":["@type","uri"],"properties":{"@type":{"const":"VirtualLocation"},"name":{"type":"string"},"uri":{"type":"string","format":"uri"},"features":{"type":"object","additionalProperties":{"const":true}}}},"RecurrenceRule":{"description":"A JSCalendar RecurrenceRule (RFC 8984 §4.3.3); the subset the engine accepts.","type":"object","required":["@type","frequency"],"properties":{"@type":{"const":"RecurrenceRule"},"frequency":{"type":"string","enum":["yearly","monthly","weekly","daily","hourly"]},"interval":{"type":"integer","minimum":1,"default":1},"byDay":{"type":"array","items":{"type":"object","required":["@type","day"],"properties":{"@type":{"const":"NDay"},"day":{"type":"string","enum":["mo","tu","we","th","fr","sa","su"]},"nthOfPeriod":{"type":"integer"}}}},"count":{"type":"integer","minimum":1},"until":{"$ref":"#/components/schemas/LocalDateTime"}}},"PatchObject":{"description":"A JSCalendar PatchObject (RFC 8984 §1.4.9): keys are JSON pointers into\nthe Event without the leading slash, values replace what is there and\nnull removes it. A transition may change only the properties its graph\nlists in `changes`.\n","type":"object","propertyNames":{"pattern":"^[A-Za-z@][^\\s]*$"}},"Actor":{"description":"Who acted. `onBehalfOf` records a person an organiser or operator acted for.","type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["participant","staff","organizer","operator","system","integration"]},"id":{"type":"string"},"onBehalfOf":{"type":"string"}}},"CreateEventRequest":{"type":"object","required":["template","eventType","event"],"properties":{"template":{"$ref":"#/components/schemas/Key"},"eventType":{"$ref":"#/components/schemas/Key"},"event":{"description":"The JSCalendar part of the new Event. uid, sequence, created, updated, status and the ihgiz.net properties are the engine's to set.","type":"object","required":["title","start","duration"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"locale":{"description":"BCP 47 language tag: the language its messages are written in.","type":"string"},"start":{"$ref":"#/components/schemas/LocalDateTime"},"timeZone":{"$ref":"#/components/schemas/TimeZoneId"},"duration":{"$ref":"#/components/schemas/Duration"},"recurrenceRules":{"type":"array","items":{"$ref":"#/components/schemas/RecurrenceRule"}},"locations":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Location"}},"virtualLocations":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/VirtualLocation"}},"participants":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Participant"}},"ihgiz.net:capacity":{"type":"integer","minimum":1},"ihgiz.net:fields":{"type":"object"}}},"then":{"description":"A transition to run from the initial state in the same call, e.g. confirm a slot booked from open availability.","$ref":"#/components/schemas/Key"},"reason":{"type":"string","maxLength":2000}}},"TransitionRequest":{"type":"object","required":["transition"],"properties":{"transition":{"description":"A transition name from the Event's graph.","$ref":"#/components/schemas/Key"},"region":{"description":"For graphs with parallel regions","which region the transition belongs to.":null,"$ref":"#/components/schemas/Key"},"participant":{"description":"For participation graphs","whose part changes.":null,"$ref":"#/components/schemas/Id"},"patch":{"$ref":"#/components/schemas/PatchObject"},"reason":{"type":"string","maxLength":2000},"onBehalfOf":{"description":"The person the caller acts for; required when an organiser records a participant's answer.","type":"string"},"evidence":{"description":"References to what supports an answer recorded on someone's behalf.","type":"array","items":{"type":"string","format":"uri"}}}},"ReplyRequest":{"type":"object","required":["participationStatus"],"properties":{"participationStatus":{"type":"string","enum":["accepted","declined","tentative"]},"comment":{"type":"string","maxLength":2000},"counter":{"description":"A proposed new time (iTIP COUNTER); the Event stays as it is until the organiser accepts it.","type":"object","required":["start"],"properties":{"start":{"$ref":"#/components/schemas/LocalDateTime"},"timeZone":{"$ref":"#/components/schemas/TimeZoneId"},"duration":{"$ref":"#/components/schemas/Duration"}}}}},"AvailableTransition":{"type":"object","required":["name","to"],"properties":{"name":{"$ref":"#/components/schemas/Key"},"region":{"$ref":"#/components/schemas/Key"},"to":{"$ref":"#/components/schemas/StateRef"},"changes":{"type":"array","items":{"type":"string"}},"labels":{"type":"object","additionalProperties":{"type":"string"}}}},"HistoryEntry":{"description":"One append-only record per transition. System cascades record actor kind system and what triggered them.","type":"object","required":["id","at","actor","transition","subject","from","to","sequence"],"properties":{"id":{"type":"string"},"at":{"$ref":"#/components/schemas/UTCDateTime"},"actor":{"$ref":"#/components/schemas/Actor"},"transition":{"$ref":"#/components/schemas/Key"},"region":{"$ref":"#/components/schemas/Key"},"subject":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["event","participation"]},"participant":{"$ref":"#/components/schemas/Id"}}},"from":{"oneOf":[{"$ref":"#/components/schemas/StateRef"},{"type":"null"}]},"to":{"$ref":"#/components/schemas/StateRef"},"sequence":{"type":"integer","minimum":0},"patch":{"$ref":"#/components/schemas/PatchObject"},"reason":{"type":"string"},"itip":{"type":"string","enum":["PUBLISH","REQUEST","REPLY","ADD","CANCEL","REFRESH","COUNTER","DECLINECOUNTER"]},"triggeredBy":{"description":"For system cascades: what set them off (e.g. auto-confirm-on-booking, hold-expiry).","type":"string"},"evidence":{"type":"array","items":{"type":"string","format":"uri"}}}},"MessageIntent":{"description":"\"Tell these participants this happened.\" The router picks the channel and the template's wording.","type":"object","required":["intent","to"],"properties":{"intent":{"type":"string","pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"},"to":{"type":"array","items":{"$ref":"#/components/schemas/Id"}}}},"TransitionResult":{"type":"object","required":["event","history","messages"],"properties":{"event":{"$ref":"#/components/schemas/Event"},"history":{"$ref":"#/components/schemas/HistoryEntry"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MessageIntent"}}}},"EventPage":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"next":{"$ref":"#/components/schemas/NextCursor"}}},"NextCursor":{"description":"Cursor for the next page; absent on the last page.","type":"string"},"RegionStates":{"description":"Where an entity stands in each region of its graph (\"main\" for a graph without regions).","type":"object","additionalProperties":{"$ref":"#/components/schemas/Key"}},"Entity":{"description":"A thing that is not an Event but has a lifecycle the engine runs — a membership, a deal, an order.","type":"object","required":["@type","uid","created","updated","sequence","ihgiz.net:tenant","ihgiz.net:template","ihgiz.net:entityType","ihgiz.net:states","ihgiz.net:phases"],"properties":{"@type":{"const":"Entity"},"uid":{"type":"string"},"created":{"$ref":"#/components/schemas/UTCDateTime"},"updated":{"$ref":"#/components/schemas/UTCDateTime"},"sequence":{"type":"integer","minimum":0},"title":{"type":"string"},"subject":{"description":"The product record it stands for.","type":"string"},"ihgiz.net:tenant":{"type":"string"},"ihgiz.net:template":{"$ref":"#/components/schemas/TemplateRef"},"ihgiz.net:entityType":{"$ref":"#/components/schemas/Key"},"ihgiz.net:states":{"$ref":"#/components/schemas/RegionStates"},"ihgiz.net:phases":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Phase"}},"ihgiz.net:fields":{"type":"object"}}},"CreateEntityRequest":{"type":"object","required":["template","entityType"],"properties":{"template":{"$ref":"#/components/schemas/Key"},"entityType":{"$ref":"#/components/schemas/Key"},"subject":{"type":"string"},"title":{"type":"string"},"fields":{"type":"object"},"reason":{"type":"string","maxLength":2000}}},"EntityTransitionRequest":{"type":"object","required":["transition"],"properties":{"transition":{"$ref":"#/components/schemas/Key"},"region":{"description":"Which region","when the name is in more than one.":null,"type":"string"},"patch":{"$ref":"#/components/schemas/PatchObject"},"reason":{"type":"string","maxLength":2000}}},"EntityHistoryEntry":{"type":"object","required":["id","at","actor","transition","region","from","to","sequence"],"properties":{"id":{"type":"string"},"at":{"$ref":"#/components/schemas/UTCDateTime"},"actor":{"$ref":"#/components/schemas/Actor"},"transition":{"type":"string"},"region":{"type":["string","null"]},"from":{"oneOf":[{"$ref":"#/components/schemas/RegionStates"},{"type":"null"}]},"to":{"$ref":"#/components/schemas/RegionStates"},"sequence":{"type":"integer","minimum":0},"patch":{"$ref":"#/components/schemas/PatchObject"},"reason":{"type":"string"},"triggeredBy":{"type":"string"}}},"EntityResult":{"type":"object","required":["entity","history","messages"],"properties":{"entity":{"$ref":"#/components/schemas/Entity"},"history":{"$ref":"#/components/schemas/EntityHistoryEntry"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MessageIntent"}}}},"Webhook":{"type":"object","required":["id","url","types","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"types":{"description":"The change types it receives; empty is every one.","type":"array","items":{"$ref":"#/components/schemas/WebhookType"}},"createdAt":{"$ref":"#/components/schemas/UTCDateTime"},"lastAttemptAt":{"oneOf":[{"$ref":"#/components/schemas/UTCDateTime"},{"type":"null"}]},"lastStatus":{"description":"The HTTP status of the last attempt","or null if it did not get one.":null,"type":["integer","null"]}}},"WebhookType":{"type":"string","enum":["net.ihgiz.event.created","net.ihgiz.event.transitioned","net.ihgiz.event.participant.replied","net.ihgiz.event.cancelled","net.ihgiz.entity.created","net.ihgiz.entity.changed"]},"WebhookCreateRequest":{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","format":"uri","maxLength":2048},"types":{"type":"array","items":{"$ref":"#/components/schemas/WebhookType"},"uniqueItems":true}}},"Problem":{"description":"RFC 9457 Problem Details, with the engine's code and, for a refusal, the guard that refused.","type":"object","required":["type","title","status"],"properties":{"type":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"status":{"type":"integer","minimum":400,"maximum":599},"detail":{"type":"string"},"instance":{"type":"string","format":"uri-reference"},"code":{"type":"string"},"refusedBy":{"description":"The guard capability that refused a transition.","type":"string"}}},"EventChangedCloudEvent":{"description":"A CloudEvents 1.0 structured-mode event whose data is one change's result — an Event's TransitionResult, or an entity's EntityResult.","type":"object","required":["specversion","id","source","type","time","subject","data"],"properties":{"specversion":{"const":"1.0"},"id":{"description":"The history entry id; identical across retries.","type":"string"},"source":{"description":"The tenant's engine endpoint, e.g. https://api.ihgiz.net/engine/v0/tenants/t_7f2a","type":"string","format":"uri-reference"},"type":{"type":"string","enum":["net.ihgiz.event.created","net.ihgiz.event.transitioned","net.ihgiz.event.participant.replied","net.ihgiz.event.cancelled","net.ihgiz.entity.created","net.ihgiz.entity.changed"]},"subject":{"description":"The Event's or the entity's uid.","type":"string"},"time":{"$ref":"#/components/schemas/UTCDateTime"},"datacontenttype":{"const":"application/json"},"dataschema":{"type":"string","format":"uri"},"data":{"oneOf":[{"$ref":"#/components/schemas/TransitionResult"},{"$ref":"#/components/schemas/EntityResult"}]}}}}}}