If something’s wrong, the API tends to fail loudly and early — a missing required env var crashes on boot with the variable name in the message, not a silent misroute. This page collects the recurring problems we’ve seen. If your issue isn’t here, open one on GitHub with the error message and your environment (host platform, Postgres + Redis providers, anything non-default).Documentation Index
Fetch the complete documentation index at: https://docs.letmepost.dev/llms.txt
Use this file to discover all available pages before exploring further.
Boot failures
KEK_MASTER env var is required
Generate one:
KEK_MASTER in your .env (dev) or your platform’s variable store (prod). Make sure the api and worker services have the same value — the worker decrypts tokens the api wrote.
If you change KEK_MASTER on an instance that already has connected accounts, every encrypted token becomes unreadable. There is no automatic re-wrap today; rotation requires a script that decrypts with the old key and re-encrypts with the new.
S3_BUCKET env var is required for the media upload service
You hit a code path that needs media S3 before configuring the bucket. Set the full set in environment:
connection refused to Postgres or Redis on local dev
The dev compose maps containers to non-standard host ports so they don’t collide with anything already running on :5432 / :6379:
| Service | Host port | Container port |
|---|---|---|
| Postgres | 5433 | 5432 |
| Redis | 6380 | 6379 |
DATABASE_URL and REDIS_URL need to use the host port:
Migrations don’t apply on deploy
Bothstart:api and start:worker are wired with very different boot sequences:
start:apirunsnode dist/db/migrate.js && node dist/server.js— migrations then server.start:workerrunsnode dist/queue/worker.js— no migrations.
start:worker, neither will apply pending migrations and the API will fail on the first request that touches a new column. Check the worker’s start command — it should be pnpm --filter @letmepost/api start:worker and the api’s should be pnpm --filter @letmepost/api start:api.
OAuth and connect flows
platform_auth_failed immediately after redirecting from the provider
The most common cause is a redirect URI mismatch. The OAuth provider compares the redirect_uri you sent against the one registered on the app — they must match character for character, including scheme, port, trailing slash, and case.
Check that the redirect URI registered on the platform’s developer portal matches:
${BETTER_AUTH_URL} is whatever the API has set in env (e.g. http://localhost:3000 in dev, https://api.your-domain.example in prod). The trailing path /v1/accounts/oauth/<platform>/callback is the same on every platform — only the platform name changes.
See platform credentials for the exact redirect URI per platform. Common gotchas:
- Facebook: the path is
/facebook/callback, not/meta/callback. Older docs were wrong about this. - Instagram: uses a separate OAuth app from Facebook Pages — its own client id, its own redirect URI, its own scope namespace.
- Threads: authorize URL is
https://threads.net/oauth/authorize, notfacebook.com/dialog/oauth. It has its own client credentials too.
Pinterest: “use sandbox” error on pin creation
Pinterest’s developer apps start in Trial Access and pin creation against production (api.pinterest.com) is hard-blocked with an explicit “use sandbox” error until Standard Access is approved. While you wait:
/v5/* call and the OAuth token-exchange endpoint, since both live under the same host. Clear the variable once Standard Access is approved — it defaults back to https://api.pinterest.com/v5.
Meta App Review takes forever and posts fail
Meta App Review takes 2–8 weeks per cycle and rejections are normal. While you wait, build against Development Mode + Tester accounts — same Graph API endpoints, same response shapes; review only lifts the testers-only gate. Add your test users as Testers on the app, and the production Graph API will accept their tokens immediately. Business verification is required before submitting for review.Runtime
Worker not picking up jobs
Posts stayqueued indefinitely. Check:
- The worker process is running. A common cause is starting both services with
start:api, leaving no consumer on the queues. The worker should be runningpnpm --filter @letmepost/api start:worker. REDIS_URLis the same on both services. They communicate exclusively through Redis — different URLs means no jobs flow between them.- The worker can reach Redis. Check its logs for
ECONNREFUSEDor BullMQ errors. Managed Redis providers sometimes needrediss://(TLS) instead ofredis://.
Posts publish but the dashboard log is empty
CheckCORS_ORIGINS and TRUSTED_ORIGINS — the dashboard fetches the post log via the API, and a missing origin will block the request. The dashboard origin must be in both lists. In a cross-subdomain prod setup, COOKIE_DOMAIN also needs to be set to the parent domain with a leading dot (e.g. .your-domain.example) so the session cookie is readable by the dashboard host.
Error docUrl and ruleUrl point at the wrong site
Set DOCS_BASE_URL to your docs origin. The default is https://docs.letmepost.dev — every error envelope stamps error.docUrl and error.ruleUrl (when a preflight rule fires) using this prefix.
Infrastructure
S3-compatible storage
The current build instantiates the AWS S3 client without anendpoint override, so by default it talks only to AWS S3. Self-hosting with Cloudflare R2, MinIO, Wasabi, or any S3-compatible provider needs a small code change to pass endpoint into the S3Client constructor in apps/api/src/media/s3.ts. The fields you’d want from env: S3_ENDPOINT (or similar) wired through getS3Client().
Drop an issue or PR on the repo if you need this — it’s a known gap and the fix is small.
Postgres + Redis must persist
The dev compose ships ephemeral volumes — fine for local development, never ship that to prod. Use managed Postgres (Neon, RDS, Supabase, Postgres you operate) and managed Redis (Upstash, ElastiCache, Redis you operate) with real durability. Both stores are load-bearing: Postgres holds every account, post, key, and webhook endpoint; Redis holds in-flight job state and the idempotency replay cache.Same KEK_MASTER on every replica
If you scale the api or worker horizontally, every replica needs the same KEK_MASTER. They’re all decrypting the same tokens from the same database. A mismatch produces opaque crypto errors on the post path — usually unsupported state or unable to authenticate data from the AES-GCM verifier.
Getting help
Still stuck:- Re-read the error message. The API tries to make boot-time failures self-describing — the exact variable name or platform call is usually in the message.
- Check the
requestIdon any error envelope and grep your logs. - Open an issue at github.com/rosekamallove/letmepost.dev/issues with the error, your host platform, and which step you were on.