eekaamdocs
Dev portal ↗

Updated July 28, 2026

Eekaam App Developer API Reference

The Eekaam App Ecosystem lets third-party developers build apps that merchants install from the Eekaam marketplace. Once installed, an app authenticates with an OAuth-style access token and calls the Admin API to read or write a store's products, orders, customers, themes, and more — all scoped to the merchant that installed it.

This document is the canonical reference for every endpoint an app developer can call.


1. Concepts

TermMeaning
AppA piece of software built by a developer and listed on the Eekaam marketplace. Each app has a client_id and client_secret.
InstallA specific merchant's installation of an app. Each install carries its own access token and is locked to one tenant (store).
Tenant / ShopA merchant's store. All Admin API responses are automatically scoped to the installed tenant — you never pass tenant_id in a request.
ScopeA capability the app requested at install time (e.g. read_products, write_inventory). The API rejects calls outside granted scopes.
Access tokenAn opaque bearer token returned by the OAuth access_token exchange. Used as Authorization: Bearer <token> on every Admin API call.
Webhook subscriptionA URL the app registers to receive POST callbacks when events happen in the store. Signed with HMAC-SHA256.
Embedded session tokenA short-lived JWT issued by Eekaam that proves the calling merchant user is logged in. Apps can verify it server-side via the session/verify endpoint.

Hosts and base URLs

The platform is multi-tenanted by hostname. All API URLs in this document use the merchant store's host — for example, a merchant on acme.eekaam.com will hit https://acme.eekaam.com/admin/api/2026-05/.... The Admin API is also mirrored under /api/apps/admin/2026-05/... for compatibility, and the OAuth endpoints are mirrored under /api/admin/oauth/....

API version

The current Admin API version is 2026-05. It is embedded in the URL path:

https://{shop}.eekaam.com/admin/api/2026-05/{resource}

2. Registering an app

Apps are created from the developer portal (/dev/apps in the web app, backed by /api/developer/apps). When you create an app you get back:

  • client_id — public identifier (UUID-shaped)
  • client_secret — shown once, store it immediately
  • redirect_urls — at least one HTTPS URL the OAuth flow will redirect to
  • requested_scopes — the scope list merchants will be asked to approve

The developer-portal endpoints (auth: developer session cookie) are:

MethodPathPurpose
GET/api/developer/appsList your apps
POST/api/developer/appsCreate an app
GET/api/developer/apps/:idGet a specific app
PUT/api/developer/apps/:idUpdate app metadata / scopes / webhook definitions
POST/api/developer/apps/:id/credentials/rotateRotate client_secret
POST/api/developer/apps/:id/test-installInstall the app on one of your dev stores
POST/api/developer/apps/:id/submitSubmit app for marketplace review
GET/api/developer/apps/:id/webhooks/deliveriesList recent webhook delivery attempts
POST/api/developer/apps/:id/webhooks/deliveries/:delivery_id/replayManually replay a failed delivery
GET/api/developer/apps/billing/revenueYour aggregated app revenue

3. OAuth installation flow

Apps use a standard OAuth 2.0 authorization-code flow. There are two mirrored prefixes — pick one and stick with it:

  • /admin/oauth/...
  • /api/admin/oauth/...

3.1 Begin authorization

Redirect the merchant's browser to:

GET https://{shop}.eekaam.com/admin/oauth/authorize
  ?client_id={client_id}
  &redirect_uri={your_redirect_url}
  &state={opaque_csrf_token}

Eekaam validates the client_id, checks the app is approved, and verifies the redirect URI against the app's allow-list. If the merchant approves the install, the browser is redirected back to your redirect_uri with:

?code={authorization_code}
&state={same_state_you_sent}
&tenant={tenant_uuid}
&shop={shop_subdomain_or_domain}

Always verify the returned state matches what you sent. Authorization codes expire after 5 minutes and can be used once.

3.2 Exchange code for access token

POST https://{shop}.eekaam.com/admin/oauth/access_token
Content-Type: application/json

