API contracts / App Service v2 / Auth & session

Log in with login, tenant and password

Operationlogin
Call
POST /gateway/urvi-app-service/api/v2/auth/login
Group
Auth & session
Features
Login, App startup and role detection
Replaces today

Use case. Called by the Login screen on first launch (online only) and after "Session expired". The app sends the trimmed, lower-cased login, the tenant and the password together with its app-scoped device id; the App Service authenticates against Bridge server-side, resolves the tenant (including the federated ap.gov host), binds the refresh token to the device and returns short-lived tokens. The app stores only the refresh token (Keystore-backed) and immediately calls GET /session/bootstrap. The password is never persisted on the phone.

Rules.

  • Login shapes accepted: email, login@@tenant, numeric IE login; a plain email gets @@tenant appended.
  • Unknown tenant -> 404 TENANT_NOT_FOUND; bad credentials -> 401 INVALID_CREDENTIALS (no detail about which part was wrong).
  • When the tenant enforces device binding and another device is bound, login still succeeds; bootstrap reports CONFLICT.
  • Rate limited per login and per device.

Error codes. INVALID_CREDENTIALS, TENANT_NOT_FOUND, DEVICE_BLOCKED, VERSION_TOO_OLD, RATE_LIMITED

Parameters

NameInTypeRequiredMeaningExample
X-App-VersionheaderstringYesApp version `major.minor.patch+build`. Too old -> 426 `VERSION_TOO_OLD`. · pattern ^\d+\.\d+\.\d+(\+\d+)?$2.0.0+200
X-Device-Idheaderstring (uuid)YesApp-scoped device UUID (not ANDROID_ID).6b1f2c3d-0000-4000-8000-00000000d001
X-Request-Idheaderstring (uuid)NoClient-generated request id for tracing; echoed in responses and problems.0e0c5a1b-0000-4000-8000-0000000000a1
Accept-LanguageheaderstringNoLanguage for messages and labels. · one of en, te, ta; default "en"te
X-TenantheaderstringNoTenant code; must match the token. Urvi: `ap.gov`. · one of ap.govap.gov
Idempotency-Keyheaderstring (uuid)YesUUID per write request; retained >= 30 days. Reusing a key with a different body -> 422 `IDEMPOTENCY_KEY_REUSED`.9c8b7a6d-0000-4000-8000-0000000000b1

Request

Body schema LoginRequest.

Example request body
{
  "login": "user.a@@ap.gov",
  "tenant": "ap.gov",
  "password": "example-password",
  "deviceId": "6b1f2c3d-0000-4000-8000-000000053249"
}
FieldTypeRequiredRulesMeaning
loginstringYespattern ^\S+$; min length 1; max length 200Lower-cased and trimmed. Accepted shapes: `email`, `login@@tenant`, numeric IE login.
tenantstringNomin length 1; max length 100Tenant (organisation) name; optional when `login` already contains `@@tenant`. Urvi serves `ap.gov`.
passwordstring (password)Yesmin length 1; max length 200Never stored on the device.
deviceIdstring (uuid)YesApp-scoped device UUID (not ANDROID_ID). Must equal `X-Device-Id`.
integrityTokenstringNoOptional Play Integrity token.

Responses

200 Logged in.

Schema TokenResponse.

Example 200 body
{
  "accessToken": "eyJhbGciOiJSUzI1NiJ9.EXAMPLE.SIGNATURE",
  "tokenType": "Bearer",
  "expiresIn": 900,
  "refreshToken": "rt_EXAMPLE_0000000000000000000000",
  "refreshExpiresIn": 2592000,
  "userId": 9001,
  "tenant": "ap.gov"
}
FieldTypeRequiredRulesMeaning
accessTokenstringYesJWT, ~15 minutes. Claims: user, tenant, roles, device.
tokenTypestringYes
expiresInintegerYes≥ 1Seconds until the access token expires.
refreshTokenstringYesRotating, device-bound, ~30 days. Store in Keystore-backed storage.
refreshExpiresInintegerYes≥ 1
userIdinteger (int64)NoServer id (assigned by the App Service).
tenantstringNo

400 Malformed request or schema violation.

Schema Problem.

Example 400 body
{
  "type": "https://urvi.example.invalid/problems/validation-failed",
  "title": "Validation failed",
  "status": 400,
  "detail": "One or more fields are invalid.",
  "code": "VALIDATION_FAILED",
  "requestId": "0e0c5a1b-0000-4000-8000-0000000000a1",
  "errors": [
    {
      "field": "/mobile",
      "code": "INVALID_MOBILE",
      "message": "Mobile must be 10 digits starting 6-9."
    }
  ]
}

401 Invalid credentials.

Schema Problem.

Example 401 body
{
  "type": "https://urvi.example.invalid/problems/invalid-credentials",
  "title": "Login failed",
  "status": 401,
  "detail": "User id or password is incorrect.",
  "code": "INVALID_CREDENTIALS",
  "requestId": "0e0c5a1b-0000-4000-8000-0000000000a1"
}

403 Authenticated but not allowed (role, tenant, device binding or feature switch).

Schema Problem.

Example 403 body
{
  "type": "https://urvi.example.invalid/problems/role-not-allowed",
  "title": "Not allowed",
  "status": 403,
  "detail": "Your role cannot perform this action.",
  "code": "ROLE_NOT_ALLOWED",
  "requestId": "0e0c5a1b-0000-4000-8000-0000000000a1"
}

404 Unknown tenant.

Schema Problem.

Example 404 body
{
  "type": "https://urvi.example.invalid/problems/tenant-not-found",
  "title": "Organisation not found",
  "status": 404,
  "detail": "Organisation name not recognised.",
  "code": "TENANT_NOT_FOUND",
  "requestId": "0e0c5a1b-0000-4000-8000-0000000000a1"
}

426 App version below the minimum; block the UI with an update prompt. Pending outbox ops are kept.

Schema Problem.

Example 426 body
{
  "type": "https://urvi.example.invalid/problems/version-too-old",
  "title": "Update required",
  "status": 426,
  "detail": "Please update Urvi to continue.",
  "code": "VERSION_TOO_OLD",
  "requestId": "0e0c5a1b-0000-4000-8000-0000000000a1",
  "minAppVersion": "2.0.0"
}

429 Rate limited per device / user. Respect `Retry-After`.

Schema Problem.

Example 429 body
{
  "type": "https://urvi.example.invalid/problems/rate-limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Slow down and retry later.",
  "code": "RATE_LIMITED",
  "requestId": "0e0c5a1b-0000-4000-8000-0000000000a1",
  "retryAfterSeconds": 30
}

500 Unexpected server error; retry with backoff (writes are idempotent).

Schema Problem.

Example 500 body
{
  "type": "https://urvi.example.invalid/problems/internal-error",
  "title": "Server error",
  "status": 500,
  "detail": "Something went wrong. Please try again.",
  "code": "INTERNAL_ERROR",
  "requestId": "0e0c5a1b-0000-4000-8000-0000000000a1"
}