8.5 KiB
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 you’ll see
X-Dewemoji-Tier:freeorproX-Dewemoji-Plan:freeorpro- Rate‑limit headers on free tier (page=1 only):
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset
- Caching:
ETagCache-Control
Endpoints
GET /emojis
Search emojis with filters.
Query params:
q(string): search query (also acceptsquery)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
qis empty, it returns all emojis (subject to pagination). - If
qhas multiple terms, all terms must match. categorymust match the exact category label in the dataset.subcategoryis matched by slugifying both the request and dataset.planfield in response will befreeorpro.
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:
400if slug is missing (error: missing_slug)404if 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(orX-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:
400missing_key401invalid_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=sitedoes not requiredevice_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-Idas 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=true→403 public_access_denied - Else → soft throttle with
DEWEMOJI_PUBLIC_HOURLY_LIMIT(HTTP429 public_rate_limited)
- If
Whitelist rules:
Originis inDEWEMOJI_PUBLIC_ORIGINS- Or
X-Dewemoji-Frontend/User-Agentcontains 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/settingsPOST /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/subscriptionsPOST /v1/admin/subscription/grantPOST /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/webhooksGET /v1/admin/webhooks/{id}POST /v1/admin/webhooks/{id}/replay
Deduping:
- PayPal
idis stored asevent_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 short‑lived tokens).
- Shared secret header for internal services.
- WAF rules for
/v1/*endpoints.
Error payloads (common)
{ "ok": false, "error": "not_found" }
Other possible errors:
missing_slugmissing_keyinvalid_licensedaily_limit_reacheddata_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.