{
  "client_id":     "{client_id}",
  "client_secret": "{client_secret}",
  "code":          "{authorization_code}"
}

Response:

The token is stored hashed server-side; save it immediately because it cannot be retrieved later.

{
  "access_token": "shpat_...",
  "token_type":   "Bearer",
  "scope":        "read_products,write_inventory,read_orders"
}

3.3 Verify embedded session (optional, for embedded apps)

If your app runs inside an iframe in the merchant dashboard, Eekaam mints a short-lived JWT that proves the calling user is logged in. Verify it server-side before trusting the request:

POST https://{shop}.eekaam.com/admin/oauth/session/verify
Content-Type: application/json

{
  "client_id":     "{client_id}",
  "client_secret": "{client_secret}",
  "session_token": "{jwt_from_the_iframe}"
}

Response includes app_id, install_id, tenant_id, user_id, shop, scopes, and expires_at.


4. Authentication

Every Admin API request must include the bearer access token:

Authorization: Bearer shpat_xxxxxxxxxxxxxxxxxxxx

On every request, the server:

  1. Looks up the install by hashed token.
  2. Confirms the install is active.
  3. Confirms the install was granted the scope required by the route.

Failures return:

  • 401 Unauthorized — missing/invalid token, install uninstalled
  • 403 Forbidden — scope not granted, body includes "scope": "<required>"

Tenant ID is always derived from the install record; you cannot escape your tenant by sending a different tenant_id.


5. Rate limiting

The Admin API is rate-limited to 40 requests per second per access token.

When you exceed the limit Eekaam responds with:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{ "error": "API rate limit exceeded", "retry_after": "1s" }

Back off and retry. Bursts within the budget are fine.


6. Scope reference

The exact scope strings the API recognises:

ScopeGrants
read_settingsGET /shop
read_webhooksList webhook subscriptions
write_webhooksCreate / delete webhook subscriptions
read_productsList / get products
write_productsCreate / update / delete products
read_ordersList / get orders
read_customersList / get customers (PII subset)
read_inventoryList inventory levels
write_inventorySet or adjust inventory
read_filesList / get media files
write_filesUpload / update / delete media files
read_themesList themes and theme assets
write_themesUpsert theme assets
read_discountsList / get discounts
write_discountsCreate / update / delete discounts
read_marketsList / get markets
write_marketsCreate / update markets
read_metafieldsList metafield definitions and values
write_metafieldsUpsert / delete metafield values
read_metaobjectsList metaobject definitions and entries
write_metaobjectsCreate / update metaobject entries
read_fulfillmentsList fulfillments on an order
write_fulfillmentsCreate a fulfillment on an order
read_analyticsAnalytics summary & full breakdown

Request only the scopes you need — fewer scopes means higher merchant install conversion and a smaller blast radius if the token leaks.


7. Admin API reference

Base URL: https://{shop}.eekaam.com/admin/api/2026-05 (or /api/apps/admin/2026-05). All endpoints require Authorization: Bearer.

Pagination convention for list endpoints:

  • Query params: page (default 1), limit (default 50, max 250)
  • Response: { "<resource>": [...], "pagination": { "page", "limit", "total", "pages" } }

7.1 Shop

MethodPathScopeResponse
GET/shopread_settings{ "shop": { id, name, domain, subdomain, email, plan, status, created_at, updated_at } }

7.2 Products

MethodPathScope
GET/products?page=&limit=&search=&status=read_products
GET/products/:idread_products
POST/productswrite_products
PUT/products/:idwrite_products
DELETE/products/:idwrite_products

POST body:

PUT accepts the same fields, all optional — only fields you send are updated. List + get responses preload Images and Variants.

{
  "title":        "Required",
  "description":  "",
  "price":        0,
  "sku":          "",
  "quantity":     0,
  "status":       "draft | active | archived",
  "is_published": false
}

7.3 Orders (read-only)

MethodPathScope
GET/orders?page=&limit=&fulfillment_status=&payment_status=read_orders
GET/orders/:idread_orders

Preloads Items and ShippingAddress.

