Seylora

A gemstone API built for shops that sell online.

Pull reviewed inventory into your own site at your account price, keep it in sync with a changes cursor, and reserve stones with a single request that cannot double-book.

MethodPathScopeDescription
GET/api/v1/gemstonescatalog:readList approved gemstones at your account price. Filters: type, origin, treatment, shape, certified, carat_min, carat_max, price_min, price_max, available, cursor, limit (≤100).
GET/api/v1/gemstones/changes?cursor=…catalog:readGemstones changed since an opaque cursor, ordered by change_seq. Poll this to stay in sync.
GET/api/v1/gemstones/{id}catalog:readOne gemstone by public id.
GET/api/v1/gemstones/{id}/availabilitycatalog:readLightweight availability check with feed freshness.
POST/api/v1/reservationsreservations:writeBody: { gemstone_id, idempotency_key?, notes? }. Atomically holds the stone. 409 if it is not available.
GET/api/v1/reservations/{id}reservations:writeReservation status: pending_supplier_confirmation, confirmed, expired, rejected, cancelled, converted.
POST/api/v1/ordersorders:writeBody: { reservation_id, idempotency_key?, shipping? }. Only supplier-confirmed reservations can be ordered.
GET/api/v1/orders/{id}orders:writeOrder status and price snapshot.
GET/api/v1/orders/{id}/trackingorders:writeTracking details once shipped.
POST/api/v1/webhook-endpointswebhooks:writeBody: { url, events[] }. Returns a signing secret once.

Authentication and limits

  • Send Authorization: Bearer <key>. Keys start with gsn_live_ or gsn_test_ (sandbox), are scoped, and are shown once at creation. Never put a key in a URL or a browser bundle.
  • 120 requests per minute per key. Responses carry x-request-id; quote it when you contact support.
  • All timestamps are UTC ISO 8601. Money is { amount, currency, basis } with two decimals, per stone.
  • Pagination is cursor based. next_cursor is null on the last page.
  • Idempotency: pass idempotency_key on reservations and orders; replays return the original resource.
  • Versioning: breaking changes ship under /api/v2; v1 is supported for at least 12 months after a successor is announced.

Errors

StatusCodeMeaning
401unauthorizedMissing or invalid key
403forbidden / insufficient_scopeOrg not approved, or key lacks the scope
404not_foundUnknown id, or not visible to your account
409not_available / feed_staleStone cannot be reserved right now
422validation_errorBad request body; details lists fields
429rate_limited120 requests per minute per key
{ "error": { "code": "not_available", "message": "This gemstone is no longer available to reserve", "details": null, "request_id": "a1b2c3d4e5f6a7b8" } }

Sample gemstone

{
  "id": "gem_7kq2m9x4pa3n",
  "slug": "2-14ct-blue-sri-lanka-oval-sapphire-7kq2m9x4pa3n",
  "type": "sapphire",
  "variety": "blue sapphire",
  "origin": { "country": "Sri Lanka", "region": "Ratnapura", "evidence": "supplier_declared" },
  "shape": "oval",
  "carat": 2.14,
  "dimensions_mm": { "length": 8.62, "width": 6.41, "depth": 4.12 },
  "color": "blue",
  "treatment": { "value": "HEATED", "details": "Heated only", "evidence": "certificate_attached" },
  "certification": { "lab": "GRS", "number": "GRS2026-041712", "url": "https://…" },
  "media": { "photos": ["https://…/photo-1.webp", "…"], "video": "https://…/video-1.mp4" },
  "availability": { "status": "available", "available": true, "quantity": 1, "feed_status": "fresh" },
  "price": { "amount": 1350.00, "currency": "USD", "basis": "per_stone" },
  "supplier_last_synced_at": "2026-09-22T14:02:11Z",
  "platform_updated_at": "2026-09-22T14:02:11Z",
  "change_seq": "1790092931000000"
}

Examples

curl
curl -s https://seyloragems.com/api/v1/gemstones?type=sapphire&available=true \
  -H "Authorization: Bearer gsn_test_YOUR_KEY"
JavaScript
const res = await fetch("https://seyloragems.com/api/v1/reservations", {
  method: "POST",
  headers: { Authorization: "Bearer gsn_test_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ gemstone_id: "gem_7kq2m9x4pa3n", idempotency_key: crypto.randomUUID() }),
});
if (res.status === 409) console.log("Not available:", (await res.json()).error.message);
PHP
$ch = curl_init("https://seyloragems.com/api/v1/gemstones/changes?cursor=" . $cursor);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer gsn_test_YOUR_KEY"]]);
$data = json_decode(curl_exec($ch), true);
foreach ($data["items"] as $gem) { /* upsert into your catalog */ }
$cursor = $data["next_cursor"];
Python
import requests
r = requests.get("https://seyloragems.com/api/v1/gemstones/gem_7kq2m9x4pa3n/availability",
                 headers={"Authorization": "Bearer gsn_test_YOUR_KEY"})
print(r.json()["availability"])
Webhook verification
// Node.js webhook verification
const [t, v1] = req.headers["x-gsn-signature"].split(",").map(p => p.split("=")[1]);
const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${rawBody}`).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.status(400).end();
// Deliveries are at-least-once: deduplicate on the "id" field.

Integration checklist

  1. Create a sandbox key in Developers and pull /gemstones.
  2. Store id and change_seq; poll /gemstones/changes every few minutes.
  3. Show availability.available as reservable, not as in-stock. Hide stones whose feed_status is stale.
  4. On checkout, POST a reservation with an idempotency key; handle 409 by refreshing the listing.
  5. Register a webhook and verify signatures; act on reservation.confirmed before charging your customer.
  6. Rotate to a live key after approval; revoke the sandbox key.