Files
dewemoji/api-how-it-works.md
2026-02-12 00:52:40 +07:00

8.5 KiB
Raw Blame History

Dewemoji API — How It Works

This document explains the current API surface in the rebuild app, including filters, headers, and request/response shapes.

Base URLs

  • Local: http://127.0.0.1:8000/v1
  • Staging: https://dewemoji.backoffice.biz.id/v1

Auth & headers

License key (optional for free, required for Pro)

You can send a license key in either header:

  • Authorization: Bearer YOUR_LICENSE_KEY (recommended)
  • X-License-Key: YOUR_LICENSE_KEY (also supported)

Optional headers

  • X-Account-Id: Optional usage association.
  • X-Dewemoji-Frontend: Optional frontend identifier (string).

Response headers youll see

  • X-Dewemoji-Tier: free or pro
  • X-Dewemoji-Plan: free or pro
  • Ratelimit headers on free tier (page=1 only):
    • X-RateLimit-Limit
    • X-RateLimit-Remaining
    • X-RateLimit-Reset
  • Caching:
    • ETag
    • Cache-Control

Endpoints

GET /emojis

Search emojis with filters.

Query params:

  • q (string): search query (also accepts query)
  • category (string): category name (e.g. Smileys & Emotion)
  • subcategory (string): subcategory name (will be slugified internally)
  • page (int, default 1)
  • limit (int):
    • Free tier: max 20
    • Pro tier: max 50

Behavior:

  • If q is empty, it returns all emojis (subject to pagination).
  • If q has multiple terms, all terms must match.
  • category must match the exact category label in the dataset.
  • subcategory is matched by slugifying both the request and dataset.
  • plan field in response will be free or pro.

Example:

curl -s "http://127.0.0.1:8000/v1/emojis?q=love&limit=5" | jq .

Response (shape):

{
  "items": [
    {
      "emoji": "😍",
      "name": "smiling face with heart-eyes",
      "slug": "smiling-face-with-heart-eyes",
      "category": "Smileys & Emotion",
      "subcategory": "face-affection",
      "supports_skin_tone": false,
      "summary": "...",
      "unified": "U+1F60D",
      "codepoints": ["1F60D"],
      "shortcodes": [":smiling-face-with-heart-eyes:", "..."],
      "aliases": [],
      "keywords_en": ["love", "heart", "..."],
      "keywords_id": ["cinta", "..."],
      "related": ["🥰", "🤩", "😘"],
      "intent_tags": ["love", "affection"]
    }
  ],
  "total": 2131,
  "page": 1,
  "limit": 20,
  "plan": "free"
}

GET /emoji/{slug} or /emoji?slug=...

Fetch detail for a specific emoji.

Example:

curl -s "http://127.0.0.1:8000/v1/emoji/grinning-face" | jq .

Errors:

  • 400 if slug is missing (error: missing_slug)
  • 404 if slug is not found (error: not_found)

GET /categories

Returns a map of category → list of subcategories.

Example:

curl -s "http://127.0.0.1:8000/v1/categories" | jq .

Response:

{
  "Smileys & Emotion": ["face-smiling", "face-affection", "..."],
  "People & Body": ["hand-fingers-open", "..."]
}

POST /license/verify

Validate a license key.

Headers:

  • Authorization: Bearer YOUR_LICENSE_KEY (or X-License-Key)

Example:

curl -s -X POST "http://127.0.0.1:8000/v1/license/verify" \
  -H "Authorization: Bearer YOUR_LICENSE_KEY" | jq .

Success response:

{
  "ok": true,
  "tier": "pro",
  "source": "gumroad|mayar|sandbox|...",
  "plan": "pro",
  "product_id": "…",
  "expires_at": null,
  "error": null
}

Errors:

  • 400 missing_key
  • 401 invalid_license

POST /license/activate

Activate a device or site session.

Body (JSON):

{
  "email": "you@example.com",
  "product": "extension|site",
  "device_id": "device-123"
}

Notes:

  • product=site does not require device_id.
  • Other products require device_id.

POST /license/deactivate

Deactivate a device.

Body (JSON):

{
  "product": "extension|site",
  "device_id": "device-123"
}

GET /health

Basic health check.

Response:

{ "ok": true, "time": "...", "app": "Dewemoji" }

GET /metrics-lite

Lightweight metrics (if enabled).

GET /metrics

Full metrics (requires token or allowed IP).

Extension verification (public, no login)

These endpoints allow verified extension installs to access public search without login.

Fallback (temporary):

  • If Verified Access token is not available, the server can accept X-Extension-Id as a soft allowlist signal. This is spoofable and should not be treated as a strong security boundary.

POST /extension/verify

