> ## Documentation Index
> Fetch the complete documentation index at: https://docs.factrove.app/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Describe what the running code does. Do not invent hosted pricing, launch dates, or security guarantees. Share and viewer links are a configured base URL plus an artifact path. Show localhost and a placeholder origin such as https://library.example.com.

# Troubleshooting

> Ports, secrets, storage, API origins, cookies, profiles, public links, and content tickets.

These are the failures the local loop and a self-hosted install hit most often.

## Ports and the container runtime

* **Container runtime unavailable.** `pnpm infra:up` exits when `docker` is missing or the daemon is stopped. Start the runtime and run `pnpm infra:up` again.
* **Port 5432 is taken.** Another PostgreSQL is listening on `127.0.0.1:5432`. Stop it, or change the host port in `compose.yaml` and `DATABASE_URL` together.
* **Port 3900 is taken.** Another process is listening on `127.0.0.1:3900`. Stop it, or change the Garage host port in `compose.yaml` and `S3_ENDPOINT` together.
* **Port 3000 or 3001 is taken.** Stop the other process and start again. Local development uses `http://localhost:3000` for the web app and `http://localhost:3001` for the API.
* **`pnpm e2e` reports a taken port.** That command uses `127.0.0.1:5433` for PostgreSQL, `127.0.0.1:3910` for Garage, `127.0.0.1:3100` for the web app, and `127.0.0.1:3101` for the API.

## Startup and storage

