---
name: ada-diamonds-api
description: Reference for the Ada Diamonds public REST API (https://www.adadiamonds.com/api/v1) covering every endpoint, self-serve API keys and OAuth 2.1 scopes, the public sandbox key, rate limits, error shapes, versioning, and working curl examples. Use when integrating with Ada Diamonds over HTTP, generating client code, debugging a 401/403/429, or when the ada-diamonds-shopping skill needs exact parameter or response details.
license: MIT
metadata:
  author: Ada Diamonds
  homepage: https://www.adadiamonds.com/developers
  version: "1.0.0"
---

# Ada Diamonds REST API

Base URL: `https://www.adadiamonds.com/api/v1`. All responses are JSON. All prices are US dollars as decimals.

Authoritative machine descriptions (prefer these over memory when generating code):

- OpenAPI 3.1: `https://www.adadiamonds.com/openapi.json`
- RFC 9727 API catalog: `https://www.adadiamonds.com/.well-known/api-catalog`
- Human docs: `https://www.adadiamonds.com/developers` (also served as markdown with `Accept: text/markdown`)
- Agent guide: `https://www.adadiamonds.com/agents.md`
- Site index: `https://www.adadiamonds.com/llms.txt`

## Quickstart (no credential)

```bash
curl "https://www.adadiamonds.com/api/v1/diamonds?shape=Oval&min_carat=1&max_price=4000&sort=price_asc"
```

## Endpoints

| Method and path | Returns | Scope |
| --- | --- | --- |
| `GET /diamonds` | Available loose lab diamonds | public |
| `GET /diamonds/{id}` | One diamond, by slug or grading report number | public |
| `GET /engagement-rings` | Engagement ring settings (price excludes center stone) | public |
| `GET /engagement-rings/{slug}` | One setting, by slug or SKU (`AD-066` or `066`) | public |
| `GET /jewelry` | Fine jewelry | public |
| `GET /jewelry/{slug}` | One piece, by slug or SKU | public |
| `GET /knowledge-base` | Published guides (`q` matches title and summary) | public |
| `GET /knowledge-base/{slug}` | One guide, body as markdown | public |
| `GET /showrooms` | Showrooms and remote consultation options | public |
| `POST /consultations` | Request a jeweler consultation | `appointments:write` |
| `POST /keys` | Issue a self-serve API key | public |
| `POST /batch` | Up to 20 GET requests in one round trip | public |
| `POST /exports` | Queue a full catalog export; 202 with a job to poll | public |
| `GET /jobs/{id}` | Status of an export job (`queued` or `succeeded`) | public |
| `GET /exports/{id}` | Download a finished export (NDJSON or JSON) | public |

Outside `/api/v1`, on the site root:

| Method and path | Returns |
| --- | --- |
| `GET https://www.adadiamonds.com/ask?query=...` | NLWeb natural-language answer over the catalog and guides (JSON, or SSE with `streaming=true`) |
| `GET https://www.adadiamonds.com/.well-known/agent-card.json` | A2A agent card; JSON-RPC endpoint at `/a2a` |
| `GET https://www.adadiamonds.com/auth.md` | Agent authentication walkthrough |
| `GET https://www.adadiamonds.com/pricing.md` | Machine-readable pricing rules |
| `GET <any page>?mode=agent` | Structured agent view of a page instead of marketing HTML |

Full parameter tables and response fields: [references/endpoints.md](references/endpoints.md).

### Batch

```bash
curl -X POST https://www.adadiamonds.com/api/v1/batch \
  -H "Content-Type: application/json" \
  -d '{"requests":[
    {"id":"setting","method":"GET","path":"/api/v1/engagement-rings","query":{"shape":"Oval","type":"Solitaire","limit":1}},
    {"id":"stone","method":"GET","path":"/api/v1/diamonds","query":{"shape":"Oval","min_carat":1.5,"max_carat":1.6,"sort":"price_asc","limit":1}}
  ]}'
# 200 { "object": "batch", "count": 2, "responses": [ { "id": "setting", "status": 200, "body": {...} }, { "id": "stone", "status": 200, "body": {...} } ] }
```

GET only, catalog and content paths only, at most 20 entries. Each entry returns its own `status` and body in the same order, so one failure never hides the others; the batch itself is 200. The batch counts as one rate-limit unit.

### Exports and jobs

```bash
curl -X POST https://www.adadiamonds.com/api/v1/exports \
  -H "Content-Type: application/json" \
  -d '{"type":"diamonds","format":"jsonl","filters":{"shape":"Oval"}}'
# 202, Location + Retry-After headers, body: { "job": { "id", "status": "queued", "status_url", "result_url": null, "expires_at" } }
curl https://www.adadiamonds.com/api/v1/jobs/<id>        # "queued" (with Retry-After) then "succeeded" with result_url
curl https://www.adadiamonds.com/api/v1/exports/<id>     # application/x-ndjson, one product per line
```

`type` is `diamonds`, `engagement-rings`, or `jewelry`; `format` is `jsonl` (default) or `json`; `filters` accepts the same query parameters as the matching list endpoint. Exports complete within seconds and expire 24 hours after creation. Honour `Retry-After` instead of polling in a loop. Use this instead of walking `offset` through the whole catalog.

### Pagination

List endpoints take `limit` (1-200, default 25) and `offset`, and return:

```json
{
  "object": "list",
  "currency": "USD",
  "data": [ ... ],
  "pagination": { "total": 412, "limit": 25, "offset": 0, "has_more": true }
}
```

Engagement ring lists also carry a `price_note` reminding that `price_from` excludes the center diamond.

## Authentication

Reading the catalog and content needs nothing. Credentials raise the rate limit and unlock customer-specific endpoints.

### Public sandbox key (works for everyone, no signup)

```bash
curl "https://www.adadiamonds.com/api/v1/diamonds?limit=1" \
  -H "X-Ada-Api-Key: ada_test_public_sandbox_key"
```

It carries only `catalog:read` and `content:read`. Sandbox credentials read the real catalog and accept writes without creating anything: a sandbox `POST /consultations` returns `201 { "sandbox": true, ... }` and no jeweler is contacted.

### Self-serve API key

```bash
curl -X POST https://www.adadiamonds.com/api/v1/keys \
  -H "Content-Type: application/json" \
  -d '{"name":"my-agent","env":"sandbox"}'
```

- `name` (required), `env` (`sandbox` or `production`, default sandbox), `scopes` (optional; only `catalog:read` and `content:read` are self-serve).
- The key is returned once and stored only as a hash.
- Send it as `Authorization: Bearer <key>` or `X-Ada-Api-Key: <key>`.

### OAuth 2.1

Metadata: `https://www.adadiamonds.com/.well-known/oauth-authorization-server`. PKCE is mandatory. Dynamic client registration (RFC 7591):

```bash
curl -X POST https://www.adadiamonds.com/api/oauth/register \
  -H "Content-Type: application/json" \
  -d '{"client_name":"My shopping agent","redirect_uris":["https://example.com/callback"],"grant_types":["authorization_code","refresh_token"],"scope":"catalog:read content:read"}'
```

Public scopes with no user involved:

```bash
curl -X POST https://www.adadiamonds.com/api/oauth/token \
  -d "grant_type=client_credentials" -d "client_id=ada_client_..." -d "scope=catalog:read content:read"
```

Customer scopes use the authorization code flow: the customer signs in at `/oauth/authorize` and approves the exact scopes on a consent screen.

### Scopes

| Scope | Grants | How to obtain |
| --- | --- | --- |
| `catalog:read` | Products, prices, specifications | API key or `client_credentials` |
| `content:read` | Articles, guides, showroom info | API key or `client_credentials` |
| `profile:read` | The signed-in customer's profile | Authorization code flow |
| `orders:read` | The signed-in customer's orders | Authorization code flow |
| `cart:write` | Build a cart for the customer | Authorization code flow |
| `appointments:write` | Request a consultation for the customer | Authorization code flow |

## Consultations

```bash
curl -X POST https://www.adadiamonds.com/api/v1/consultations \
  -H "Authorization: Bearer <token with appointments:write>" \
  -H "Idempotency-Key: 6f1c0e3a-..." \
  -H "Content-Type: application/json" \
  -d '{"email":"shopper@example.com","name":"Sam","topic":"engagement_ring","message":"1.5ct oval, under $6k total"}'
```

Fields: `email` (required), `name`, `phone`, `topic` (`engagement_ring`, `loose_diamond`, `wedding_band`, `fine_jewelry`, `custom_design`, `other`), `message`, `preferred_contact`. Always send `Idempotency-Key`; without it a retry can create a duplicate request that a jeweler follows up on twice. A jeweler replies by email within one business day.

## Rate limits

| Caller | Limit | Window |
| --- | --- | --- |
| Anonymous | 120 requests | 60 seconds |
| API key or access token | 600 requests | 60 seconds |

Every response carries RFC 9331 headers. Read them and pace yourself instead of retrying into a refusal:

```
RateLimit: limit=120, remaining=118, reset=47
RateLimit-Policy: "anonymous";q=120;w=60
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 47
```

A `429` carries `Retry-After` and `retry_after_seconds`. Counters are per serverless instance, so treat the headers as a cooperative signal rather than a hard guarantee.

## Errors

Always JSON, including 404s on unknown paths:

```json
{
  "error": "not_found",
  "error_description": "Diamond \"abc\" was not found. Lab diamond inventory turns over as stones sell. List what is currently available at https://www.adadiamonds.com/api/v1/diamonds.",
  "documentation_url": "https://www.adadiamonds.com/developers"
}
```

| Status | `error` | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | Bad parameter or body; the description says which |
| 401 | `unauthorized` | Authenticate and retry. `WWW-Authenticate: Bearer resource_metadata="https://www.adadiamonds.com/.well-known/oauth-protected-resource"` points at the RFC 9728 metadata, which names the authorization server |
| 403 | `insufficient_scope` | Authenticated but missing the named scope |
| 404 | `not_found` | Unknown resource or path; the description lists what to try instead |
| 429 | `rate_limited` | Wait `Retry-After` seconds |

## Versioning

`/api/v1` is current with no retirement date. Additive changes ship without a bump, so ignore unknown fields. Every response carries `X-API-Version: v1` and `Link: <https://www.adadiamonds.com/developers#versioning>; rel="deprecation-policy"`. Retirement, if ever, is signalled by a `Deprecation` header (RFC 9745) then a `Sunset` header (RFC 8594) at least 12 months later.

Catalog data is edge-cached for up to an hour. Diamond inventory is the volatile part; re-check a stone before quoting it.

## Markdown for every page

```bash
curl -H "Accept: text/markdown" https://www.adadiamonds.com/knowledge-base/cvd-lab-diamonds
curl https://www.adadiamonds.com/knowledge-base/cvd-lab-diamonds.md
```

Responses send `Vary: Accept` and a `Link: rel="alternate"` header pointing at the markdown twin.

## Etiquette

Identify your agent with a descriptive `User-Agent`. Bulk scraping of the HTML catalog is not permitted; these endpoints and `POST /exports` are the supported way to read it.

Official clients: `npx @ada-diamonds/cli` (Node 18+, no dependencies; also published unscoped as `adadiamonds-cli`), the `ada-diamonds` Python package (`pip install ada-diamonds`, https://pypi.org/project/ada-diamonds/), the `github.com/adadiamonds/adadiamonds-go` Go module (standard library only, Go 1.21+), and the `adadiamonds` Ruby gem (`gem install adadiamonds`, https://rubygems.org/gems/adadiamonds). Developer questions: it@adadiamonds.com.
