Skip to main content

Getting started

Everything you need to make your first authenticated API call — in about 5 minutes.

tip

If you already have an account, jump straight to Step 2 — Issue an API key.


Prerequisites

You'll need:

  • A Retail Digitals account (free to create — sign up →)
  • A tool to send HTTP requests. Any of these work:
    • Terminal with curl (built into macOS / Linux, available on Windows 10+)
    • Postman or Insomnia (both free)
    • Your language runtime — Node.js, PHP, Python, whatever you're building in
  • A few credits in your balance. New accounts start with 10 free credits — enough to make a few requests to try things out (the exact number depends on how you shape each call — see Cost model). Top up when you're ready to build production traffic.

Step 1 — Create an account

Head to images.retaildigitals.com/register. It's the same account you'd use for the web UI order flow — the API just reuses your login. Your credit balance is shared between UI orders and API usage.

note

The 10 free credits arrive automatically. Check your balance any time at images.retaildigitals.com/billing.


Step 2 — Issue an API key

Once logged in, navigate to Account → API Keys (or go direct to images.retaildigitals.com/account/api-keys).

Click "Create new API client", give it a name that'll help you identify it later (e.g. "Website production", "POS staging"), and hit Create.

You'll see a screen like this:

┌────────────────────────────────────────────────────────────────┐
│ ⚠ This is the only time you'll see the API key. │
│ Copy it now and store it somewhere safe. │
│ │
│ Client ID: rd_client_01h8y3g7z8mnpqrsw │
│ [📋] │
│ │
│ API Key: ••••••••••••••••••••••••••••••••••••• [👁] │
│ [📋] │
│ │
│ [ Done ] │
└────────────────────────────────────────────────────────────────┘
  • client_id is safe to log, embed in URLs, put in your version control. It's your public identifier.
  • api_key is a secret. Click the 👁 to reveal, then 📋 to copy. You cannot retrieve it later — if you lose it, come back and rotate it (which invalidates the old one).

Save both somewhere secure. Environment variables are ideal:

# Add these to your .env (never commit to git!)
RD_CLIENT_ID=rd_client_01h8y3g7z8mnpqrsw
RD_API_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
warning

Never commit your API key. Never paste it into browser DevTools or a public GitHub issue. If a key leaks, rotate it immediately at Account → API Keys → Rotate — the old one is dead within 5 seconds.


Step 3 — Exchange credentials for an access token

The API uses a two-step auth: your long-lived client_id + api_key is used once to obtain a short-lived access token (1 hour) plus a refresh token (90 days). Every subsequent API call uses only the access token.

Send your first request:

curl -X POST https://images.retaildigitals.com/api/v1/auth/token \
-H "Content-Type: application/json" \
-d "{\"client_id\":\"$RD_CLIENT_ID\",\"api_key\":\"$RD_API_KEY\"}"

You'll get back:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "rt_01h8y3g7z8mnpqrsw...",
"token_type": "Bearer",
"expires_in": 3600
}
  • access_token — attach as Authorization: Bearer <token> on every subsequent call. Lives for 3600 seconds (1 hour).
  • refresh_token — save it. When your access token expires, exchange the refresh token for a new access token without needing your original credentials. See Authentication → Refresh flow for details.

