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

356 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```bash
curl -s "http://127.0.0.1:8000/v1/emojis?q=love&limit=5" | jq .
```
Response (shape):
```json
{
"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:
```bash
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:
```bash
curl -s "http://127.0.0.1:8000/v1/categories" | jq .
```
Response:
```json
{
"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:
```bash
curl -s -X POST "http://127.0.0.1:8000/v1/license/verify" \
-H "Authorization: Bearer YOUR_LICENSE_KEY" | jq .
```
Success response:
```json
{
"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):
```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):
```json
{
"product": "extension|site",
"device_id": "device-123"
}
```
### GET `/health`
Basic health check.
Response:
```json
{ "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:
```json
{ "ok": true, "verified": true }
```
### GET `/extension/search`
Public search for verified extension installs only.
Headers:
- `X-Extension-Token: <token>`
Example:
```bash
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:
```bash
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` (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:
```json
{
"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:
```bash
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:
```bash
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)
```json
{ "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.