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.
| Method | Path | Scope | Description |
|---|---|---|---|
GET | /api/v1/gemstones | catalog:read | List 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:read | Gemstones changed since an opaque cursor, ordered by change_seq. Poll this to stay in sync. |
GET | /api/v1/gemstones/{id} | catalog:read | One gemstone by public id. |
GET | /api/v1/gemstones/{id}/availability | catalog:read | Lightweight availability check with feed freshness. |
POST | /api/v1/reservations | reservations:write | Body: { gemstone_id, idempotency_key?, notes? }. Atomically holds the stone. 409 if it is not available. |
GET | /api/v1/reservations/{id} | reservations:write | Reservation status: pending_supplier_confirmation, confirmed, expired, rejected, cancelled, converted. |
POST | /api/v1/orders | orders:write | Body: { reservation_id, idempotency_key?, shipping? }. Only supplier-confirmed reservations can be ordered. |
GET | /api/v1/orders/{id} | orders:write | Order status and price snapshot. |
GET | /api/v1/orders/{id}/tracking | orders:write | Tracking details once shipped. |
POST | /api/v1/webhook-endpoints | webhooks:write | Body: { url, events[] }. Returns a signing secret once. |
Authentication and limits
- Send
Authorization: Bearer <key>. Keys start withgsn_live_orgsn_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_cursoris null on the last page. - Idempotency: pass
idempotency_keyon 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
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid key |
| 403 | forbidden / insufficient_scope | Org not approved, or key lacks the scope |
| 404 | not_found | Unknown id, or not visible to your account |
| 409 | not_available / feed_stale | Stone cannot be reserved right now |
| 422 | validation_error | Bad request body; details lists fields |
| 429 | rate_limited | 120 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
- Create a sandbox key in Developers and pull
/gemstones. - Store
idandchange_seq; poll/gemstones/changesevery few minutes. - Show
availability.availableas reservable, not as in-stock. Hide stones whosefeed_statusis stale. - On checkout, POST a reservation with an idempotency key; handle 409 by refreshing the listing.
- Register a webhook and verify signatures; act on
reservation.confirmedbefore charging your customer. - Rotate to a live key after approval; revoke the sandbox key.