AppEngine is the backend half of the platform: identity, permissions, data, files, money and audit, already built and already running. You do not stand it up. You point at it and start calling.
This walks the first three requests — is it up, sign in, read something — because that sequence is where most integrations either click or stall.
Check it is up
Health and readiness are the only routes that need neither authentication nor a tenant header.
curl https://appengine.appmint.io/health
A healthy server returns the envelope below. When a dependency is down you get the same shape with a 503, which makes it safe to point a monitor at.
{ "isHealthy": true, "services": { }, "timestamp": "2026-08-28T10:00:00.000Z" }
/readiness is the Kubernetes readiness probe and /version reports the build.
Sign in
Every request carries a tenant. That is the orgid header, and sign-in is no exception.
curl -X POST https://appengine.appmint.io/profile/signin -H 'Content-Type: application/json' -H 'orgid: acme' -d '{ "email": "[email protected]", "password": "…" }'
The same handler is mounted at four paths — /profile/signin, /profile/user/signin, /user/signin and /user/user/signin — because the controller declares both prefixes. Pick one and stay with it.
A 200 does not always mean you are in
This is the part that catches people. Sign-in has three non-token exits, and all of them are successful responses:
- requiresPasswordChange — the caller used a temporary password and must set a real one.
- requiresTwoFactor — returns a
challengeTokenand atwoFactorMethodofemail,smsorauthenticator. For the first two, a code has already been sent. - requiresTwoFactor with isNewDevice — the org has new-device authentication on and has not seen this device fingerprint before.
Handle all three or your login screen will hang on a response it thinks succeeded. Wrong credentials are a 400; a blocked device fingerprint is a 403.
What the token actually is
The JWT payload is the signed user record. That is why middleware can read the id, the datatype and the roles straight out of it without a database lookup.
Worth knowing before you lean on it: the current user is re-fetched from the org database on every request, and permissions and roles are copied from the token onto it. A revoked user is rejected with 401. A role changed after the token was issued still rides along until the token is refreshed.
Two lifetimes, both configured: one for the access token, one for the refresh token.
Make an authenticated request
Two headers, always — the bearer token and the tenant:
curl https://appengine.appmint.io/profile/whoami -H 'Authorization: Bearer eyJhbGciOi…' -H 'orgid: acme'
Drop the orgid and the request is not anonymous, it is tenant-less — which is a different failure and a more confusing one.
Reading your data
There is one repository API, generic over every collection you have. You do not get a bespoke endpoint per model:
GET /repository/get/:datatype/:id
GET /repository/find-by-attribute/:datatype/:attribute/:value
GET /repository/find-related/:datatype/:anyId
The same shape covers a customer, an invoice and a device. That is the point — the surface does not grow as your schema does.
Where to go next
- Quickstart — the same path with every payload printed in full
- Authentication — sessions, API keys, OAuth, two-factor and devices
- Working with data — the full repository surface