Skip to content
Menu

API basics

Keys, errors, safe retries, usage, limits and lists.

Base URL: https://api.seekgist.com. Every path starts with /v1. The full list of endpoints is in the API reference.

Keys

CredentialLooks likeUse it for
Public keysg_pub_ + 44 characterssuggest, search, recommend, click, event; assistant chat only for a key made with Also allow the shopping assistant
Secret keysg_sec_ + 44 charactersproducts, synonyms, rules, translation, stats, webhooks
Sessioncookie sg_sessionthe dashboard only

Send a key in one of these ways:

Authorization: Bearer sg_pub_...
X-SeekGist-Key: sg_pub_...
GET /v1/suggest?q=lab&key=sg_pub_...        (public keys only)
  • A secret key in a URL is refused with 401.
  • You can lock a public key to your website with allowed_origins.
  • Each key has scopes, for example search or catalog:write. A missing scope gives 403 insufficient_scope.
  • GET /v1/whoami tells you which store and scopes a key has. Connectors use it for their "Test connection" button.

Errors

Every error has the same shape (RFC 9457, Content-Type: application/problem+json):

{
  "type": "https://seekgist.com/docs/errors/invalid_request",
  "title": "Invalid request",
  "status": 422,
  "code": "invalid_request",
  "detail": "Some fields are not valid.",
  "request_id": "req_4f1c0d9a8b7e6f5a4b3c",
  "errors": [{ "field": "upsert.3.price", "code": "greater_than_equal", "message": "Input should be >= 0" }]
}

Use code in your program: it never changes. Quote request_id when you ask for help.

CodeMeaning
invalid_requestSome fields are not valid. See errors.
unauthorizedNo key, or a wrong key.
forbidden, insufficient_scopeThe key may not do this.
not_foundThe thing does not exist (or belongs to another store).
conflictThe change clashes with the current state.
idempotency_key_reuse, idempotency_in_progressSee "Safe retries" below.
rate_limitedToo many requests. Wait for Retry-After seconds.
plan_limit_reachedYour plan limit is reached.
unsupported_localeThe language is not one of your store's languages.
not_implementedThis endpoint is not built yet.
internal_errorOur fault. Retry later and quote request_id.

Safe retries

Send Idempotency-Key: <a unique value, 8-255 characters> on writes (POST, PUT, PATCH, DELETE). If the network drops and you send the same request again with the same key, you get the first answer back with Idempotent-Replayed: true, and nothing happens twice.

  • Keys are kept for 24 hours per store.
  • The same key with a different body gives 422 idempotency_key_reuse.
  • The same key while the first call still runs gives 409 idempotency_in_progress.

Usage and billing

  • Only searches are billed: a /v1/search call with page=1. Pages 2 and later are free.
  • Suggestions while typing are never billed.
  • Every search answer has a header like this (limit=-1 means no fixed limit):
X-SeekGist-Usage: searches=1234, limit=150000, billed=1, period="2026-10"

Rate limits

Limits are per key, per second. Every answer has RateLimit-Limit and RateLimit-Remaining. Over the limit you get 429 rate_limited with Retry-After.

Lists

Lists answer {"data": [...], "next_cursor": "...", "total": 123}. Pass next_cursor back as ?cursor= to get the next page. total is there when it is cheap to count.

Long jobs

Reindexing, imports, syncs, translation and exports answer 202 with a job. Call GET /v1/jobs/{id} until status is succeeded, failed or canceled.

Languages

lang, locale and target_locale take a two-letter code (en, ar). It must be one of your store's languages (GET /v1/settings shows locales), or you get 422 unsupported_locale. lang=auto lets SeekGist detect the language.

Webhooks

Webhooks are signed with the Standard Webhooks scheme. Check the signature before you trust a delivery:

import base64, hashlib, hmac, time
 
def verify(secret: str, headers: dict, body: bytes) -> bool:
    key = base64.b64decode(secret.removeprefix("whsec_"))
    msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > 300:
        return False
    mac = hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest()
    expected = "v1," + base64.b64encode(mac).decode()
    return any(hmac.compare_digest(expected, s) for s in headers["webhook-signature"].split())

Reject timestamps more than 5 minutes from now, and drop a webhook-id you have seen before. Failed deliveries are retried with backoff for 3 days, then marked dead.

Versions

/v1 only grows: new endpoints, new optional fields, new values in answers. Your code must ignore fields it does not know. A breaking change would come as /v2.