Skip to content

Integrations & 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.

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.

TierMatching RuleKey StrategyLimitWindow
Auth / LoginPath contains /login, /forget-password, or /reset-passwordClient IP10 req1 minute
ExportPath contains /export AND (/generate OR /download)User ID5 req1 minute
ReindexPath contains /reindex or /re-indexUser ID1 req5 minutes
SearchGET request with a keyword query paramUser ID60 req1 minute
GeneralAll other authenticated endpointsUser ID300 req1 minute

For unauthenticated requests where no user_id is present in context, the General tier falls back to IP-based keying.

Every response includes the following headers:

HeaderDescription
X-RateLimit-LimitThe maximum number of requests allowed in the window
X-RateLimit-RemainingThe number of requests remaining in the current window
X-RateLimit-ResetUnix timestamp when the current window resets

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
}
}

The following paths are always exempt from rate limiting:

  • /swagger/* — API documentation
  • Any path containing health — Health check endpoint

The limits can be overridden without redeploying by setting these env vars (defaults shown):

VariableDefaultDescription
RATE_LIMIT_LOGIN_LIMIT10Max login/auth attempts per minute
RATE_LIMIT_AUTH_LIMIT300Max general authenticated requests per minute
RATE_LIMIT_SEARCH_LIMIT60Max search requests (with keyword) per minute
RATE_LIMIT_EXPORT_LIMIT5Max export generate/download requests per minute
RATE_LIMIT_REINDEX_LIMIT1Max reindex requests per 5 minutes
RATE_LIMIT_REDIS_PREFIXmerq:ratelimitKey prefix (used when Redis store is added)

The backend integrates with several external services, all wired in internal/platform/.

Email sending uses a failover pattern implemented in internal/platform/email/failover_client.go. Providers are tried in order until one succeeds:

  1. Resend (primary) — configured via RESEND_API_KEY + RESEND_SENDER_EMAIL
  2. Plunk (first fallback) — configured via PLUNK_API_KEY + PLUNK_SENDER_EMAIL
  3. 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 to mobile clients are sent via Firebase Cloud Messaging (FCM), configured in internal/platform/firebase/.

VariableDescription
FIREBASE_CREDENTIALS_FILEPath to the Firebase service account JSON file (default: ./firebase/service-account.json)

All file uploads (photo evidence, receipts, import/export files) are stored in DigitalOcean Spaces (S3-compatible), configured in internal/platform/spaces/.

VariableDescription
DO_SPACES_KEYSpaces access key
DO_SPACES_SECRETSpaces secret key
DO_SPACES_BUCKETBucket name
DO_SPACES_ENDPOINTEndpoint URL (e.g., https://sgp1.digitaloceanspaces.com)
DO_SPACES_REGIONRegion slug (e.g., sgp1)

Search across entities (outlets, products, users, etc.) is powered by Typesense, configured in internal/platform/search/.

VariableDefaultDescription
TYPESENSE_HOSTlocalhostTypesense server hostname
TYPESENSE_PORT8108Typesense server port
TYPESENSE_PROTOCOLhttpProtocol (http or https)
TYPESENSE_API_KEYAPI key for Typesense

Redis is used for caching and is also the intended future store for distributed rate limiting. Configured in internal/platform/redis/.

VariableDefaultDescription
REDIS_HOSTlocalhostRedis hostname
REDIS_PORT6379Redis port
REDIS_PASSWORDRedis password (optional)
REDIS_DB0Redis database index