apistockdocs
v0.4 GitHub apistock.dev
Guides/Help

Troubleshooting

Find what you see in the left column. Each entry says what it means, how to confirm it, and how to fix it. The messages are quoted exactly as apistock prints them, so you can search this page for the words in your terminal.

First, three checks#

Most problems show up in one of these:

terminal
docker compose ps                     # are postgres and mailpit running and healthy?
curl http://127.0.0.1:8080/readyz     # is the API up, and can it reach the database?
go run ./cmd/api auth-providers       # which sign-in methods are on (with the environment loaded)

Every error the API returns carries a request_id, such as req_339a889c4f6816eb. The same ID is on the matching log line in the terminal running aps dev: search for it to see what happened.

Installing and creating an app#

command not found: aps#

Means: go install put aps in Go's bin folder, which your shell doesn't search.

Check: ls "$(go env GOPATH)/bin/aps" shows the file.

Fix: add export PATH="$(go env GOPATH)/bin:$PATH" to ~/.zshrc (macOS) or ~/.bashrc (Linux), then open a new terminal.

aps: the git repository has uncommitted changes; commit or stash them first, or pass --allow-dirty#

Means: aps gen and aps add only change an app whose changes are all committed, so what they write shows up as its own diff. A new app has no commits at all: aps new creates the repository but doesn't commit.

Check: git status --short lists files.

Fix: git add -A && git commit -m "Create acme-api" (or commit your own work), then run the command again. --allow-dirty skips the check.

go: go.mod requires go >= 1.26.0#

Means: your Go is older than apistock needs.

Fix: install the latest Go from go.dev/dl and check with go version. See What you need.

Starting with aps dev#

Docker isn't installed, or Docker isn't running, or compose.yaml is invalid (docker compose ps failed)#

Means: a Full app runs PostgreSQL and Mailpit in Docker, and aps dev couldn't reach Docker.

Check: docker version shows both Client and Server. If Server is missing, Docker isn't running.

Fix: start Docker Desktop (macOS) or sudo systemctl start docker (Linux), wait until it's ready, and run aps dev again. If you already run PostgreSQL elsewhere, set DATABASE_URL to it in .env and run aps dev --no-services.

port 5432 for postgres is already in use by another program#

Means: another program, often another project's database, uses the port.

Fix: add the line aps dev suggests to .env. For PostgreSQL, also change the port inside DATABASE_URL:

terminal
POSTGRES_PORT=5433
DATABASE_URL=postgres://acme-api:acme-api@127.0.0.1:5433/acme-api?sslmode=disable

Bind for 0.0.0.0:5432 failed: port is already allocated#

Means: the same as above. Some programs, such as a PostgreSQL container from another Docker project, aren't detected by aps dev's own check, so Docker reports it when starting the container.

Check: lsof -nP -iTCP:5432 -sTCP:LISTEN, or docker ps --format '{{.Names}} {{.Ports}}' | grep 5432.

Fix: the same two lines as above. Use the same approach for 1025 (MAILPIT_SMTP_PORT and MAILPIT_SMTP_ADDR) and 8025 (MAILPIT_WEB_PORT).

listen tcp 127.0.0.1:8080: bind: address already in use#

Means: another program, or another copy of your app, uses port 8080. The lines just before it (shutdown started, record release instance … context canceled) are the app stopping cleanly because of it; they aren't a second problem.

Check: lsof -nP -iTCP:8080 -sTCP:LISTEN.

Fix: stop the other program, or set APP_ADDR=127.0.0.1:8081 in .env. On another port, also set APP_PUBLIC_URL=http://localhost:8081 for Google sign-in, and WEBAUTHN_RP_ID=localhost with WEBAUTHN_ORIGINS=http://localhost:8081 for passkeys.

migrations failed#

Means: a migration's SQL has an error, or the database isn't reachable.

Check: the lines above it show PostgreSQL's message, such as syntax error at or near …, with the file.

Fix: correct the SQL in db/migrations/. While aps dev is running, the previous version of the app keeps serving until the migration succeeds. If you edited a migration that already ran on your computer, reset the local database with docker compose down -v: a migration runs only once per database.

seed: AUTH_ENCRYPTION_KEYS is required#