7.4 Customers (read-only)

MethodPathScope
GET/customers?page=&limit=&search=read_customers
GET/customers/:idread_customers

Returns a PII-limited projection: id, tenant_id, first_name, last_name, email, phone, accepts_marketing, created_at, updated_at.

7.5 Inventory

MethodPathScope
GET/inventory_levels?product_id=&variant_id=read_inventory
POST/inventory_levels/setwrite_inventory
POST/inventory_levels/adjustwrite_inventory

Set body:

Adjust body:

Either product_id or variant_id is required. Variant-level updates always win when both are provided.

{ "product_id": "uuid", "variant_id": "uuid|optional", "available": 25,
  "reason": "manual", "reference_id": "po-1234" }
{ "variant_id": "uuid", "adjustment": -3, "reason": "shrinkage" }

7.6 Files (media library)

MethodPathScope
GET/files?page=&limit=&search=read_files
GET/files/:idread_files
POST/fileswrite_files
PUT/files/:idwrite_files
DELETE/files/:idwrite_files

POST body (you upload the asset to your own CDN/S3 first and register the URL here):

{ "filename": "hero.jpg", "url": "https://...", "content_type": "image/jpeg",
  "size": 184213, "alt_text": "Hero banner" }

7.7 Themes

MethodPathScope
GET/themesread_themes
GET/themes/:id/assetsread_themes
GET/themes/:id/assets/*read_themes
PUT/themes/:id/assets/*write_themes

The asset key (the * wildcard) must start with assets/ and may not contain ... PUT body:

{ "content": "body { color: red; }", "mime_type": "text/css" }

7.8 Discounts

MethodPathScope
GET/discountsread_discounts
GET/discounts/:idread_discounts
POST/discountswrite_discounts
PUT/discounts/:idwrite_discounts
DELETE/discounts/:idwrite_discounts

Body fields match the Discount model — title is required; method defaults to "code"; starts_at defaults to now if omitted.

7.9 Markets

MethodPathScope
GET/marketsread_markets
GET/markets/:idread_markets
POST/marketswrite_markets
PUT/markets/:idwrite_markets

Responses preload ShippingZones and PaymentConfigs. On create, name is required; slug is auto-generated from name if omitted, and currency_code defaults to PKR.

7.10 Metafields

MethodPathScope
GET/metafield_definitions?owner_type=read_metafields
GET/metafields?owner_type=&owner_id=read_metafields
PUT/metafieldswrite_metafields
DELETE/metafields/:idwrite_metafields

Upsert body (definition must already exist):

{
  "definition_id": "uuid",
  "owner_type":    "product | variant | customer | order | ...",
  "owner_id":      "uuid",
  "namespace":     "my_app",
  "key":           "warranty_months",
  "value_json":    "12"
}

7.11 Metaobjects

MethodPathScope
GET/metaobject_definitionsread_metaobjects
GET/metaobjects?definition_id=read_metaobjects
POST/metaobjectswrite_metaobjects
PUT/metaobjects/:idwrite_metaobjects

Create body requires definition_id and handle; status defaults to active.

7.12 Fulfillments

MethodPathScope
GET/orders/:id/fulfillmentsread_fulfillments
POST/orders/:id/fulfillmentswrite_fulfillments

POST body:

{ "status": "fulfilled | shipped | delivered | cancelled | unfulfilled",
  "tracking_number": "1Z...",
  "shipping_method": "FedEx Ground",
  "notify_customer": true }

7.13 Analytics

MethodPathScopeNotes
GET/analytics/summary?days=30read_analyticsdays clamped to [1, 365], default 30
GET/analytics?days=30read_analyticsFull breakdown

7.14 Webhook subscriptions (managed by the app)

MethodPathScope
GET/webhooksread_webhooks
POST/webhookswrite_webhooks
DELETE/webhooks/:idwrite_webhooks

POST body:

  • Only format: "json" is currently supported.
  • callback_url must be a valid absolute URL.
  • The response includes a one-time secret and a secret_notice — store it, you will use it to verify HMAC signatures.

DELETE does a soft disable (sets status to disabled); the subscription record is retained for delivery history.

See dev/apps/{id}/webhooks/docs in the dashboard for the full retry-policy and HMAC verification reference — the next section is a condensed mirror.


{ "topic": "orders/create", "callback_url": "https://app.example.com/hooks", "format": "json" }

8. Webhooks

8.1 Supported topics

app/uninstalled       — the merchant uninstalled your app (always delivered, even after delete)
orders/create         — a new order was placed
orders/updated        — an order changed (fulfillment, status, line items, etc.)
products/create
products/update
products/delete
customers/create
customers/update

Trying to subscribe to anything else returns 400 Unsupported webhook topic.

8.2 Delivery payload

Every webhook POST body has this shape:

{
  "id":         "evt_...",
  "topic":      "orders/create",
  "app_id":     "uuid",
  "tenant_id":  "uuid",
  "created_at": "2026-05-22T12:00:00Z",
  "data":       { /* topic-specific payload */ }
}

