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.
The canonical source is apps/api/.env.example in the repo — it’s the file the API actually reads, with comments next to each variable. This page is the same set of variables organized by purpose so you can scan it before a deploy.
Core (required to boot)
These four are non-negotiable. The API refuses to start without them.
| Variable | What it is |
|---|
DATABASE_URL | Postgres connection string. NeonDB pooler URL recommended for hosted use; local Postgres also works. |
REDIS_URL | Redis URL. Powers BullMQ (publish / validate / refresh-token / webhook-deliver queues) and the idempotency cache. |
KEK_MASTER | AES-256-GCM master key for the token envelope. Base64 of exactly 32 bytes. Generate with openssl rand -base64 32. Must be rotated via a re-wrap script — replacing the value locks every connected account. |
BETTER_AUTH_SECRET | better-auth signing secret. 32 bytes minimum. openssl rand -base64 32. |
HTTP server
| Variable | Default | What it is |
|---|
PORT | 3000 | HTTP port the API listens on. |
HOST | 0.0.0.0 | Bind address. The default is reachable from outside the container/host. Override to 127.0.0.1 if you only want local reads. |
PUBLIC_API_BASE_URL | http://localhost:3000 | Public origin the API is reachable on. Used by platform providers to build OAuth redirect URIs at runtime. Must match exactly what each platform’s developer portal has on file. |
BETTER_AUTH_URL | http://localhost:3000 | Public origin where better-auth callbacks land. Must match the URL the dashboard hits. |
CORS_ORIGINS | empty | Extra CORS origins, comma-separated. The dashboard at http://localhost:3001 is always allowed. Add your prod dashboard origin here. |
TRUSTED_ORIGINS | empty | Origins better-auth trusts for cross-origin auth flows. Local :3001 is always allowed; add the prod dashboard origin. |
DASHBOARD_URL | http://localhost:3001 | Where the OAuth callback redirects after a successful (or failed) connect — typically the dashboard’s /accounts page. |
COOKIE_DOMAIN | empty | Set in production when API and dashboard live on different subdomains of the same parent — e.g. .letmepost.dev (note the leading dot). Configures the session cookie with Domain=<value>; SameSite=None; Secure. Leave blank in dev. |
Documentation links
| Variable | Default | What it is |
|---|
DOCS_BASE_URL | https://docs.letmepost.dev | Stamped on every error envelope as error.docUrl (and error.ruleUrl when a preflight rule fires), so callers land on the canonical remediation page in one click. Override for staging or a self-hosted docs site. |
If you mirror the docs at, say, https://docs.example.internal, set DOCS_BASE_URL=https://docs.example.internal and every error response will link there.
The API uses a single bucket, with environments separated by an in-bucket key prefix. The current build talks to AWS S3 directly — there is no endpoint override, so S3-compatible providers (R2, MinIO, Wasabi) need a follow-up PR to expose the AWS SDK’s endpoint option. See troubleshooting for the workaround.
| Variable | What it is |
|---|
AWS_REGION | AWS region, e.g. us-east-1. |
S3_BUCKET | Bucket name. Layout: ${MEDIA_ENV_PREFIX}/${orgId}/${mediaId}.${ext}. |
S3_ACCESS_KEY_ID | IAM access key id. |
S3_SECRET_ACCESS_KEY | IAM secret access key. |
MEDIA_PUBLIC_BASE_URL | Public URL prefix concatenated with the S3 key. Direct S3 in v1 (https://<bucket>.s3.<region>.amazonaws.com); swap to CloudFront / a custom CDN domain later without code changes. |
MEDIA_ENV_PREFIX | Key prefix that namespaces objects per environment (e.g. prod, dev). Keeps prod / dev objects in separate key namespaces while sharing a bucket. |
The bucket itself needs Block Public Access OFF (objects are served by URL), ACLs disabled (Object Ownership: “Bucket owner enforced”), and an IAM user with s3:PutObject, GetObject, DeleteObject, AbortMultipartUpload, ListMultipartUploadParts on objects, plus ListBucket / ListBucketMultipartUploads on the bucket itself.
Optional dashboard sign-in providers
Set both *_CLIENT_ID and *_CLIENT_SECRET to enable each provider; leave blank to disable. The default is email + password.
| Variable pair | Portal | Authorized callback |
|---|
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | Cloud Console → Credentials | ${BETTER_AUTH_URL}/api/auth/callback/google |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | GitHub → Developer settings → OAuth Apps | ${BETTER_AUTH_URL}/api/auth/callback/github |
These are only required when you want users to connect that platform. The full per-platform setup (portal links, exact scope strings, app review status) lives in platform credentials.
| Platform | Required vars | Optional overrides |
|---|
| LinkedIn | LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET | — |
| Twitter / X | TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET | TWITTER_LAUNCH_CAP_PER_ACCOUNT (default 50) |
| Pinterest | PINTEREST_CLIENT_ID, PINTEREST_CLIENT_SECRET | PINTEREST_API_BASE (Trial Access sandbox) |
| Facebook Pages | META_APP_ID, META_APP_SECRET | META_GRAPH_VERSION |
| Instagram (IG Login) | INSTAGRAM_CLIENT_ID, INSTAGRAM_CLIENT_SECRET | INSTAGRAM_GRAPH_BASE, INSTAGRAM_OAUTH_AUTHORIZE_URL, INSTAGRAM_OAUTH_TOKEN_URL (test-only) |
| Threads | THREADS_CLIENT_ID, THREADS_CLIENT_SECRET | THREADS_API_BASE, THREADS_API_VERSION |
| Bluesky | — (uses app passwords) | — |
The Meta family runs on three independent OAuth apps in v1 — Facebook Pages, Instagram (Instagram API with Instagram Login), and Threads are all separate developer apps with separate client credentials. See platform credentials for why.
Operational toggles
| Variable | Default | What it is |
|---|
PLATFORM_STATE_OVERRIDES | empty | Override the launch state of a platform without redeploying. Comma-separated platform:state pairs (live, trial, pending). Example: pinterest:live,linkedin:trial. |
TEST_DATABASE_URL | empty | Used only by the repository test suite (per-test transaction rollback). Point at a disposable local Postgres or a Neon preview branch. |
What’s not in scope today
Three names appear in the example file but are not yet read by the source code. Don’t carry these into your .env expecting them to do anything in v1:
YOUTUBE_CLIENT_ID / YOUTUBE_CLIENT_SECRET — placeholders for a future YouTube provider.
TIKTOK_CLIENT_KEY / TIKTOK_CLIENT_SECRET — deferred to v2.
Likewise, three variables that older self-host docs referenced do not exist in the current build:
MEDIA_S3_BUCKET, MEDIA_S3_REGION, MEDIA_S3_ENDPOINT, MEDIA_S3_ACCESS_KEY_ID, MEDIA_S3_SECRET_ACCESS_KEY — superseded by the AWS_REGION + S3_* + MEDIA_* set above.
ENCRYPTION_KEY — superseded by KEK_MASTER (AES-256-GCM, base64 of 32 bytes).
WEBHOOK_SIGNING_SECRET — there is no global signing secret. Each webhook endpoint generates its own signing secret at registration time and returns it once in the create response.