Integrations & Rate Limiting
Rate Limiting
Section titled “Rate Limiting”The Merq API enforces rate limiting on all routes to protect against abuse. The implementation lives in internal/middleware/rate_limiter.go and uses the github.com/ulule/limiter/v3 library with a fixed-window algorithm.
Current Store
Section titled “Current Store”The rate limiter currently uses an in-memory store (per-process). A Redis-backed distributed store is planned but not yet implemented — there is a TODO comment in the source for this.
Implication: On multi-instance deployments, each instance tracks its own counters independently. The effective limit across all instances is
limit × number of instances.
Rate Limit Tiers
Section titled “Rate Limit Tiers”| Tier | Matching Rule | Key Strategy | Limit | Window |
|---|---|---|---|---|
| Auth / Login | Path contains /login, /forget-password, or /reset-password | Client IP | 10 req | 1 minute |
| Export | Path contains /export AND (/generate OR /download) | User ID | 5 req | 1 minute |
| Reindex | Path contains /reindex or /re-index | User ID | 1 req | 5 minutes |
| Search | GET request with a keyword query param | User ID | 60 req | 1 minute |
| General | All other authenticated endpoints | User ID | 300 req | 1 minute |
For unauthenticated requests where no user_id is present in context, the General tier falls back to IP-based keying.
Rate Limit Headers
Section titled “Rate Limit Headers”Every response includes the following headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | The maximum number of requests allowed in the window |
X-RateLimit-Remaining | The number of requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the current window resets |
Rate Limit Exceeded Response
Section titled “Rate Limit Exceeded Response”When the limit is reached, the API returns HTTP 429 with error code RATE_LIMIT_EXCEEDED:
{ "status": 429, "message": "Too many requests", "code": "RATE_LIMIT_EXCEEDED", "data": { "limit": 10, "remaining": 0, "reset": 1735689600 }}Exemptions
Section titled “Exemptions”The following paths are always exempt from rate limiting:
/swagger/*— API documentation- Any path containing
health— Health check endpoint
Configuration via Environment Variables
Section titled “Configuration via Environment Variables”The limits can be overridden without redeploying by setting these env vars (defaults shown):
| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_LOGIN_LIMIT | 10 | Max login/auth attempts per minute |
RATE_LIMIT_AUTH_LIMIT | 300 | Max general authenticated requests per minute |
RATE_LIMIT_SEARCH_LIMIT | 60 | Max search requests (with keyword) per minute |
RATE_LIMIT_EXPORT_LIMIT | 5 | Max export generate/download requests per minute |
RATE_LIMIT_REINDEX_LIMIT | 1 | Max reindex requests per 5 minutes |
RATE_LIMIT_REDIS_PREFIX | merq:ratelimit | Key prefix (used when Redis store is added) |
External Integrations
Section titled “External Integrations”The backend integrates with several external services, all wired in internal/platform/.
Email — Failover Chain
Section titled “Email — Failover Chain”Email sending uses a failover pattern implemented in internal/platform/email/failover_client.go. Providers are tried in order until one succeeds:
- Resend (primary) — configured via
RESEND_API_KEY+RESEND_SENDER_EMAIL - Plunk (first fallback) — configured via
PLUNK_API_KEY+PLUNK_SENDER_EMAIL - Mailersend (second fallback) — configured via
MAILERSEND_API_KEY+MAILERSEND_SENDER_EMAIL
If all three fail, the error from the last provider is returned. Email is used for password reset and transactional notifications.
Push Notifications — Firebase FCM
Section titled “Push Notifications — Firebase FCM”Push notifications to mobile clients are sent via Firebase Cloud Messaging (FCM), configured in internal/platform/firebase/.
| Variable | Description |
|---|---|
FIREBASE_CREDENTIALS_FILE | Path to the Firebase service account JSON file (default: ./firebase/service-account.json) |
File Storage — DigitalOcean Spaces
Section titled “File Storage — DigitalOcean Spaces”All file uploads (photo evidence, receipts, import/export files) are stored in DigitalOcean Spaces (S3-compatible), configured in internal/platform/spaces/.
| Variable | Description |
|---|---|
DO_SPACES_KEY | Spaces access key |
DO_SPACES_SECRET | Spaces secret key |
DO_SPACES_BUCKET | Bucket name |
DO_SPACES_ENDPOINT | Endpoint URL (e.g., https://sgp1.digitaloceanspaces.com) |
DO_SPACES_REGION | Region slug (e.g., sgp1) |
Full-Text Search — Typesense
Section titled “Full-Text Search — Typesense”Search across entities (outlets, products, users, etc.) is powered by Typesense, configured in internal/platform/search/.
| Variable | Default | Description |
|---|---|---|
TYPESENSE_HOST | localhost | Typesense server hostname |
TYPESENSE_PORT | 8108 | Typesense server port |
TYPESENSE_PROTOCOL | http | Protocol (http or https) |
TYPESENSE_API_KEY | API key for Typesense |
Redis is used for caching and is also the intended future store for distributed rate limiting. Configured in internal/platform/redis/.
| Variable | Default | Description |
|---|---|---|
REDIS_HOST | localhost | Redis hostname |
REDIS_PORT | 6379 | Redis port |
REDIS_PASSWORD | Redis password (optional) | |
REDIS_DB | 0 | Redis database index |