# Harbor marketplace integration Building your first company integration? Start with the [step-by-step company walkthrough](/developers/api/#first-integration) for setup, downloads, route wiring and checks. This guide explains the complete Harbor reference implementation. Harbor separates public shopping, optional Mementom connection, private-auction access and bid approval. Its public marketplace has no website sign-in and creates no customer account. ## Integration modules Harbor uses several modules with clear responsibilities. A partner reuses the company SDK and window components, then supplies its own product and action rules. | Layer | Source entry point | Contract | | --- | --- | --- | | Company server SDK | `packages/sdk/src/index.mjs`: `Mementom.connect`, `Mementom.setup`, `MementomVendorClient`, `MementomInsightsClient`, `createCompanySessionCookie` | Registration/configuration, opaque company sessions, challenge requests, verified one-use decisions and insight APIs. Import through `@mementom/sdk` and its server subpaths. | | Browser SDK | `packages/sdk/src/browser.mjs`: `createAuthorizationWindow`, `returnFromAuthorization` | Open a Mementom-owned authorization window and receive its scoped return. | | Shared reference adapter | `apps/service/src/aster-reference.mjs`: `createReferencePartner` | Shared Harbor/Aster server orchestration, registered origin/return binding, pending requests, session continuation and logout. This is reference application code, separate from the SDK package. | | Harbor adapter | `apps/service/src/harbor-reference.mjs`: `HARBOR_PARTNER`, `createHarborReferencePartner` | Harbor branding, registered challenges, capability checks and commerce composition. | | Harbor commerce | `apps/service/src/harbor-commerce.mjs`: `createHarborCommerceApi`, `createHarborCommerce`; `harbor-catalog.mjs` | Catalog, saved items, transactional bids/orders, stock, idempotency and server-observed shopping activity. | | Harbor insights | `harbor-commerce-insights.mjs`: `harborCommerceInsightTemplates`, `deriveHarborCommerceInsights`; `harbor-insight-library.mjs`: `harborInsightLibrary`; `reference-insights.mjs`: `prepareReferenceInsights`, `writeReferenceInsights` | Registered data contracts, catalogs, changed-value contributions and audience discovery. Files are in `apps/service/src/`. | | Browser presentation | `apps/web/public/shared/partner-session-badge.js`: `createPartnerSessionBadge`; `partner-activity-window.js`: `createPartnerActivityWindow` | Reusable QR connection and Mementom Activity window; Harbor storefront logic stays in `redesign-preview/harbor-partner.js`. | The active storefront is `/redesign-preview/harbor.html`, which loads `harbor-partner.js`. Its hash routes are `#marketplace`, `#private-auctions`, `#product/`, `#compare` and `#activity`. The older `harbor-demo.js` is not loaded by that storefront. `/harbor/return.html` is the current callback page; `/harbor/api/auctions` retains the protected compatibility HTML response, while the active private area reads `/harbor/api/private-catalog`. ## Rights match the requested action | Experience | Access requirement | | --- | --- | | Browse 24 public products, search, filter and compare | No connection required. | | Save products, place reviewed buy-now orders and read My activity | Current Mementom connection; no Harbor account required. | | Connect with Mementom | One eligible personal key in the service-issued Connect ceremony; no Harbor invitation or pre-granted `login` right. | | View the five original private auction lots | Connection and `view-private-auctions`, with two eligible keys. | | Submit a public marketplace bid | Connection and `place-marketplace-bids`, with three eligible keys. | | Submit a private-lot bid | Connection, `view-private-auctions` and `place-marketplace-bids`, with three eligible keys. | The protected-action and legacy-login key counts listed here are source defaults. Existing registered tags and published challenge policies govern those actions; registration preserves their policies. The dedicated `connect` action is explicitly reconciled to one personal key. The default action is `connect`, with direct authorization route `/harbor/authorize?challengeId=connect`. The active badge opens the prepared signed request described below. The older `login` action and `/harbor/authorize?challengeId=login` remain separate compatibility paths with their original two-key source default. The adapter resolves the requested action literally; `login` is not an alias for the one-key `connect`. Registration selects `connect` as the application login action. Neither creates a Harbor customer account or gates public browsing. Connect links an existing personal keyring to the current browser visit. Its registered Harbor definition uses one key and no ceremony animation. This policy applies only to that exact action. Shared and delegated key restrictions, owner requirements and other companies' challenge policies remain unchanged. The earlier `place-bid` action remains bound to its original fixed lot and amount. Its decision cannot grant general marketplace bidding. Existing permissions, limits and revocations remain effective. ## Connect the open browser visit by QR Harbor's persistent Mementom badge shows the requesting company and site domain. After the Harbor visit exists on page load, the badge prepares a pending signed request. Scanning the QR and completing the secure steps connects that existing browser visit. Preparing a request is not a completed connection. The implementation calls `POST /harbor/api/authorize` with `{challengeId: "connect"}`, then uses the existing `/api/v1/remote-ceremonies/create` operation. The QR contains the returned opaque relay URL. The browser watches the relay status, submits the completion to `/harbor/api/return`, and uses relay `advance` and `finish` as needed. These are the established signed ceremony and relay APIs; this does not introduce a generic Connect API. The badge labels are **Scan to connect**, **Finish on phone** and **Session connected**. Harbor confirms the current connection before showing the final state. The relay is checked every 1.2 seconds and the connection every 15 seconds while the page is visible. An expired badge reads **Share session expired.** and **Scan or Click to renew.** on two lines, with the Mementom icon occupying the former QR space. Clicking the expired badge requests a fresh signed code and opens that code directly, without an intermediate options menu. This renewal behavior belongs to the shared partner badge, not the Harbor storefront. **Continue on this device** reuses the prepared request and cancels QR watching. | Badge state | Display | Badge click | | --- | --- | --- | | Code available | Scannable code and requesting company/domain | Open the larger code; reuse the same signed request. | | Code expired | Two-line renewal message and Mementom icon | Request a fresh code and open it directly. | | Preparing | Progress message and Mementom icon | Keep the same pending request; do not start another. | | Connected | Connection status and current session reference | Open Activity; preserve the connected visit. | Renewal does not connect a keyring by itself. Only the server-confirmed completion changes the badge to connected; an unsuccessful retry leaves a visible error and a retry action inside the code dialog. The disconnected badge shows its QR connection menu. Once connected, clicking the badge opens the Mementom Activity popup directly. **Disconnect** stays at the bottom of that window. Connection does not grant insight-sharing, private-auction or bidding permissions. All credential steps remain inside the separately served Mementom authorization surface. ## Enforce access on the server The storefront reads `/harbor/api/access` for current capability booleans and challenge identifiers: `connected`, `canViewPrivateAuctions`, `canBid`, `connectionChallengeId`, `privateAuctionChallengeId` and `bidChallengeId`. The server also checks every protected request: - `/harbor/api/catalog` returns public products only. - `/harbor/api/private-catalog` requires connection and private-auction approval. - `POST /harbor/api/bids` requires the distinct bidding approval, plus private access when the selected lot is private. - `POST /harbor/api/orders` requires connection and accepts a reviewed order. The resulting order status reflects its persisted state. The public storefront must not contain protected lot data. A connected visit does not automatically gain private-auction or bidding permissions. Cancelled, expired or rejected requests grant nothing. A successful bid records a bid; an order records an order. Neither implies a payment or auction win. Private saved items and private activity records stay hidden when private permission is missing, without deleting the records. Use the [server SDK setup](sdk-setup.md) and [browser window component](browser-window.md) to open a service-issued Mementom ceremony. Photos, captions, keyring files and credential proof remain inside the Mementom surface. The partner verifies and consumes the opaque result through its server integration. ## Preserve pending actions and verify returns The server SDK owns decision verification and one-use consumption. Company credentials and response private keys remain on the server. The opaque current company session travels in the protected `harbor_partner_visit_session` cookie; the company keeps no session-to-keyring identity mapping. The old `harbor_commerce` guest cookie is retired. The shared adapter serializes connection preparation and reuses the same unexpired signed request after checking its service status. Reloads and simultaneous tabs cannot replace the QR just by preparing it again. A replaced, cancelled or ended request is not reusable. Returns must match the current pending state and browser visit. The server can replay only the identical already-completed return safely. A `verification_required` response supplies `nextAuthorizationUrl` for fresh service verification of the pending action. For a protected action with an existing unexpired session, `continueSessionId` allows Mementom to preserve that same session and previous approvals after checking the holder/connection binding. Cancel or denial grants no action right and preserves an existing browsing connection. Harbor retains save/order/bid/private-area intent and resumes the requested flow after authorization; review and confirmation remain explicit. Saving uses `POST /harbor/api/saved` with `{productId,saved}`. Bids use `{productId,amountMinor,idempotencyKey}` and orders use `{productId,quantity,idempotencyKey,offerCart?}`. Server validation enforces stock, auction deadlines, minimum/increment and private/bid rights. The same key/input returns its original receipt; reusing a key with different input fails. After an uncertain response, check that same request rather than create another action. Commerce POSTs require connection and reject cross-origin requests. ## Contribute only permitted activity Harbor derives insights from meaningful shopping actions within the current connected visit: product views, saves, comparisons, bids and orders. Guest history and previous connections are excluded. Raw search text is not contributed. The scalar commerce aggregates contain no individual product IDs; separate registered event datasets carry catalog-backed product references for actual product observations. Contributions write only values changed since the last accepted update. Idle timers, connection checks and activity refreshes do not trigger insight writes. Event times support the internal journal and session boundaries; they are not commerce insight datasets. Harbor also stops repeating writes to its legacy **Connected site activity** dataset. There are 24 active commerce fields, excluding `active_seconds` and `activity_span_seconds`. Historical registered definitions and datasets keep their contracts. An existing 26-field **Harbor browsing, bids and orders** dataset receives only the active fields. The older 24-field contract lacks `bid_starts` and `checkout_starts`, so its workspace continues to use the separately registered complete commerce dataset. Fresh workspaces use **Harbor shopping activity** with 24 active fields. Field membership identifies the contract; matching field counts do not establish compatibility. An active company connection permits registered site-insight writes unless the owner has stopped recording. Recording does not grant sharing or audience matching. Existing sharing permissions are not widened, and creating a dataset or audience does not activate campaigns. ## Show the actual connection activity Clicking the connected badge opens a Mementom-hosted popup for meaningful shopping actions and keyring contribution results. API reads, polling, idle timers, unchanged updates and replayed requests do not add rows. It updates every two seconds while open and visible, and after commerce receipts. Shopping continues in the original window; phones may open the popup as a tab. **My activity** remains the place for saved products, bids and orders. `GET /harbor/api/activity` returns the current visit's activity and permitted saved activity from earlier sessions in a newest-first `timeline` and a `connection` object with `connected`, `status`, `companyName` and `domain`. The badge and window share the same verified current connection. Stale responses are invalidated when it changes; preparing a QR or failing a read does not independently establish another connection state. Activity polling does not write insights. Each row describes the action, time, site outcome and contribution status. Mutation and insight-sync responses also include `activity` and `activityRecorded`. Use `saved` only when the SDK confirmed a commerce insight contribution. Successful rows show the action and time without repeated save confirmations. Shared value details say **insights updated**, because fields replace aggregates. Accepted field counts remain visible when values are private. Contributed values require current sharing; a private value does not mean the write failed. `recorded` means the site activity was recorded; it does not mean data reached the keyring. Keep `paused`, `permission_required`, `pending`, `unavailable`, `not_connected`, `no_activity` and `failed` outcomes distinct. The site action's `outcome` (`success`, `no_change`, `replayed` or `error`) is separate: a saved order can coexist with an insight write blocked by an explicit recording stop. Responses can report no change or replay without a new journal row. Do not create a display entry unless `activityRecorded` confirms one. The company server keeps up to 100 journal rows per connected commerce partition. Accepted contribution history is stored by Mementom and read using the current opaque session. Mementom resolves the keyring internally and scopes history to that keyring, the company application and registered origin. Applications sharing one development hostname do not share history. Current holder sharing and private-auction rights remain enforced. No stable keyring identifier or session-to-keyring mapping is returned to or retained by the company. Saved rows include an opaque `receiptId` for deduplication and `currentSession` for distinguishing earlier activity. `historyStatus` reports an unavailable history read without hiding current local actions or claiming they were saved. Earlier receipts without stored action context cannot reconstruct product names or searches. The window never exposes credentials, secret tokens or proof. For customer account association in another partner product, use the [account-access guide](vendor-account-access.md). Harbor's connection does not create that association. ## Manage activity and sharing Activity can expose a scoped opaque `activityContext` and `activityPaused`. The Mementom-owned `/session-activity/` page obtains owner authorization through a service-issued ceremony, then calls its own `/api/v1/key-ring/activity-sharing/{presentation,state,authorize,exchange,save}` operations. Owner sessions and mutation authority stay on that surface. The company bridge accepts only activity reads and Disconnect. The owner can pause/resume activity, choose sharing for eligible saved receipt groups, and set **New activity** sharing for future items. That default does not change older receipt choices. Pausing preserves connection and permitted commerce actions while stopping activity capture; resume starts a fresh capture partition without backfilling paused actions. Dataset recording stops, sharing and audience matching are separate controls. Activity reads remain scoped to the current holder, company application and registered origin. ## Product viewing audiences Marketing → Audiences → Add condition → **Product viewed** offers a searchable **Product** selector and **Viewing time** comparison, amount and seconds/minutes controls. For example: **Product viewed where Product is Tidal Forms and Viewing time is at least 30 seconds**. Product and duration are filters on the same observation. Time on another product cannot satisfy this condition. One registered event definition, `commerce.product_viewed`, serves the entire catalog. Its properties are `productId` and `durationSeconds`. Product IDs and names live in the application's mutable `products` catalog; adding a product does not create a definition or require a new audience-builder component. Both the editor and the matcher use the registered schema, with no Harbor-specific branch. The reusable product-view tracker sends paired `product_viewed` / `product_left` boundaries with the same `viewId` and `productId`. The partner backend supplies both timestamps and calculates elapsed seconds. The browser cannot submit its own duration or timestamp. Navigating away, hiding the page or disconnecting completes the view. Returning to a visible product starts a new view. Page exit uses a keepalive request; missing or unmatched end events do not invent duration. These intervals measure elapsed visible-page time, not verified human attention. Completed observations retain their product/time pairing. Separate visits are never summed. Only changed observations are submitted, with no periodic browser time or heartbeat writes. The service appends bounded records across connected sessions on the same keyring, with independent observation time and expiry for each record. An unrelated new product cannot refresh an older observation's expiry. Equal records compact; the current event contract retains up to 100 recent distinct records per definition within its 30-day expiry. This is bounded insight storage rather than a raw clickstream archive. Historical activity is not backfilled from ambiguous or unpaired entries. **Product viewing** is a separate registered dataset. Existing commerce contracts, owner sharing decisions and private-auction access checks remain unchanged. Recording a value does not enable sharing or audience matching. Company/app scope remains enforced independently from the display catalog. The generic `event` field supports reviewed custom properties and catalog references for other company needs. A condition uses `operator: "matches"` and `value.filters`; every filter must match a single record. Scalar definitions and existing audience JSON remain supported. See the SDK documentation for catalog updates and event contributions. ## Simplified company insight library (October 11) Marketing offers source-configured action choices instead of a separate insight for every product or counter. Product, category, filter, amount and challenge details are catalog-backed blanks within the action. Existing saved audiences retain their original definitions; retired counters and the undefined “Journey completed” starter are hidden in new audience discovery. `commerce.product_viewed` carries `productId` and `durationSeconds` together. Shopping actions include save, compare, category browse, bid/checkout start, accepted bid and accepted order. Product filters retain the actual option selected; price filters retain the applied numeric range. USD values store cents and display dollars. Every property has an explicit Any choice that removes its restriction. When offers Any time and 1, 7 or 30 days, bounded by retained history. The selected period is observation scope (`lookbackDays`), separate from property `filters`. All selected properties use AND on the same event. For alternatives, add the same insight again with different values and join the instances with OR; the existing logic groups combine these rules. All-Any means an actual retained occurrence is required, not that everyone matches. Original server observation times survive delayed delivery and retry. A later unrelated action cannot refresh an older event. A genuine repetition can update that event's recency; an idle synchronization cannot. Legacy tuples with no original time are not silently backfilled. Site and Mementom challenge options come from published application definitions. Only verified server observations are recorded; repeated syncs reuse their original timestamp and idempotency key. Session linking is not labeled as a newly completed Mementom action challenge. Sites with no configured challenges have no invented challenge options. Custom reviewed definitions use the same model and remain visible in their own group. The library includes eleven commerce choices: Product viewed, Category browsed, Product filter selected, Price range selected, Product saved, Product searched, Product compared, Bid started, Bid placed, Checkout started and Order placed. Site challenge and Mementom challenge appear when their published definitions have real options. Legacy aggregate fields and `commerce.filter_used` remain usable by existing audiences while hidden from new discovery. ## Active commerce field contract The 24 scalar definitions use the prefix `harbor.session.`: | Type | Field suffixes | | --- | --- | | Count | `product_views`, `category_views`, `searches`, `filters`, `comparisons`, `save_actions`, `unsave_actions`, `bid_starts`, `checkout_starts`, `bids`, `orders`, `products_viewed`, `categories_explored`, `saved_items`, `lots_bid_on`, `products_ordered`, `units_ordered`, `interactions`, `repeat_product_views` | | USD value band | `highest_viewed_price`, `highest_bid`, `order_value` | | Enum | `most_explored_category`, `stage` | These replace current-visit aggregates with a one-day observation window and 30-day expiry. Product viewing, Shopping actions, Product filters and Product searches are separate event datasets. Their records retain original server observation times and catalog-backed properties; they do not add fields to the 24-scalar contract. New recorded values remain private unless the owner chooses sharing, including an explicit sharing default for new activity. ## Campaign landing, cart and outcomes Harbor publishes descriptions, listed prices, photos, availability and product destinations in its registered products catalog. Offer authors select a product and landing details; the marketing preview, consumer offer and hosted landing reuse the same creative renderer. The shared `apps/web/public/shared/offer-arrival.js` handles arrival confirmation and cart presentation. `apps/service/src/campaign-landing.mjs` exports `createCampaignLandingApi`; `offer-cart.mjs` exports `offerCart`. Harbor adds its catalog and checkout adapter to this shared flow. A consumer offer opens an exact HTTPS destination with an opaque `#mementom-visit=` arrival capability. `POST /api/v1/campaign-landings/complete` validates `{token,page}`, the current assignment/revision, session, destination and origin. A confirmed arrival records one earned visit reward per delivery and returns its receipt. The selected product becomes a removable cart line. `POST /api/v1/offer-cart` supports `read`, `remove` and `handoff` with an origin-bound cart token. **Continue to cart** carries `#mementom-cart=` to the product's Harbor destination. **Review order** resumes Harbor's connection, quantity/stock check and explicit order review. The order includes `offerCart:{token,lineId}`; Harbor verifies the line, application and product and uses the server quote. Reopening a removed delivery does not restore its line. After an accepted order, the browser removes its cart line in a separate request; removal failure does not undo the order. Keep `arrival_confirmed`, `insight_release_completed`, `purchase_confirmed` and `purchase_reversed` as separate outcomes. Purchase confirmation requires a prior confirmed arrival; reversal requires its original purchase. Harbor's accepted order receipt reports `status:'placed'` and its actual `paymentStatus`; recording an order is not confirmation of payment, fulfillment or a purchase reward. Visit rewards are reported as earned, with settlement tracked separately. An offer or cart capability grants neither private-auction access nor bidding rights.