# PocketBase SDK Path Construction ## The double `/api` trap When building a reverse proxy for a PocketBase-backed SPA, the PB JS SDK constructs its own API paths. Understanding this avoids the most common proxy bug. ### How the PB SDK builds URLs The PocketBase JS SDK's `buildURL` method concatenates `baseURL` + `path`: ```js // If you create: new PocketBase('/pb') // Then calling pb.collection('users').authWithPassword(email, password) // SDK builds: GET /pb/api/collections/users/auth-with-password // ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // baseURL SDK-added /api/ path ``` The SDK ALWAYS prepends `/api/` to its paths. Your proxy must strip only the `/pb` prefix and forward the rest as-is — including the `/api/` that the SDK already added. ### The bug ```python # WRONG — this adds a second /api/, producing /api/api/collections/... if self.path.startswith('/pb'): url = PB_BACKEND + '/api' + self.path[3:] # DOUBLE /api! ``` PocketBase returns `404 {"message": "The requested resource wasn't found."}` for the double-path URL, which is the same error message as genuinely missing resources — making this a silent, confusing failure. ### The fix ```python # CORRECT — preserve the original path, which already includes /api/ if self.path.startswith('/pb'): url = PB_BACKEND + self.path[3:] # /pb/X → /X on backend ``` ## Python `http.server` path behavior `BaseHTTPRequestHandler.path` includes the query string. When forwarding, the query string is already part of the path. `urllib.request.Request(url)` handles query strings correctly, so no special parsing is needed. ## PocketBase admin auth PocketBase admin/superuser authentication is version-dependent: - **v0.22+**: `POST /api/collections/_superusers/auth-with-password` - **Older**: `POST /api/admins/auth-with-password` (may be a 404) - The admin dashboard is at `/_/` (web UI only, not an API endpoint) If neither endpoint works, check the PocketBase version. The admin user must be created via the `./pocketbase superuser` CLI command or through the web UI on first run. ## PocketBase Query Field Errors (400 "Something went wrong") PocketBase returns `400 {"message": "Something went wrong while processing your request."}` when a query references fields that don't exist in the collection schema. This is NOT a 404 — it's a 400, and the error message is generic, making it easy to misdiagnose as a permissions or setup issue. ### The `created` / `updated` system field trap Collections created via the PocketBase API (rather than the admin UI) may lack the `created` and `updated` system fields. Any query that sorts or filters on these fields will fail with 400. **Symptom:** `sort: '-created'` succeeds via curl against a valid collection but returns 400 when tested against the same collection on a different PB instance. The collection EXISTS but the query fails because the field doesn't. **Detection:** Test the exact query parameters one at a time: ```python # Isolate which parameter breaks # Test: ?page=1 (should work) # Test: ?page=1&sort=-created (fails → field missing) # Test: ?page=1&fields=created (may fail or be silently ignored) ``` **Workaround:** Replace `sort: '-created'` with `sort: '-id'` — PocketBase record IDs are sortable and roughly chronological. For `fields`, remove any references to `created`/`updated` if the collection lacks them. **Root cause:** Subagents building against one PB instance may use system fields that work there, but the target deployment's PB instance has collections created via API without auto-system-fields enabled. ### Collection naming mismatches PocketBase collection names are case-sensitive and can use either camelCase (`repairOrders`) or snake_case (`repair_orders`) depending on how they were created. The API silently returns 404 for the wrong case, and the error message is identical to a genuinely missing collection. Always verify the exact collection name by querying `GET /api/collections` before assuming a collection doesn't exist. ## PocketBase Sign-Up Error Handling When `pb.collection('users').create()` fails, the PocketBase SDK throws a `ClientResponseError` with a generic top-level `.message` (e.g., `"Failed to create record."`) and field-level details in `.data`: ```json { "data": { "email": {"code": "validation_not_unique", "message": "Value must be unique."} }, "message": "Failed to create record.", "status": 400 } ``` **Never use the raw `.message` directly** — it's always `"Failed to create record."` for any validation failure. Instead, unwrap `.data` to show the user which field failed: ```typescript } catch (err: unknown) { let message = 'Failed to create account.'; if (err && typeof err === 'object' && 'data' in err) { const pbErr = err as { data?: Record }; if (pbErr.data) { const fieldErrors = Object.entries(pbErr.data) .map(([field, info]) => `${field}: ${info.message}`) .join('; '); if (fieldErrors) message = fieldErrors; } } setError(message); } ``` This produces user-friendly messages like `email: Value must be unique.` instead of the unhelpful `Failed to create record.` Common PocketBase validation errors on user creation: - **email not unique** → `email: Value must be unique.` - **password too short** → `password: Must be at least 8 characters.` - **missing required field** → `email: Cannot be blank.` ## PocketBase SMTP Configuration PocketBase requires SMTP to be configured for email verification to work. Check the `_params` table in the PocketBase data directory: ```sql SELECT value FROM _params WHERE id='settings' ``` If `smtp.enabled` is `false`, verification emails will never send. Configure SMTP via the PocketBase admin UI (`/_/`) or by updating the `_params` row directly with valid SMTP credentials (host, port, username, password, TLS).