8.3 Headers Eekaam sends

HeaderValue
Content-Typeapplication/json
User-AgentEekaam-Webhooks/1.0
X-Eekaam-Topice.g. orders/create
X-Eekaam-Webhook-Idunique event ID (use it to dedupe)
X-Eekaam-Hmac-Sha256HMAC-SHA256 of the raw body, hex-encoded

8.4 Verifying the signature

Compute HMAC-SHA256(raw_body, subscription_secret), hex-encode it, and compare against X-Eekaam-Hmac-Sha256 with a timing-safe comparison. Always sign the raw bytes before any JSON parsing or re-serialisation.

import crypto from "node:crypto";

export function verifyEekaamWebhook(rawBody, secret, headerSignature) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(headerSignature, "hex"),
  );
}

8.5 Retry policy

If the endpoint responds with a non-2xx status, or fails to respond within 10 seconds, Eekaam retries with exponential backoff up to 8 attempts over roughly a 16-hour window. Any 2xx response — including an empty body — counts as success. Payloads are retained for 72 hours; failed deliveries can be manually replayed from the dev portal's deliveries page.

A sustained-high failure rate auto-disables the subscription and fires the app/webhook_subscription_disabled event.


9. Error format

All errors return JSON of the form:

403 Forbidden responses for missing scopes additionally include "scope": "<scope-name>".

Standard HTTP status codes apply:

CodeMeaning
400Malformed JSON, missing required field, invalid UUID, unsupported value
401Missing/invalid bearer token
403Token valid but lacks the required scope
404Resource does not exist in this tenant
409Conflict (e.g. handle already exists, re-enable embed on uninstalled app)
429Rate limit exceeded
500Server error — safe to retry with backoff

{ "error": "Human-readable message" }

10. Quick start — a complete install + first call

# 1. Send the merchant to the authorize URL
open "https://acme.eekaam.com/admin/oauth/authorize?client_id=APP_ID&redirect_uri=https://app.example.com/install&state=$(uuidgen)"

# 2. After approval, Eekaam redirects to your URL with ?code=...&state=...

# 3. Exchange the code for an access token
curl -X POST https://acme.eekaam.com/admin/oauth/access_token \
  -H 'Content-Type: application/json' \
  -d '{"client_id":"APP_ID","client_secret":"APP_SECRET","code":"AUTH_CODE"}'

# {"access_token":"shpat_...","token_type":"Bearer","scope":"read_products"}

# 4. Call the Admin API
curl https://acme.eekaam.com/admin/api/2026-05/products \
  -H 'Authorization: Bearer shpat_...'

# 5. Subscribe to a webhook (need write_webhooks scope)
curl -X POST https://acme.eekaam.com/admin/api/2026-05/webhooks \
  -H 'Authorization: Bearer shpat_...' \
  -H 'Content-Type: application/json' \
  -d '{"topic":"orders/create","callback_url":"https://app.example.com/hooks/orders"}'

# Response includes a one-time "secret" — store it, it won't be shown again.