Skip to main content

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.
VariableWhat it is
DATABASE_URLPostgres connection string. NeonDB pooler URL recommended for hosted use; local Postgres also works.
REDIS_URLRedis URL. Powers BullMQ (publish / validate / refresh-token / webhook-deliver queues) and the idempotency cache.
KEK_MASTERAES-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_SECRETbetter-auth signing secret. 32 bytes minimum. openssl rand -base64 32.

HTTP server

VariableDefaultWhat it is
PORT3000HTTP port the API listens on.
HOST0.0.0.0Bind 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_URLhttp://localhost:3000Public 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_URLhttp://localhost:3000Public origin where better-auth callbacks land. Must match the URL the dashboard hits.
CORS_ORIGINSemptyExtra CORS origins, comma-separated. The dashboard at http://localhost:3001 is always allowed. Add your prod dashboard origin here.
TRUSTED_ORIGINSemptyOrigins better-auth trusts for cross-origin auth flows. Local :3001 is always allowed; add the prod dashboard origin.
DASHBOARD_URLhttp://localhost:3001Where the OAuth callback redirects after a successful (or failed) connect — typically the dashboard’s /accounts page.
COOKIE_DOMAINemptySet 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.
VariableDefaultWhat it is
DOCS_BASE_URLhttps://docs.letmepost.devStamped 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.

Media (S3)

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.
VariableWhat it is
AWS_REGIONAWS region, e.g. us-east-1.
S3_BUCKETBucket name. Layout: ${MEDIA_ENV_PREFIX}/${orgId}/${mediaId}.${ext}.
S3_ACCESS_KEY_IDIAM access key id.
S3_SECRET_ACCESS_KEYIAM secret access key.
MEDIA_PUBLIC_BASE_URLPublic 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_PREFIXKey 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 pairPortalAuthorized callback
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETCloud Console → Credentials${BETTER_AUTH_URL}/api/auth/callback/google
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRETGitHub → Developer settings → OAuth Apps${BETTER_AUTH_URL}/api/auth/callback/github

Platform OAuth credentials

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.
PlatformRequired varsOptional overrides
LinkedInLINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET
Twitter / XTWITTER_CLIENT_ID, TWITTER_CLIENT_SECRETTWITTER_LAUNCH_CAP_PER_ACCOUNT (default 50)
PinterestPINTEREST_CLIENT_ID, PINTEREST_CLIENT_SECRETPINTEREST_API_BASE (Trial Access sandbox)
Facebook PagesMETA_APP_ID, META_APP_SECRETMETA_GRAPH_VERSION
Instagram (IG Login)INSTAGRAM_CLIENT_ID, INSTAGRAM_CLIENT_SECRETINSTAGRAM_GRAPH_BASE, INSTAGRAM_OAUTH_AUTHORIZE_URL, INSTAGRAM_OAUTH_TOKEN_URL (test-only)
ThreadsTHREADS_CLIENT_ID, THREADS_CLIENT_SECRETTHREADS_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

VariableDefaultWhat it is
PLATFORM_STATE_OVERRIDESemptyOverride the launch state of a platform without redeploying. Comma-separated platform:state pairs (live, trial, pending). Example: pinterest:live,linkedin:trial.
TEST_DATABASE_URLemptyUsed 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.