Means: seed data creates an administrator with two-factor authentication, whose secret must be encrypted, and no key is set. aps dev fills the key in .env; this happens when running go run ./cmd/seed yourself.

Fix: generate one into .env and load it:

terminal
echo "AUTH_ENCRYPTION_KEYS=k1:$(openssl rand -base64 32)"   # paste the output into .env
set -a; . ./.env; set +a

I lost the administrator's password or 2FA key#

Means: seed data prints them once and doesn't store them.

Fix: any of these:

  • Password: POST /v1/auth/password/forgot with {"email": "admin@example.com"}, read the code in Mailpit, then POST /v1/auth/password/reset.
  • Authenticator app: sign in with a recovery code as "recovery_code", or run go run ./cmd/api reset-mfa admin@example.com with the environment loaded.
  • Start fresh: docker compose down -v, then aps dev. This deletes every row in your local database.

Running commands yourself#

DATABASE_URL is required (or migrate: DATABASE_URL is required)#

Means: you ran go run ./cmd/api, ./cmd/migrate or ./cmd/seed directly. The app reads environment variables, not the .env file: aps dev loads .env for you, and plain go run doesn't.

Fix: load .env into the terminal first, and again after each change to it or in each new terminal:

terminal
set -a; . ./.env; set +a
go run ./cmd/migrate

postgres: connect: … dial tcp 127.0.0.1:5432: connect: connection refused#

Means: nothing listens at the address in DATABASE_URL: PostgreSQL isn't running, or it's on another port.

Check: docker compose ps shows postgres as healthy, and the port in its PORTS column matches DATABASE_URL.

Fix: docker compose up -d --wait; or make DATABASE_URL's port match POSTGRES_PORT.

failed SASL auth: FATAL: password authentication failed for user "acme-api" (SQLSTATE 28P01)#

Means: the database answered but refused the user or password in DATABASE_URL. Locally, this usually means the Docker volume was created earlier with different credentials, such as before you renamed the app, or DATABASE_URL points at another project's PostgreSQL on the same port.

Check: the user, password and database in DATABASE_URL match POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB in compose.yaml, and POSTGRES_PORT is your app's.

Fix: correct DATABASE_URL. If the volume has old credentials, reset it: docker compose down -v, then aps dev (this deletes local data).

settings: load: ERROR: relation "settings_values" does not exist (SQLSTATE 42P01)#

Means: the database has no tables yet. The app never creates or updates tables when it starts, so a new or reset database must be migrated first. The table named may differ.

Fix: go run ./cmd/migrate with the environment loaded (aps dev does it for you), then start the app. In production, run migrations before each new version starts.

Configuration errors at start#

The app checks every setting before it starts and lists all problems at once, under invalid configuration:. Fix each line, then start again.

Message Fix
AUTH_ENCRYPTION_KEYS is required in production Generate a key: Encryption key
AUTH_ENCRYPTION_KEYS: auth: invalid encryption keys: key "k1" must be 32 bytes in base64 The part after k1: isn't a 32-byte key; generate it with openssl rand -base64 32 and copy the whole line
RESEND_API_KEY is required to send email with Resend Set the key (Email sending); in development, leave MAIL_DELIVERY empty to use Mailpit
SMTP_HOST is required or SMTP_PASSWORD is required when SMTP_USERNAME is set Fill in your SMTP values
MAIL_DELIVERY=mailpit is for development; production sends email through the provider Remove MAIL_DELIVERY in production
GOOGLE_CLIENT_SECRET is required with GOOGLE_CLIENT_ID Add the secret, or empty the client ID (Google)
sign-in with Apple also needs … Set the variables it names, or empty every APPLE_ variable (Apple)
APPLE_PRIVATE_KEY_FILE: … isn't a PEM private key Point to the .p8 file Apple gave you
APP_PUBLIC_URL is required with Google or Apple sign-in Set your API's https address
WEBAUTHN_RP_ID is required with WEBAUTHN_ORIGINS, WEBAUTHN_APPLE_APP_IDS or WEBAUTHN_ANDROID_APPS Set your domain as WEBAUTHN_RP_ID (Passkeys)
WEBAUTHN_ORIGINS: "http://…" must use https in production Use https addresses
config: both variable and _FILE variant are set: DATABASE_URL Set DATABASE_URL or DATABASE_URL_FILE, not both
config: read DATABASE_URL_FILE: … The file path is wrong or unreadable
APP_ENV must be development or production Use one of the two words
APP_ADDR "…" is not host:port Such as 127.0.0.1:8080
APP_DB_MAX_CONNS must be between 1 and 1000 / APP_JOB_WORKERS must be between 1 and 10000 Use a number in range

