Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.closient.com/llms.txt

Use this file to discover all available pages before exploring further.

Closient’s local-first search depends on accurate, fresh in-store inventory. This skill takes a retailer Organization from “signed up” to “discoverable in local search” — POS connected, first batch of InStoreOffer records synced, and at least one Location published with PostGIS coordinates so distance-ranked search can find them.

When to use

  • A retailer just signed up and needs to be onboarded.
  • The agent is helping a retailer add their second / third store location after the first connect.
  • A retailer’s existing POS integration broke and the connection needs re-establishing.

Auth

OAuth token (or API key) with integrations:write, offers:write, and locations:write scopes. The caller must hold OWNER or MANAGER role on the retailer Organization.

Flow

Step 1 — Pick a POS integration

Closient supports Shopify today, with Square, Clover, and Lightspeed on the roadmap (see Known gaps). The agent should ask the retailer which POS they use; if it’s not Shopify, route them to the support flow rather than mid-onboarding.

Step 2 — Connect via OAuth

Shopify connect is a non-API browser flow today (the OAuth redirect cannot complete inside an agent-only context):
GET /integrations/shopify/install?shop=<retailer-shop>.myshopify.com
This redirects to Shopify’s OAuth consent screen. After the user approves, Shopify calls back to:
GET /integrations/shopify/callback?code=...&shop=...&state=...
Closient persists the access token and provisions an InventorySource row (in the retailers app, not integrations — see apps/integrations/models.py for the rationale). The agent’s job here is to:
  1. Generate the install URL with the user’s shop domain.
  2. Hand the URL to the user (or surface it as a clickable link).
  3. Wait for the user to confirm completion, then verify via Step 3.

Step 3 — Verify the connection

GET /integrations/api/v1/webhooks/endpoints/
The provisioned webhook endpoints for the new Shopify connection should appear in the list. If count == 0 after the user completed the OAuth handshake, the connect didn’t land — re-issue Step 2 with a fresh state token.

Step 4 — Sync first batch of inventory

Shopify inventory sync is automatic on connection (background task in apps/integrations/shopify/). The agent should poll for results:
GET /retailers/api/v1/organizations/{organization_id}/in-store-offers
The first sync typically takes 30-120 seconds for catalogs under 1k SKUs. The agent should report progress to the user with the count returned. Manual offer creation (for retailers who want to bootstrap without a POS) is also supported:
POST /retailers/api/v1/organizations/{organization_id}/in-store-offers
Content-Type: application/json

{
  "product_id": "<short_id>",
  "physical_store_id": "<location short_id>",
  "sku": "...",
  "price_amount": 1099,
  "price_currency": "USD",
  "quantity_on_hand": 12,
  "status": "in_stock"
}
The (product_id, physical_store_id, sku) triple is unique — duplicates return 422.

Step 5 — Publish the first Location

Local search ranks by great-circle distance using PostGIS, so a Location with accurate lat/lon is required for the retailer to appear in nearby-store queries. Today this is done via the dashboard UI (/organizations/create and the org-scoped location form) — there is no POST /locations/api/v1/locations endpoint yet (see Known gaps). The agent should:
  1. Direct the user to the dashboard location form.
  2. Once published, verify via the public endpoint:
GET /locations/api/v1/locations?lat=<store_lat>&lon=<store_lon>&radius=1
The retailer’s location should appear with distance_km near zero.

Step 6 — Confirm discoverability

POST /search/api/v1/search/session
Content-Type: application/json

{
  "query": "<one product the retailer carries>",
  "latitude": <store_lat>,
  "longitude": <store_lon>,
  "radius_km": 5
}
The retailer’s store should appear in results within a few minutes of the inventory sync completing. If it doesn’t, the most common cause is zero published Location rows for the org — re-verify Step 5.

Required inputs

  • organization_id — the retailer’s organization.
  • shop_domain<retailer>.myshopify.com (Shopify-only today).
  • One physical store address (city, state/region, postal code) for Step 5.

Optional inputs

  • latitude / longitude — if the retailer already knows them; else the dashboard form geocodes from the address.
  • Store hours — populated via the dashboard location form.
  • Webhook endpoints — provisioned automatically; can be listed via GET /integrations/api/v1/webhooks/endpoints/.

Output

  • integration_statusconnected once Step 3 verifies; pending while waiting for OAuth completion.
  • synced_offer_count — from the count returned by the in-store-offers list endpoint in Step 4.
  • first_location_url — the public location URL once Step 5 completes (form path: /marketplace/<org_short_id>/locations/<location_short_id>).

Guidance for agents

  • Surface the dashboard handoff explicitly. Steps 2 (OAuth) and 5 (Location create) are not fully API-driven today — the user has to click a link / fill a form. Be honest about this rather than pretending the agent can complete them headlessly.
  • Don’t poll faster than 5 seconds on the in-store-offers list — Shopify’s catalog sync is rate-limited and we throttle the public API at 300 req/min/key. A patient backoff is more user-friendly than flooding the API.
  • One Location at minimum, but encourage all of them. Local search ranks by proximity per-location, so a multi-store retailer that only publishes one location is invisible to the other neighborhoods.
  • Inventory freshness matters more than completeness. Surface the last_inventory_sync field on offers so the user knows their data is current — stale inventory ranks lower in search.
  • Webhook secret. The OAuth flow provisions endpoints with a rotating secret. If the retailer needs to verify webhook deliveries on their side, the secret is available via GET /integrations/api/v1/webhooks/endpoints/{endpoint_id} and can be rotated via POST /webhooks/endpoints/{endpoint_id}/rotate-secret/.

Known gaps (planned)

  • Shopify-only today. The original ticket scope mentions Square, Clover, and Lightspeed. Models reference these as future InventorySource types but no OAuth flow exists. Planned per app in apps/integrations/ — track via the integrations roadmap.
  • No headless POS-connect endpoint. Step 2 requires a browser redirect today. A device-code OAuth flow (so agents can complete the connect on behalf of a user with a one-time code) is planned.
  • No POST /locations/api/v1/locations. Step 5 is currently a dashboard form. A first-class Location create endpoint (with PostGIS coordinates, hours, contact info) is planned so this skill can be fully API-driven end-to-end.
  • No “is my store discoverable?” health endpoint. Step 6 uses the search endpoint as an indirect probe. A dedicated readiness check (POS connected + ≥1 location published + ≥1 in-stock offer) would be cleaner.
  • No sync-job status endpoint. The agent has to poll the offer list to infer sync progress. A GET /integrations/api/v1/jobs/{job_id} is planned.
  • claim-brand — the parallel flow for brand-owner Organizations.
  • local-product-search — the surface this onboarding enables — what consumers see once the retailer is live.
  • check-product-availability — how agents probe a specific GTIN against the retailer’s now-synced offers.
  • brand-retail-footprint — once a retailer is live, this skill (called from a brand owner’s side) shows their products being carried.