Running it locally¶
There's no single "up" command for the whole platform — each service is its own repo
with its own stack. The reliable pattern is: run the APIs (and their Mongos) in
Docker, run the fronts on the host with npm, and make sure everyone agrees on the
IdP's URL. This page is the map; each repo's CLAUDE.md has the exact commands.
Minimum viable stack
To log in and click around, you need three things up: the Identity service, one product API, and one front for that product. Add the other product when you want to exercise the integration.
Ports at a glance¶
| Service | URL | Notes |
|---|---|---|
| Identity (IdP) | http://localhost:8082 |
OIDC + hosted login at root; API at /api/v1. |
| Xifrasoft API | http://localhost:8081 |
/api/v1. |
| Figestió API | http://localhost:8080 |
/api/v2. |
| Xifrasoft front | http://localhost:3001 |
|
| Figestió backoffice | http://localhost:3000 |
|
| Figestió portal | http://localhost:3002 |
|
| Syncthing | http://localhost:8384 |
REST API the figestió API drives. |
Two ways to run each service¶
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Inter, system-ui, sans-serif','fontSize':'14px','lineColor':'#7d8f8e','primaryColor':'#e7efee','primaryTextColor':'#211915','primaryBorderColor':'#5f9b9a','clusterBkg':'#ffffff00','clusterBorder':'#c4d0cf'}}}%%
flowchart LR
subgraph host["Host (npm run dev)"]
fronts["Fronts<br/><small>:3000 :3001 :3002</small>"]
end
subgraph docker["Docker (compose)"]
apis["APIs + Mongo<br/><small>:8080 :8081 :8082</small>"]
end
fronts -->|"localhost:8082"| apis
apis -->|"host.docker.internal:8082"| apis
class fronts h
class apis d
classDef h fill:#d9e8e7,stroke:#4f8f8d,color:#15302f;
classDef d fill:#f3ece6,stroke:#b39a82,color:#2b211b;
- APIs in Docker. Each API repo ships a
docker-composethat brings up the Express service and its Mongo together. This is the default and the closest to production. - Fronts on the host. The Next fronts run with
npm run dev(Turbopack) on their ports. They reach the APIs and the IdP over plainlocalhost.
You can run an API on the host too (handy for debugging), but then mind the URL rule below.
The host.docker.internal rule¶
A browser and a container resolve localhost to different places. The IdP, fronts,
and a host-run API all see localhost:8082 as the IdP. A containerised API does
not — to it, localhost is its own container. From inside Docker, point at the host
with host.docker.internal:
| Caller | How it should reach the IdP |
|---|---|
| A front (browser / Next server on host) | http://localhost:8082 |
| An API in Docker | http://host.docker.internal:8082 |
| An API on the host | http://localhost:8082 |
So a containerised xifrasoft/figestió API typically sets:
IDP_ISSUER=http://localhost:8082 # must equal the token's `iss`
IDP_JWKS_URI=http://host.docker.internal:8082/.well-known/jwks.json
IDP_BASE_URL=http://host.docker.internal:8082
IDP_ISSUER is not a URL you fetch — it's a string you compare
IDP_ISSUER must match the iss claim the IdP stamps (its ISSUER_URL,
http://localhost:8082). IDP_JWKS_URI/IDP_BASE_URL are URLs the API actually
calls, so those get host.docker.internal. Mixing these up is the most common
"valid token, still 401" bug.
First-run order¶
- Identity up first (its Mongo + the service). In dev it generates an ephemeral RS256 key at boot, so restarting it invalidates existing tokens — log in again.
- Seed the OIDC clients (below) so the fronts have something to authenticate against.
- A product API up, pointed at the IdP per the rule above.
- The matching front with
npm run dev. - Open the front, sign up, verify your email (with no
RESEND_API_KEY, the link is printed to the IdP's stdout — copy it from the logs), and you're in.
Seed the OIDC clients¶
The IdP only accepts clients it knows. The identity repo provisions the first-party clients from env overrides:
It reads, per client, a redirect-URI list and (for confidential clients) a secret:
| Client | *_REDIRECT_URIS |
Secret? |
|---|---|---|
figestio-backoffice |
http://localhost:3000/api/auth/callback |
yes |
xifrasoft-front |
http://localhost:3001/api/auth/callback |
yes |
figestio-portal |
http://localhost:3002/api/auth/callback |
yes |
figestio-companion |
http://127.0.0.1:51789/callback |
no (public, PKCE only) |
Each front then needs the matching OIDC_CLIENT_ID / OIDC_CLIENT_SECRET /
OIDC_REDIRECT_URI in its own .env. Redirect URIs must match exactly — a
trailing slash mismatch is rejected at /oidc/authorize.
Seed an agency (to exercise the accountant flow)¶
To see the cross-product accountant experience you need a User who is an agency member, an Agency, and at least one grant over a customer workspace. Using the IdP API with a normal access token:
- Create the agency:
POST /api/v1/agencies { name }(you become itsadmin). - Grant a customer workspace:
POST /api/v1/agencies/:id/grants { workspaceId, scope }withscope: "manage"to also allow folder assignment. - In the backoffice, switch to the agency context (
/auth/refresh?agency_id=) and the granted workspace appears as a customer.
For figestió's file flow you'll also want Syncthing running and an EMPRESES root
folder seeded (POST /folders/root); the
companion app automates the device side.
The four-places rule for API env vars¶
This has bitten the team twice, so it's a platform convention. Adding an env var to an
API means updating four places or it's silently undefined at runtime (no compile
error):
environment.d.ts— the TypeScript declaration..env— the value (gitignored, so it's not carried by commits, merges, or worktrees — set it per machine).docker-compose.yml— theenvironment:passthrough list.- Recreate the container:
docker compose up -d --force-recreate <service>, then verify the value inside the container.
Each API's CLAUDE.md has the full procedure and the per-service variable list. See
also Identity config, Xifrasoft config,
and Figestió config.