rmi-backend/app/routers/dev_portal.py
opencode c762564d40 style(rmi-backend): complete lint cleanup — 1175→0 ruff errors
- Fix 71 invalid-syntax files (class-body newline-broken assignments)
- Add from/None chain to 307 B904 raise-without-from sites
- Add B008 ignore to ruff.toml (already in pyproject.toml)
- Noqa F401 on __init__.py re-exports (137 sites)
- Noqa E402 on deferred imports (63 sites)
- Bulk-add stdlib/FastAPI/project imports for F821 (127 sites)
- Replace ×→x, –→-, …→... in docstrings (4093 chars)
- Manual refactor of 5 SIM103/SIM116 patterns

Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py)
Co-authored-by: opencode <opencode@rugmunch.io>
2026-07-06 15:43:20 +02:00

96 lines
3.1 KiB
Python

"""API Developer Portal - self-service key management, docs, usage tracking."""
import hashlib
import os
from datetime import UTC, datetime
from fastapi import APIRouter, Header, HTTPException
from pydantic import BaseModel
router = APIRouter(prefix="/api/v1/developer", tags=["developer-portal"])
# In-memory store (Supabase in prod)
_api_keys: dict[str, dict] = {}
_usage_store: dict[str, list] = {}
class CreateKeyRequest(BaseModel):
email: str
name: str = ""
class APIKeyResponse(BaseModel):
key: str
name: str
email: str
tier: str = "free"
created_at: str
requests_today: int = 0
def _generate_key() -> str:
return "rmi_" + hashlib.sha256(os.urandom(32)).hexdigest()[:32]
@router.post("/keys")
async def create_api_key(req: CreateKeyRequest):
"""Generate a new API key. Free tier: 100 requests/day."""
key = _generate_key()
_api_keys[key] = {
"key": key,
"name": req.name or "Untitled",
"email": req.email,
"tier": "free",
"created_at": datetime.now(UTC).isoformat(),
"requests_today": 0,
}
return APIKeyResponse(**_api_keys[key])
@router.get("/keys")
async def list_keys(x_api_key: str = Header(...)):
"""List your API keys."""
if x_api_key not in _api_keys:
raise HTTPException(401, "Invalid API key")
return {"keys": [_api_keys[x_api_key]]}
@router.delete("/keys/{key}")
async def revoke_key(key: str):
"""Revoke an API key."""
_api_keys.pop(key, None)
return {"revoked": key}
@router.get("/usage")
async def get_usage(x_api_key: str = Header(...)):
"""Get your API usage stats."""
if x_api_key not in _api_keys:
raise HTTPException(401, "Invalid API key")
return {
"key": x_api_key,
"tier": _api_keys[x_api_key]["tier"],
"requests_today": _api_keys[x_api_key].get("requests_today", 0),
"limit": 100 if _api_keys[x_api_key]["tier"] == "free" else 10000,
}
@router.get("/docs")
async def api_docs():
"""API documentation - available endpoints."""
return {
"base_url": "https://rugmunch.io/api/v1",
"authentication": "X-API-Key header",
"rate_limits": {"free": "100/day", "pro": "10,000/day", "enterprise": "unlimited"},
"endpoints": [
{"method": "GET", "path": "/databus/fetch/{data_type}", "desc": "Query DataBus"},
{"method": "GET", "path": "/token/scan", "desc": "SENTINEL security scan"},
{"method": "GET", "path": "/mev-sniper/signals", "desc": "MEV sniper signals"},
{"method": "GET", "path": "/sentiment/token/{symbol}", "desc": "Social sentiment"},
{"method": "GET", "path": "/arbitrage/find/{symbol}", "desc": "Cross-chain arbitrage"},
{"method": "GET", "path": "/prediction-markets/markets", "desc": "Prediction markets"},
{"method": "GET", "path": "/dify-tools/catalog", "desc": "All 15 AI tools"},
],
"sdks": ["Python: pip install rmi-sdk (coming soon)", "JavaScript: npm install rmi-sdk (coming soon)"],
"swagger": "https://rugmunch.io/docs",
}