# API Reference Base URL (production): `https://shaileshrv1432-meme-backend.hf.space` Local: `http://localhost:8000` Interactive docs are served live by the running app: - **RapiDoc** โ€” `/docs` - **ReDoc** โ€” `/redoc` ## Conventions - **Auth**: endpoints marked ๐Ÿ”’ require a header `Authorization: Bearer `. - **Admin**: endpoints marked ๐Ÿ›ก require header `X-Admin-Secret: ` and return `404` when `APP_ENV=production`. - **Body type**: most write endpoints take `application/x-www-form-urlencoded` (`Form(...)`). A few take JSON (`Body(...)`) โ€” noted per endpoint. - **Tokens**: `access_token` (short-lived, `ACCESS_TTL_SECONDS`) + `refresh_token` (long-lived, `REFRESH_TTL_SECONDS`). Refresh via `POST /auth/refresh`. --- ## Auth โ€” `/auth` ### `POST /auth/request-otp` Send an OTP to a phone number (creates the user if new). - Form: `phone` (E.164, e.g. `+9172048...`) - 200: `{ "message": "OTP sent" }` - 502: SMS provider send failed (invalid key / no wallet balance / KYC incomplete). With `OTP_DEBUG=true` the 502 detail contains the raw provider error. - Note: if `FAST2SMS_API_KEY` is unset, no SMS is sent โ€” the OTP is printed to the server logs (`[DEV SMS] to=โ€ฆ otp=โ€ฆ`) so login still works in dev. ### `GET /auth/sms-check` ๐Ÿ›ก Diagnose OTP/SMS delivery without a real login. Admin secret via header `X-Admin-Secret` **or** `?secret=` (browser-friendly). - Query: `to` (optional 10-digit number โ€” if given, performs a **real** test send) - 200: `{ fast2sms_key_present, key_prefix, mode, verdict, provider_response? | provider_error?, hint }` - `verdict`: `no_provider` (key unset) ยท `configured_not_tested` (no `to`) ยท `ok` ยท `send_failed` ### `POST /auth/verify-otp` Verify an OTP and receive tokens. - Form: `phone`, `otp` - 200: `{ access_token, refresh_token, user: {...} }` - 400: no user / no OTP / expired / invalid - Guest: if `GUEST_MODE_ENABLED=true` and `phone` == `GUEST_PHONE_NUMBER` with `otp` == `GUEST_OTP_CODE`, returns a guest session (`account_type: "guest"`). ### `POST /auth/set-pin` Set a login PIN for a phone user. - Form: `phone`, `pin` (4โ€“6 digits) - 200: `{ "message": "PIN set" }` - 422: PIN not 4โ€“6 digits ### `POST /auth/login-pin` Login with phone + PIN. - Form: `phone`, `pin` - 200: `{ access_token, refresh_token }` - 401: invalid PIN ยท 400: no user / PIN not set ### `POST /auth/refresh` Exchange a refresh token for a fresh access token. - Form: `refresh_token` - 200: `{ access_token }` ยท 401: invalid/expired ### `POST /auth/google` Verify a Google ID token and issue app tokens. - Form: `id_token` (Google ID token from the app) - 200: `{ access_token, refresh_token, user: {...} }` - 503: `GOOGLE_WEB_CLIENT_ID` not set on the server ยท 401: invalid token - Users are keyed internally by `google:`. ### `GET /auth/me` ๐Ÿ”’ Current user. - 200: `{ id, phone_e164, name, email, avatar_url, created_at, is_active }` ### `PATCH /auth/update-profile` ๐Ÿ”’ Update profile fields (form-encoded). - Form (all optional): `name`, `email`, `avatar_url` - 200: updated user object ### `DELETE /auth/delete-account` ๐Ÿ”’ Soft-delete (deactivate) the current account. - Form: `confirm` (bool, must be true) - 200: `{ "message": "Account deactivated" }` ### `POST /auth/debug-token` *(dev only)* Decode a JWT without verifying signature. `404` in production. --- ## Memes โ€” `/` ### `POST /upload` ๐Ÿ”’ Upload an image; stores it and returns initial GIF suggestions. - Multipart: `image` (file), `mood` (optional) - 200: `{ status, uuid, object_key, url, mood, ...suggestions }` ### `POST /suggest` ๐Ÿ”’ Re-run suggestions for an already-uploaded image. - Form: `image_object_key`, `mood` (optional) - 200: `{ status, object_key, mood, ...suggestions }` ยท 404: key not found ### `POST /compose` ๐Ÿ”’ Compose a meme from form fields (single overlay). - Form: `image_object_key`, `gif_url` | `gif_object_key`, `x`, `y`, `width`, โ€ฆ (overlay placement) ### `POST /compose/layout?output_format=gif|png|mp4` ๐Ÿ”’ Compose from a full JSON layout (floating images **and text** over a background). This is what the app's editor uses. - JSON body: `image_dimensions` = ```jsonc { "bgImage": { "source": "", "width": 0, "height": 0, "applyCoverBlurAndContainImageFilter": false }, "floatingImages": [ { "file_url": "...", "width": 200, "relativeX": 0, "relativeY": 0, "rotation": 0, "z": 0 } ], "textOverlays": [ { "text": "LOL", "fontSize": 32, "color": "#FFFFFF", "strokeColor": "#000000", "width": 240, "height": 60, "relativeX": 0, "relativeY": 0, "rotation": 0, "z": 1 } ] } ``` - Query/body: `output_format` (default `gif`) - 200: `{ object_key, url, ... }` - **Layering**: every overlay (image or text) may carry a `z` (integer). All overlays are composited in ascending `z`, so higher `z` = on top. Omitting `z` falls back to input order (images first, then text) โ€” fully backward compatible. - **Rotation**: `rotation` (degrees, clockwise) rotates an overlay about its centre; `relativeX/Y` stay the un-rotated box top-left. Defaults to `0`. - **Text**: rendered server-side (Pillow) as bold, stroked meme text; `color`/`strokeColor` are hex, `width` enables word-wrap, `width`+`height` define the box the text is centred in. - Tip: pass image overlay `width` only โ€” the backend preserves the source aspect ratio (avoids stretched GIFs). ### `GET /memes?limit&offset` ๐Ÿ”’ List the current user's generated memes (latest first). - 200: `{ items: [{ id, object_key, content_type, created_at, presigned_url, permanent_url }], count, offset, limit }` ### `GET /memes/presign?object_key=โ€ฆ` ๐Ÿ”’ Get a 10-minute presigned URL for one owned meme. ### `GET /memes/{object_key}` ๐Ÿ”’ Stream the meme bytes through the API (permanent proxy URL, cached 1 year). --- ## Media โ€” `/media` ### `POST /media/url` ๐Ÿ”’ Presign any media object (JSON body). - Body: `object_key`, `expires_seconds` (default 600) - 200: `{ object_key, url, expires_seconds }` ### `POST /media/permanent-url` ๐Ÿ”’ Get the permanent proxy URL for an owned media object. - Body: `object_key` - 200: `{ object_key, permanent_url, type: "proxy", expires: "never" }` --- ## Profile โ€” `/profile` ### `GET /profile` ๐Ÿ”’ Current user's profile (from Postgres). ### `PUT /profile` ๐Ÿ”’ Update name/email/avatar (JSON body). - Body (optional): `name`, `email`, `avatar_url` - 200: `{ "message": "Profile updated" }` --- ## Subscriptions โ€” `/subscriptions` > Requires `RAZORPAY_KEY_ID` + `RAZORPAY_KEY_SECRET` on the server. Plans (paise/INR): `weekly` 5000 / 7d, `monthly` 14000 / 30d, `yearly` 80000 / 365d. ### `POST /subscriptions/order` ๐Ÿ”’ Create a Razorpay order. - Form: `plan` (`weekly|monthly|yearly`) - 200: `{ order_id, amount, currency, key_id, plan }` - 400: unknown plan ยท 503: payments not configured / keys rejected (auth) ยท 502: order creation failed - With `PAYMENTS_DEBUG=true`, the 502 detail contains the raw Razorpay error (for debugging only). ### `POST /subscriptions/verify` ๐Ÿ”’ Verify the payment signature, activate the plan, and record a `payments` row (best-effort). - Form: `razorpay_order_id`, `razorpay_payment_id`, `razorpay_signature`, `plan` - 200: `{ status: "active", plan, expires_at }` - 400: verification failed ### `GET /subscriptions/status` ๐Ÿ”’ Current subscription (auto-expires past the end date). - 200: `{ plan, status, active, expires_at, is_premium, usage: { daily_meme_limit, daily_memes_used, daily_memes_remaining, premium_only_formats } }` ### `GET /subscriptions/receipt` ๐Ÿ”’ Most recent payment, shaped for the receipt UI. - 200: `{ id, plan, plan_label, amount, amount_display, currency, status, method, payment_id, order_id, paid_at, expires_at }` - 404: no payment on record (client falls back to plan-derived data) ### `GET /subscriptions/payments` ๐Ÿ”’ Full payment/transaction history, newest first. - 200: `{ payments: [ , โ€ฆ ] }` (empty list if none / table absent) ### `GET /subscriptions/receipt/{payment_id}.pdf` ๐Ÿ”’ Download a specific payment's receipt as a PDF (generated with Pillow). Scoped to the owner. - 200: `application/pdf` (Content-Disposition attachment) - 404: receipt not found / not owned ### `GET /subscriptions/config-check` ๐Ÿ›ก Diagnose Razorpay config without a checkout. Header `X-Admin-Secret`. - 200: `{ key_id_present, key_secret_present, key_mode, key_id_prefix, secret_length, had_whitespace_or_quotes, razorpay_lib_installed, verdict, probe_order_id?, probe_error?, hint }` - `?probe=false` skips the live โ‚น1 test order. --- ## Admin โ€” `/admin` ๐Ÿ›ก *(404 in production)* | Method | Path | Purpose | |---|---|---| | POST | `/admin/seed-demo-user` | Create a demo user (phone, pin, otp, issue_tokens) | | POST | `/admin/minio-diagnose` | Check object-storage config | | POST | `/admin/test-sms` | Test the SMS provider | | GET | `/admin/users?limit&offset&search` | List users | | GET | `/admin/users/{id}` | Get one user | | PUT | `/admin/users/{id}` | Update name/email/is_active | | DELETE | `/admin/users/{id}` | Soft-delete (deactivate) | | GET | `/admin/cleanup-guest-data` | Purge expired guest data | --- ## Health & docs | Method | Path | Purpose | |---|---|---| | GET | `/health/deps` | Dependency/health check | | GET | `/docs` | RapiDoc UI | | GET | `/redoc` | ReDoc UI |