Save the access token in your environment for the rest of this guide. Paste the value from the access_token field of the response above — not the whole JSON blob (that's the #1 first-request mistake):

# One-liner: authenticate + extract in one shot
ACCESS_TOKEN=$(curl -sX POST https://images.retaildigitals.com/api/v1/auth/token \
-H "Content-Type: application/json" \
-d "{\"client_id\":\"$RD_CLIENT_ID\",\"api_key\":\"$RD_API_KEY\"}" \
| jq -r .access_token)
echo "$ACCESS_TOKEN" # sanity check

Quick sanity check — did the token work?

Before spending credits, verify with the free /balance endpoint:

curl -s https://images.retaildigitals.com/api/v1/balance \
-H "Authorization: Bearer $ACCESS_TOKEN"

You should get back a JSON body with your credit_balance. If you get a 401, you've either pasted the JSON blob instead of just the token value, or the token has already expired (unlikely — they live 1 hour).


Step 4 — Make your first product request

Now the fun part. Fetch a real product:

curl https://images.retaildigitals.com/api/v1/products/017000161563 \
-H "Authorization: Bearer $ACCESS_TOKEN"

Response:

{
"barcode": "017000161563",
"image_meta": {
"front": { "photo_date": "2026-06-04 15:49:50", "resolution": "1000x1000", "ownership": "Studio A" },
"front_clean": { "photo_date": "2026-06-04 15:49:50", "resolution": "1000x1000", "ownership": "Studio A" },
"back": null,
"back_clean": null
},
"product_meta": {
"brand": "Example Brand",
"product_name": "Example Cereal",
"category": "cereal",
"bracha": "mezonos",
"kosher_for_passover": false,
"gluten_free": false,
"vegan": true,
"hechshers": [
{ "symbol": "OU", "org": "Orthodox Union", "dairy_meat_pareve": "pareve" }
],
"nutrition": {
"serving_size": "1 cup (28g)",
"calories": 100,
"sodium": "200mg",
"total_carbohydrate": "24g"
},
"ingredients_text": "Milled corn, sugar, malt flavoring...",
"allergens": ["wheat"]
},
"images": {
"front": "https://images.retaildigitals.com/api/v1/image/eyJi...(base64url).(HMAC)",
"front_clean": "https://images.retaildigitals.com/api/v1/image/eyJi..."
},
"meta": {
"request_id": "req_01H8Y3G7Z8mnpqrsw",
"credits_debited": 2.6,
"credits_remaining": 7.4
}
}

🎉 You just made your first API call.

The images.front URL is signed, valid for 15 minutes, bound to the IP that requested it, and single-use (the first successful GET consumes it).

That has practical implications you need to design around:

  • curl from your terminal, then pasting into a browser — usually won't work. Your browser is on a different route (Wi-Fi vs VPN vs mobile hotspot) → 403 signed_url_ip_mismatch.
  • Embedding directly in an <img src> in an HTML page — works only when the same client (usually the browser) makes both the API call and fetches the image, AND you haven't fetched the URL from anywhere else first.
  • Server-side proxying — the safest pattern for web apps. Your server fetches the API, gets the signed URL, downloads the image bytes, caches them, and serves to end users from its own URL.
  • You want a URL you can embed without server-side plumbing — use the 302-redirect endpoint instead: GET /api/v1/products/{barcode}/image?variant=front returns a fresh signed URL bound to the caller's IP each request, so the URL you embed doesn't need to be pre-fetched.

Note meta.credits_debited in the response — that's the "everything, please" default cost, and it's usually more than you need. The next step shows the two levers to bring it down. Full cost model is on the Pricing page.


Step 5 — Fetch just what you need (control the metadata scopes)

The first cost lever is include=. Omitting it charges you both metadata scopes (product_meta and image_meta) whether your app uses them or not. If you only need images, ask only for images:

curl "https://images.retaildigitals.com/api/v1/products/017000161563?include=images" \
-H "Authorization: Bearer $ACCESS_TOKEN"

Response (only images; no product_meta or image_meta blocks):

{
"barcode": "017000161563",
"images": {
"front": "https://images.retaildigitals.com/api/v1/image/eyJi...",
"front_clean": "https://images.retaildigitals.com/api/v1/image/eyJi..."
},
"meta": { "credits_debited": "...", "credits_remaining": "..." }
}

Check meta.credits_debited on the response — it dropped by exactly 2 × per_scope_rate compared to the Step 4 default. That's the metadata-cost saving.

Available include values: image_meta, product_meta, images. Combine with commas, or omit for all three (default — costs more).

Only pay for the image variants you actually use (the second cost lever)

Even with include=images, the API still returns URLs for every available variant of the barcode (up to 4) and charges you per URL. If your app only ever displays the front image, use variants= to filter:

curl "https://images.retaildigitals.com/api/v1/products/017000161563?include=images&variants=front" \
-H "Authorization: Bearer $ACCESS_TOKEN"

Response — same shape, but only the variants you asked for get a signed URL. The others come back as null:

{
"barcode": "017000161563",
"images": {
"front": "https://images.retaildigitals.com/api/v1/image/eyJi...",
"back": null,
"front_clean": null,
"back_clean": null
},
"meta": { "credits_debited": "...", "credits_remaining": "..." }
}

Available values: front, back, front_clean, back_clean — comma-separated. front_clean / back_clean are the transparent-background PNGs. Requesting a variant that doesn't exist for the barcode returns null for that slot and doesn't cost anything. Anything outside these four values returns 400 bad_request.

Set BOTH parameters, or you'll still overpay

Sending variants=front without include=images still charges you the two default metadata scopes on top of the one image (~2 × per_scope + 1 × per_image, not 1 × per_image). You'll see this in meta.credits_debited — it'll be higher than you expected.

The minimum-cost image fetch is the combination ?include=images&variants=front.

This trap was the biggest source of surprise overspend in our first customer integration audit — see the Pricing page for the full formula and cheapest-pattern ladder.

Pro tip: If you only need to know whether a barcode exists — use HEAD for 0.05 credits instead of a full GET:

curl -I https://images.retaildigitals.com/api/v1/products/017000161563 \
-H "Authorization: Bearer $ACCESS_TOKEN"

Returns 200 OK if the barcode exists, 404 Not Found if it doesn't. No response body, no metadata cost.


Step 6 — Check your credit balance (free)

Every response includes your remaining credits in the meta block, but you can also fetch just the balance any time — great for dashboards, low-credit alerts, or a sanity-check before an expensive call.

curl https://images.retaildigitals.com/api/v1/balance \
-H "Authorization: Bearer $ACCESS_TOKEN"

Response:

{
"client_id": "rd_client_01h8y3g7z8mnpqrsw",
"credit_balance": 500.00,
"tier": "standard",
"meta": {
"request_id": "req_01H8Y3G7Z8mnpqrsw",
"credits_debited": 0,
"credits_remaining": 500.00,
"response_generated": "2026-07-02T16:30:00-04:00"
}
}

Prefer the full account snapshot — with rate-limit budget, usage-to-date, and scope info — via GET /account:

curl https://images.retaildigitals.com/api/v1/account \
-H "Authorization: Bearer $ACCESS_TOKEN"

Response:

{
"client_id": "rd_client_01h8y3g7z8mnpqrsw",
"client_name": "Prod POS backend",
"tier": "standard",
"credit_balance": 500.00,
"usage": {
"requests_today": 342,
"requests_this_month": 8421,
"credits_spent_today": 12.60,
"credits_spent_this_month": 421.30
},
"rate_limits": {
"requests_per_minute": 60,
"requests_per_day": 10000,
"remaining_this_minute": 58,
"remaining_today": 9658
},
"meta": { "request_id": "req_01H8Y3G7Z8...", "credits_debited": 0, "credits_remaining": 500.00 }
}

Both endpoints are free — 0 credits debited — and don't count against your daily quota differently than any other request. Safe to poll every few seconds from a status widget.

Where balance shows up:

  • credit_balance on every /account and /balance response — current pool
  • meta.credits_remaining on every response (any endpoint) — same value, so you can update your local balance tracker after every call without extra requests
  • On the site: https://images.retaildigitals.com/account/api-keys shows total credits across all your active API clients + spent-today and this-month counters

What's next

  • Guides → Recipes for caching, bulk sync, POS integration, error retry strategies
  • Authentication → Full token lifecycle, refresh flow, credential rotation, IP allowlisting
  • API reference → Every endpoint documented — parameters, response schemas, error codes
  • SDKs → Official libraries for JavaScript & PHP; community libraries for Python, Go
  • Rate limits → Per-tier limits, how to read X-RateLimit-* headers
  • Error handling → All error codes explained, retry strategy for each

Troubleshooting your first request

401 Unauthorized — "Invalid client credentials"
  • Double-check that client_id and api_key are both correct. The api_key is case-sensitive.
  • If you've rotated the key since issuance, the old one is dead. Use the current one from Account → API Keys.
  • If you can't find your api_key, rotate to get a new one.
401 Unauthorized — "Access token expired"

Access tokens live 1 hour. Use your refresh_token to get a new one — see Authentication → Refresh flow. Or exchange your client_id + api_key again.

402 Payment Required — "Insufficient credits"

Your balance is empty. New accounts get 10 free credits; after that, top up at Billing.

404 Not Found — "Barcode not found"

The barcode isn't in our catalog. Try one of these known-good test barcodes:

  • 017000161563 — Example Brand Cereal
  • 030772095355 — a national household-goods brand
  • 080878194964 — a national snack brand

Or browse the full catalog with GET /api/v1/products.

429 Too Many Requests

You're hitting rate limits. Read the Retry-After header (in seconds) and back off. See Rate limits for per-tier limits. Enterprise-tier customers can request higher limits — email api@retaildigitals.com.

Signed image URL returns 410 Gone

Signed URLs are single-use. After the first successful GET, the URL is dead. This is a security feature — even if the URL leaks, it can't be used twice. Get a fresh signed URL by calling GET /api/v1/products/{barcode}?include=images again (only costs the delivery credits if you re-download the image).

Signed image URL returns 403 Forbidden

Signed URLs are IP-bound — they only work from the exact same IP that requested them. This is one of the load-bearing security layers and cannot be disabled — it's the trace-back-to-you protection if URLs ever leak. Fix the caller-side pattern:

  • Fetch the image on the same server that generated the URL, then re-serve it from your own origin (recommended for most web apps).
  • Use GET /api/v1/products/{barcode}/image?variant=front — it returns a 302 Location: to a freshly-issued signed URL bound to the caller's IP, so you can embed the redirect URL directly in <img src> without the browser first fetching then re-fetching.
  • If your topology genuinely requires CDN-topology signed URLs (Enterprise-tier), contact api@retaildigitals.com for the CDN mode design.

Still stuck? Email api@retaildigitals.com — a human writes back within one business day.