Two APIs.One is the graph.

One holds the guest graph and answers questions about it. One fills it.

The engine is the API you build against: you send what a system knows about a person, and it answers with the person. A connector is a client of that API with a small surface of its own, the one an operator uses to watch it and to start a run. Every call below is generated from the service's own OpenAPI document at the commit this page pins, so a path here is a path that exists. Those rows are in the one language the documents are written in; the rest of this page is bilingual.

There is no instance to call. GuestGraph is Apache-2.0 and you run it yourself; the paths below are relative to your own deployment, and a running instance serves its complete document at /api-docs with no key.

One fills it

connector-apaleo

A connector is a client like any other: it reads one source system and submits what it finds. Its own surface is what an operator calls, not what an integration calls, and it answers to a bearer token rather than to a tenant's key.

  • POST/apaleo/events/{secret}Receive one Apaleo delivery, or its reachability check

    The secret names the connection: the delivery is stored under the connection whose webhook secret hash matches and answered at once; processing happens on the worker. A second delivery of the same id is acknowledged and ignored. An empty body is Apaleo's reachability check and is answered with 200. A secret that matches no connection is 404, indistinguishable from an unknown path.

    Request

    ApaleoEvent
    • id string required
    • topic string required
    • type string required
    • accountId string required
    • propertyId string required
    • timestamp string required
    • clientId string
    • subjectId string
    • data object

    Responses

    • 200 Reachability check answered
    • 202 Delivery stored
    • 404 Unknown path
  • GET/statusIs this connector fine, connection by connection

    Responses

    • 200 The status document
      Status
      • connections ConnectionStatus[] required
    • 401 Missing or wrong token · unauthorized
  • POST/connections/{connectionId}/sync/fullStart a full sync of one connection's properties and bookings

    Responses

    • 202 Started
      RunStarted
      • runId string required
    • 401 Missing or wrong token · unauthorized
    • 404 No such connection · not-found
    • 409 A full sync is already running on this connection · run-in-progress
  • POST/connections/{connectionId}/sync/reconcileStart a reconciliation from one connection's sync points

    Responses

    • 202 Started
      RunStarted
      • runId string required
    • 401 Missing or wrong token · unauthorized
    • 404 No such connection · not-found
    • 409 A run is already in progress on this connection · run-in-progress
  • POST/connections/{connectionId}/refreshRe-read one connection's held guest ids and apply the integrator rule

    Responses

    • 202 Started
      RunStarted
      • runId string required
    • 401 Missing or wrong token · unauthorized
    • 404 No such connection · not-found
  • GET/connections/{connectionId}/runs/{runId}One run's progress and outcome

    Responses

    • 200 The run
      Run
      • id string required
      • kind string required
      • startedAt string required
      • finishedAt string
      • outcome string
      • reservationsSeen integer required
      • versionsSubmitted integer required
      • recordsSubmitted integer required
      • duplicates integer required
      • flaggedForReview integer required
      • errors integer required
      • lastError string
    • 401 Missing or wrong token · unauthorized
    • 404 No such connection or run · not-found
  • PUT/connections/{connectionId}/subscriptionCreate this connection's webhook subscription again

    The same act the connector performs for every connection when it starts: creates the subscription, or replaces one whose endpoint, events or properties differ. Idempotent, and the way back from a removal without editing configuration or restarting.

    Responses

    • 200 A subscription names this connection
      SubscriptionState
      • state string required
      • id string
      • eventTypes string[] required
      • checkedAt string
    • 401 Missing or wrong token · unauthorized
    • 404 No such connection · not-found
    • 502 Apaleo refused or could not be reached; nothing was created · apaleo-unreachable
  • DELETE/connections/{connectionId}/subscriptionRemove this connection's webhook subscription in Apaleo

    Deletes the subscription whose endpoint is this connection's, and nothing else in the Apaleo account, so two connections sharing an account are independent. A statement of the end state rather than of an act: asking twice, or asking when none exists, succeeds and says so in `removed`. The absence lasts until the connector is restarted or the subscription is restored; a restart subscribes every configured connection again.

    Responses

    • 200 No subscription now names this connection
      SubscriptionRemoved
      • 401 Missing or wrong token · unauthorized
      • 404 No such connection · not-found
      • 502 Apaleo refused or could not be reached; nothing was removed · apaleo-unreachable

    What arrives

    engine

    A system registers itself, then sends what it knows about a person. Resolution happens in the same request, so the answer names the guest the record turned out to belong to.

    • POST/source-systemsRegister a source system for the caller's tenant

      Request

      • code string required
      • name string required

      Responses

      • 201 Registered
        SourceSystem
        • id string required
        • code string required
        • name string required
        • createdAt string required
      • 409 Code already registered in this tenant · conflict
    • POST/recordsIngest guest record(s); resolves synchronously

      Accepts a single record or a batch (max 1000). Each record is stored immutably and resolved independently; one outcome is returned per record. Malformed-but-parseable records are stored flagged needs_review — never dropped. Only an unparseable request body is rejected as a whole.

      Request

      IngestRecord one or a list of them
      • sourceSystem string required
      • externalKey string required
      • recordTimestamp string
      • payload object required

      Responses

    What came out

    engine

    One person, assembled from every record that turned out to be them, with the evidence for it. A guest id you store keeps answering after a merge.

    • GET/guests/{guestId}Golden profile + identifiers, or where a retired id went

      Responses

    • GET/guests/{guestId}/recordsThe guest's source records, as originally received

      Responses

      • 200 Source records
        • records SourceRecord[] required
      • 410 The guest id was retired by a merge or a split. The problem details name the current guest or guests; read GET /guests/{guestId} on the retired id for the full resolution. · guest-retired
    • GET/guests/{guestId}/explainWhy these records are one guest — the full merge-event chain

      Responses

      • 200 Decision chain, oldest first (includes events of absorbed guests)
        • guestId string required
        • events MergeEvent[] required
      • 410 The guest id was retired by a merge or a split. The problem details name the current guest or guests; read GET /guests/{guestId} on the retired id for the full resolution. · guest-retired
    • GET/guests/{guestId}/timelineThe guest's current business-object associations

      Associations the guest currently holds, ordered by business start and falling back to the observation timestamp when the submitter supplied no business dates. An association is current while the object's newest version roster places this guest in that role. With includePast=true, associations the guest no longer holds are also returned with status ENDED, naming the successor when exactly one guest holds that role now.

      Responses

      • 200 Associations page
        • items Association[] required
        • nextCursor string
      • 410 The guest id was retired by a merge or a split. The problem details name the current guest or guests; read GET /guests/{guestId} on the retired id for the full resolution. · guest-retired
    • GET/guestsLook up guests by identifier value

      The value is normalized before matching. A miss returns an empty list. Without an explicit type, every type whose normalization accepts the value is tried — except ID_DOCUMENT, which is only matched when type=ID_DOCUMENT is given (pass the value as TYPE:NUMBER; it is hashed before matching).

      Responses

      • 200 Matching guests (usually 0 or 1; >1 after a rejected shared-identifier review)
        • guests Guest[] required
    • GET/source-objects/{sourceSystem}/{objectType}/{objectId}A business object's current roster and observation history

      The object as the graph knows it: the newest version's roster — who is on it now, in which role, resolved to which guest — and every observation of the object in version order, including superseded ones and those belonging to guests no longer on it. The history is a property of the object, not of any one guest, because a reassignment spans two of them.

      Responses

      • 200 The object, its current roster, and its observations
        • sourceSystem string required
        • objectType string required
        • objectId string required
        • currentVersion string required
        • businessStart string
        • businessEnd string
        • roster RosterEntry[] required
        • observations ObjectObservation[] required

    When it is wrong

    engine

    Every merge the engine makes can be taken back, and a steward's decision outlives the next import rather than being overwritten by it.

    • POST/guests/{guestId}/unmergeDetach wrongly linked record(s) and replay resolution

      Removes the resolution links of the given source records, records an UNMERGE event (with an exclusion so identical re-ingest does not silently recreate the wrong merge), re-resolves the detached records, and recomputes the remaining guest's profile and identifiers. Source records are never altered.

      Request

      • sourceRecordIds string[] required

      Responses

      • 200 Outcome of the replay
        • unmergeEventId string required
        • remainingGuestId string
        • detachedRecords object[] required
      • 400 E.g. record not linked to this guest, or guest has a single record · invalid-unmerge
      • 410 The guest id was retired by a merge or a split. The problem details name the current guest or guests; read GET /guests/{guestId} on the retired id for the full resolution. · guest-retired
    • GET/match-reviewsPending uncertain matches

      Responses

      • 200 Review queue entries, oldest first
        • reviews MatchReview[] required
        • total integer required
        • nextCursor string
    • POST/match-reviews/{reviewId}Confirm or reject a queued match (exactly once)

      Request

      • decision string required

      Responses

      • 200 Decision applied; on CONFIRM the merge executed and was recorded
        MatchReview
        • id string required
        • status string required
        • sourceRecordId string required
        • candidateGuestId string required
        • identifier object
        • reason string required
        • matcherName string
        • confidence number
        • createdAt string required
        • decidedAt string
        • decisionEventId string
      • 409 Review already decided; the first decision stands · review-already-decided
    • GET/negative-rulesDo-not-merge rules (written by unmerge and review rejections)

      Responses

      • 200 Rules, newest first
        • rules NegativeRule[] required
        • total integer required
        • nextCursor string
    • DELETE/negative-rules/{ruleId}Lift a do-not-merge rule (steward act) — the pair may merge on next evidence

      Responses

    What the tenant decides

    engine

    Thresholds and identifier trust are per tenant, because one brand's shared front-desk number is another brand's real one.

    • GET/config/matchingThe tenant's matching thresholds

      Responses

      • 200 Current configuration
        MatchingConfig
        • autoMergeThreshold number required
        • reviewFloor number required
        • reviewThreshold integer required
    • PUT/config/matchingUpdate the tenant's matching thresholds (transactional — all or nothing)

      Request

      MatchingConfig
      • autoMergeThreshold number required
      • reviewFloor number required
      • reviewThreshold integer required

      Responses

      • 200 Applied; effective for subsequent resolutions, no restart
        MatchingConfig
        • autoMergeThreshold number required
        • reviewFloor number required
        • reviewThreshold integer required
      • 400 Invalid combination (e.g. reviewFloor > autoMergeThreshold, values outside 0..1) · invalid-request
    • GET/config/identifier-rulesIdentifier quality rules — tenant-added plus built-in defaults

      Responses

      • 200 Rules; built-ins carry builtin=true and cannot be deleted
        • rules IdentifierRule[] required
    • POST/config/identifier-rulesAdd a tenant identifier quality rule

      Request

      • identifierType string required
      • matchKind string required
      • value string required
      • rule string required
      • note string

      Responses

      • 201 Created; effective for subsequent resolutions
        IdentifierRule
        • id string required
        • identifierType string required
        • matchKind string required
        • value string required
        • rule string required
        • note string
        • builtin boolean required
        • createdAt string
      • 409 Equivalent rule already exists · conflict
    • DELETE/config/identifier-rules/{ruleId}Delete a tenant rule (built-ins are not deletable)

      Responses

      • 204 Deleted
      • 404 Unknown rule id — including built-ins, which carry no id and therefore cannot be addressed for deletion · not-found

    When a call is refused

    Both services answer a refusal the same way: an RFC 9457 problem detail whose type is a section of the problems page. Follow it and you land on what that type means and what to do about it, rather than on a status code you have to interpret. Two refusals belong to every call and so appear on none of the rows: a malformed actor claim, which the key filter raises before an operation runs, and a body over the service's size cap.

    Generated from guestgraph/engine@c8b205c and guestgraph/connector-apaleo@65d24cc, the documents those commits hold.