# Exchange a Google ID token for an app session (native apps)

`POST /v1/console/auth/google/token` (operation id `createGoogleSession`)

For the native apps (iOS and Android), which sign in with Google on the device. The gateway verifies the
Google ID token (RS256 with Google's published keys, issuer, audience = any of Dex's Google client ids,
expiry, and `nonce` when given) and requires an email address Google has verified. The address is treated
exactly like a signed-in email code: an existing account with that address is signed in, otherwise a new
account is created. The answer is a bearer `AppSession`: no cookie and no CSRF token. At most 60 Google
sign-ins per client network per hour, together with `startGoogleSignIn`.

Authentication: None. This route is public.

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id_token` | string | yes | The Google ID token (a JWT) from Google Sign-In on the device. (at most 4,096 characters; pattern ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$) |
| `nonce` | string | no | The nonce the app passed to Google Sign-In. When given, the token's `nonce` claim must equal it. (1 to 128 characters) |

## Responses

### 200

Signed in with a bearer app session.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | string | yes | (always "app_session") |
| `account_id` | AccountId | yes | (pattern ^acc_[0-9A-HJKMNP-TV-Z]{26}$) |
| `session_token` | string | yes | Shown once. Only its SHA-256 is stored. Keep it in the device's secure storage. (pattern ^dex_ses_[A-Za-z0-9_-]{43}$) |
| `expires_at` | string | yes | 30 days after sign-in; every use moves it to 30 days after that use. (format date-time) |

### 400

The request could not be read. Codes: `invalid_json` (not JSON, not UTF-8, or a `\u` escape that is half of a
surrogate pair, such as `"\ud800"` without its low half), `duplicate_key` (a JSON object
repeats a key; `param` is the JSON pointer of the repeated member, such as `/questions/q1/options/a`),
`unsupported_media_type` (not `application/json`) and `invalid_header` (a malformed `idempotency-key` or
`x-client-request-id`; `param` names the header).


### 401

The Google ID token did not sign anyone in. Codes: `invalid_google_token` (malformed, not signed by Google,
wrong issuer or audience, expired, or its nonce differs from `nonce`) and `google_email_unverified` (Google has
not verified the account's email address).


### 403

The credentials are valid but not allowed to do this (missing scope, suspended account or failed CSRF check).

### 422

The request is well-formed JSON but breaks a validation rule. `param` names the field as a dotted path.
Codes: `unknown_field`, `missing_field`, `invalid_type` (wrong JSON type), `invalid_value` (right type, value
out of range: an empty or over-long string, a state, `instructions`, `criteria` string or option description
of only whitespace, an empty `questions` or `options` object, an empty state object or array, a pattern or
allowed-value mismatch such as a label or level with outer whitespace, a state nested deeper than 32 levels, a
bad date or date range), `field_not_allowed` (`options` or `levels` on the wrong question type),
`invalid_question_id`, `too_many_questions`, `too_many_options`, `invalid_levels` (including two levels equal
after Unicode NFC normalisation and lower-casing), `invalid_min_confidence`, `duplicate_label` (two option
labels equal after Unicode NFC normalisation and lower-casing, such as `Billing` and `billing`),
`state_path_not_found`, `state_not_json` and, on the console, `top_up_limit_exceeded`.


### 429

`sign_ins_per_hour`: more than 60 Google sign-ins were started from this client network in the last hour.
`retry-after` is the real wait until one can be started again, up to 3,600 seconds.


### 500

An unexpected error. Any charge was refunded. Retry with the same idempotency key.

### 503

`maintenance`: the console's database, sign-in or payments are unavailable for a moment (for
`createGoogleSession` also: Google sign-in is not configured, or Google's signing keys cannot be fetched).
Retry after `retry-after`. Nothing was changed.


## Examples

Illustrative values. Examples show the shape of requests and responses; the numbers in them are not measured results.

Request: token

```json
{
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20ifQ.c2lnbmF0dXJl"
}
```

Response: A bearer app session (native apps)

```json
{
  "object": "app_session",
  "account_id": "acc_01M4ZPXYG0F5KZNWJ47TAN9ZT2",
  "session_token": "dex_ses_ExampleOnly0NotAReal0SessionToken0123456789",
  "expires_at": "2026-11-19T12:00:00Z"
}
```