Errors from the API#

Every error is JSON with this shape:

json
{"title": "Forbidden", "status": 403, "code": "forbidden", "detail": "missing permission for this operation", "request_id": "req_339a889c4f6816eb"}

Check code in your code: it never changes. detail is for people, and can.

Status and code Means Fix
401 unauthenticated No session, or it expired or was revoked Sign in again; send Authorization: Bearer <token>, or the session cookie from a browser
403 forbidden Signed in, but your roles don't allow this For /ops: go run ./cmd/api grant-role <email> platform_admin
403 mfa_required Your role needs two-factor authentication, and this session didn't use it Turn on an authenticator app (POST /v1/auth/mfa/totp, then /confirm), then use that session or sign in again
403 cross_origin_request_denied A browser page on another site sent a request with your session cookie Add the page's origin, such as http://localhost:3000, to APP_CORS_ORIGINS and restart
404 not_found, no route matches GET /… No such endpoint Check the path and method in /docs
413 request_too_large The body is larger than APP_MAX_BODY_BYTES (1 MiB) Send less, or raise the limit
422 validation_failed A field is missing or invalid errors lists each field and what's wrong
429 rate_limited More than 60 sign-in requests a minute from your address, or too many attempts on one account Wait a minute
503 mfa_unavailable No AUTH_ENCRYPTION_KEYS in development, so authenticator apps are off Set a key and restart
503 passkeys_unavailable WEBAUTHN_RP_ID is empty in production Set it (Passkeys)
503 auth_unavailable The app couldn't check the session, usually because the database is down curl /readyz; check PostgreSQL
500 internal_error An unexpected error; details are only in the log Search the log for the request_id

Email#

Nothing arrives in Mailpit#

Check: docker compose ps shows mailpit healthy; curl http://127.0.0.1:8080/ops/mail (as administrator) shows "delivery": "mailpit"; MAILPIT_SMTP_ADDR uses the same port as MAILPIT_SMTP_PORT.

Look at the delivery: emails are sent by a background job. GET /ops/jobs/runs?kind=apistock.mail.send shows each attempt and its error.

Fix: start Mailpit (docker compose up -d --wait), or correct the port. More email problems: Email sending.

Passkeys, Google and Apple#

What you see Fix
Browser console: SecurityError when adding or using a passkey Open the app at http://localhost:8080, not 127.0.0.1; in production the page must be https on WEBAUTHN_RP_ID or a subdomain
Browser console: blocked by CORS policy Add the page's origin to APP_CORS_ORIGINS (and to WEBAUTHN_ORIGINS for passkeys)
Google: Error 400: redirect_uri_mismatch Register the exact redirect URI Google shows (Google)
Google: Access blocked: … has not completed the Google verification process Add yourself as a test user, or publish the consent screen
Apple: invalid_client or Invalid redirect_uri Apple
Back on your site with #error=… Sign-in with the provider didn't finish; the log line for the callback says why

Tests#

Database tests are skipped#

Means: tests that need PostgreSQL skip, with instructions, unless APISTOCK_TEST_DATABASE_URL is set. A green run can hide skipped tests.

Fix: with PostgreSQL running, point the tests at it. They create and drop their own databases, so your development data is safe:

terminal
export APISTOCK_TEST_DATABASE_URL='postgres://acme-api:acme-api@127.0.0.1:5432/acme-api?sslmode=disable'
export APISTOCK_REQUIRE_DB=1     # fail instead of skip if the database is missing
go test ./...

Still stuck#

Collect the command you ran, the full output, go version, docker compose version, and the request_id of a failing request, then open an issue on GitHub. Remove secrets first: passwords, keys and tokens.

esc
↑↓ move↵ openesc close