Verifies X-Extension-Token by calling Google Instance ID API.

Headers:

  • X-Extension-Token: <token>

Response:

{ "ok": true, "verified": true }

GET /extension/search

Public search for verified extension installs only.

Headers:

  • X-Extension-Token: <token>

Example:

curl -s "http://127.0.0.1:8000/v1/extension/search?q=snail" \
  -H "X-Extension-Token: $EXT_TOKEN" | jq .

Errors:

  • 403 extension_unverified

Caching

The API uses ETag and returns 304 Not Modified if the client sends:

If-None-Match: "etag-value"

Example:

ETAG=$(curl -i "http://127.0.0.1:8000/v1/emojis?q=love&limit=5" | awk -F': ' '/^ETag:/ {print $2}' | tr -d '\r')
curl -i -H "If-None-Match: $ETAG" "http://127.0.0.1:8000/v1/emojis?q=love&limit=5" | head -n 1

Public access guard (whitelist + soft throttle)

Public endpoints (/v1/emojis, /v1/categories, /v1/emoji) are protected by a whitelist + hourly throttle.

Behavior:

  • If the request is whitelisted, it passes without throttling.
  • If not whitelisted:
    • If DEWEMOJI_PUBLIC_ENFORCE=true403 public_access_denied
    • Else → soft throttle with DEWEMOJI_PUBLIC_HOURLY_LIMIT (HTTP 429 public_rate_limited)

Whitelist rules:

  • Origin is in DEWEMOJI_PUBLIC_ORIGINS
  • Or X-Dewemoji-Frontend / User-Agent contains a configured extension ID

Key env vars:

DEWEMOJI_PUBLIC_ENFORCE=true|false
DEWEMOJI_PUBLIC_ORIGINS=https://dewemoji.com,https://www.dewemoji.com
DEWEMOJI_PUBLIC_HOURLY_LIMIT=5000
DEWEMOJI_EXTENSION_IDS=chrome-extension://...

Rate limit response example:

{
  "ok": false,
  "error": "public_rate_limited",
  "usage": {
    "used": 3,
    "limit": 3,
    "remaining": 0,
    "window": "hourly",
    "window_ends_at": "2026-02-08T14:00:00+00:00",
    "window_ends_at_unix": 1770559200
  }
}

Admin endpoints (token required)

All admin endpoints require:

X-Admin-Token: <DEWEMOJI_ADMIN_TOKEN>

Settings (feature flags / public access)

  • GET /v1/admin/settings
  • POST /v1/admin/settings

Example:

curl -s -X POST "http://127.0.0.1:8000/v1/admin/settings" \
  -H "X-Admin-Token: $DEWEMOJI_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "maintenance_enabled": false,
      "public_enforce": true,
      "public_hourly_limit": 5000,
      "public_origins": ["https://dewemoji.com","https://www.dewemoji.com"],
      "public_extension_ids": ["chrome-extension://yourid"]
    }
  }' | jq .

Subscriptions (admin grant/revoke)

  • GET /v1/admin/subscriptions
  • POST /v1/admin/subscription/grant
  • POST /v1/admin/subscription/revoke

Grant example:

curl -s -X POST "http://127.0.0.1:8000/v1/admin/subscription/grant" \
  -H "X-Admin-Token: $DEWEMOJI_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","plan":"personal","status":"active","provider":"admin"}' | jq .

Webhooks (PayPal)

  • POST /v1/paypal/webhook (logs + processes)
  • GET /v1/admin/webhooks
  • GET /v1/admin/webhooks/{id}
  • POST /v1/admin/webhooks/{id}/replay

Deduping:

  • PayPal id is stored as event_id. Duplicate events return { "ok": true, "duplicate": true }.

Note:

  • PayPal signature verification is not implemented yet (TODO before production).

Analytics

  • GET /v1/admin/analytics (counts for users/keywords/subscriptions/webhooks)

Security note: Origin/Referer are spoofable

The Origin / Referer headers are not a strong security boundary. They are used to reduce casual abuse only.

Options to harden public access:

  • Require API keys for unlimited access (personal users).
  • Rate limit at the edge (Cloudflare / Nginx).
  • Signed requests for extension (HMAC or shortlived tokens).
  • Shared secret header for internal services.
  • WAF rules for /v1/* endpoints.

Error payloads (common)

{ "ok": false, "error": "not_found" }

Other possible errors:

  • missing_slug
  • missing_key
  • invalid_license
  • daily_limit_reached
  • data_load_failed

Notes

  • Current dataset contains EN + ID keywords.
  • API reads from app/data/emojis.json (cache-first strategy).
  • Free tier limit is enforced on page=1 requests.