* **The API stops on `BETTER_AUTH_SECRET`.** The value is missing or shorter than 32 characters. Set it in `apps/api/.env`, then start the API again.
* **The API stops on `EMAIL_TRANSPORT`.** The value is not `log`, `smtp`, or `brevo`. Unset uses `log`.
* **The API stops on `EMAIL_FROM_ADDRESS` or `EMAIL_FROM_NAME`.** The from address is not an email address, or a set from name is longer than 70 characters or more than one line. An empty address uses `support@factrove.app`. An empty name uses `Factrove`.
* **The API stops on `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASSWORD`, or `SMTP_REQUIRE_TLS`.** Those checks run when `EMAIL_TRANSPORT` is `smtp`. Set host, port, and `SMTP_SECURE` (`true` or `false`). Set `SMTP_USER` and `SMTP_PASSWORD` together, or leave both empty. `SMTP_REQUIRE_TLS` is optional: unset requires STARTTLS when `SMTP_SECURE` is `false`. `false` allows plaintext. An empty SMTP block is valid when the transport is `log`.
* **The API stops on `BREVO_API_KEY`.** That key is required only when `EMAIL_TRANSPORT` is `brevo`. The API does not call `GET /v3/account`.
* **The invite is only in the API log.** `EMAIL_TRANSPORT` is unset or `log`, so the log says `EMAIL_TRANSPORT is log. Invite email was not sent.` Set `smtp` or `brevo` in `apps/api/.env` and create an account with a new address. The recipe takes that address as its first argument. See [Invite email](https://docs.factrove.app/guides/self-hosting.md#invite-email).
* **Sign-up returns 200 and the log says the invite email failed.** The account exists. The line starts with `Invite email failed for`. Check the SMTP server or the Brevo key for the transport you selected. The from address defaults to `support@factrove.app`. A second sign-up for the same address is 422 and does not send again.
* **Shutdown logs `Invite email shutdown left N send(s) unfinished`.** The API waited 10 seconds for queued and in-flight invites, then exited. Those accounts exist. A second sign-up for the same address is 422 and does not send again. `pnpm dev` and Compose give the API 15 seconds before a force stop.
* **The log says `Invite email skipped for`.** One hundred invites were already waiting. The account exists. That invite was not sent.
* **Upload returns `STORAGE_UNAVAILABLE`.** The API could not write the object, and the failed upload leaves no artifact row. For Garage, run `pnpm infra:up` and match `S3_ENDPOINT`, `S3_BUCKET`, `S3_REGION`, `S3_FORCE_PATH_STYLE`, and the access key in `apps/api/.env` to `compose.yaml`. For AWS, set `S3_BUCKET` and `S3_REGION`, remove `S3_ENDPOINT`, `S3_FORCE_PATH_STYLE`, and the Garage key pair, and use real credentials or the AWS credential chain. `S3_BUCKET=your-bucket` is an old placeholder. See [Object storage](https://docs.factrove.app/guides/self-hosting.md#object-storage).
* **The CLI reports `NETWORK_ERROR`.** The API origin is wrong or the API is not running. Precedence is `--endpoint-url` or `--api-url`, then `FACTROVE_API_URL`, then `FACTROVE_ENDPOINT_URL`, then the selected profile's `api_url` or `endpoint_url`, then a URL from `apps/cli/.env` when you use `pnpm factrove`, then `https://api.factrove.app`. An explicit `--profile` uses that profile's key and ignores `FACTROVE_API_KEY`. The published commands are `factrove` and `ftrv`. `pnpm factrove` is the checkout launcher.
* **The library says the API is unavailable.** The web app is using a different origin from the running API. Set `NEXT_PUBLIC_API_URL` in `apps/api/.env` or the environment, then restart `pnpm dev`. A production bundle from `pnpm build` keeps the origin from the build, so run `pnpm build` again after changing it. On the Docker web image, set `BROWSER_API_URL` to the origin the browser uses and `INTERNAL_API_URL` to the origin the Next.js server can reach. Those two are read at request time.
* **Docker Compose upload returns `STORAGE_UNAVAILABLE`.** The API container reaches Garage at `http://garage:3900` with `S3_ALLOW_INSECURE_HTTP=true`. `pnpm dev` does not use that flag. It keeps `http://127.0.0.1:3900`.
* **`docker compose up` starts only PostgreSQL and Garage.** The command without `-f docker-compose.yml` uses `compose.yaml`. The self-host stack is `docker compose --env-file .env.docker -f docker-compose.yml up --build -d`.
* **Garage exits during the self-host start.** The log says `Invalid RPC secret key: expected 32 bytes of random hex`. `GARAGE_RPC_SECRET` must be 64 hex characters. `openssl rand -hex 32` prints that. The placeholder in `.env.docker.example` is not a valid secret.
* **The API exits and the database URL looks truncated.** `POSTGRES_PASSWORD` is copied into `DATABASE_URL` without encoding. `@`, `:`, `/`, or `#` in that password breaks the URL. Use the hex output of `openssl rand -hex 32`.
* **Self-host ports 3000 or 3001 are taken.** `WEB_PORT` and `API_PORT` default to those ports, on `127.0.0.1`. `pnpm dev` uses the same pair. Stop the other process, or change `WEB_PORT`, `API_PORT`, and the public origins together. See [Configure](https://docs.factrove.app/guides/self-hosting.md#configure).

## Cookies and origins

A cookie-authenticated write (`POST`, `PUT`, `DELETE`, and other methods besides GET, HEAD, and OPTIONS) needs `Origin` or `Referer` to be `WEB_ORIGIN` or `PUBLIC_BASE_URL`. A missing or different origin is `403 FORBIDDEN` with message `This request origin is not allowed.` Bearer requests skip that check. Do not send `Authorization` on API-key create, list, or delete.

`localhost` and `127.0.0.1` are different hosts. A cookie domain cannot cover either. When the web app and API use different public hosts, set `AUTH_COOKIE_DOMAIN` to a parent of both. On an `https` API origin the session cookie is named `__Secure-better-auth.session_token`. On `http` it is `better-auth.session_token`. Use a cookie jar rather than typing the name. The recipe is in [API overview](https://docs.factrove.app/api-reference/overview.md#create-an-api-key-from-a-session).

## Profiles

`--profile`, then `FACTROVE_PROFILE`, then `default` selects the profile. `pnpm factrove` can fill an unset `FACTROVE_PROFILE` from `apps/cli/.env`. `node apps/cli/dist/main.js` does not read that file. Profile files live in `~/.factrove` unless `FACTROVE_CONFIG_DIR`, `FACTROVE_SHARED_CREDENTIALS_FILE`, or `FACTROVE_CONFIG_FILE` points elsewhere.

## Public links

Revoke clears the password. The next share is unprotected unless you pass the password again:

```bash theme={null}
pnpm factrove share "$artifact_id" --revoke
printf '%s' "$LINK_PASSWORD" | pnpm factrove share "$artifact_id" --password-stdin
```

`--password` needs a terminal. Rotating `SHARE_TOKEN_KEY`, or `BETTER_AUTH_SECRET` when that key is unset, makes existing public URLs return 404. Share again and supply the password if the link should stay protected. See [Lockout recovery](https://docs.factrove.app/guides/sharing-links.md#lockout-recovery).

## Content tickets

A signed-in viewer loads HTML through a short `access` query on the content URL. That grant lasts five minutes. Refresh the viewer to mint another one. A password-protected public link uses a separate `ticket` query that lasts 60 seconds and is honored at most three times per API process. Refresh the share page to mint another ticket. Reverse proxies should not log either query.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.