# Account routes

The account routes of games-api, with parameters, bodies, responses and request samples.

Generated from `openapi.yaml` of games-api 0.7.0: 8 routes.

- [`POST /v1/account/magic-link`](#post-v1-account-magic-link): Email a sign-in link
- [`POST /v1/account/magic-link/consume`](#post-v1-account-magic-link-consume): Exchange the link token for an account player token
- [`POST /v1/account/magic-link/consume-code`](#post-v1-account-magic-link-consume-code): Exchange the code of a sign-in email for an account player token
- [`POST /v1/account/sign-in`](#post-v1-account-sign-in): Sign in with email and password
- [`PUT /v1/account/password`](#put-v1-account-password): Set or change the account's password
- [`DELETE /v1/account/password`](#delete-v1-account-password): Remove the password; the account signs in by link or code only
- [`GET /v1/account/export`](#get-v1-account-export): Everything stored about the player
- [`DELETE /v1/account`](#delete-v1-account): Delete the player and all data

<RouteHeading id="post-v1-account-magic-link" method="POST" path="/v1/account/magic-link" />

<CodePanel title="POST /v1/account/magic-link">
  <Fragment slot="curl">

    ```sh
    curl -X POST 'https://api.filudi.com/v1/account/magic-link' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{
      "email": "you@example.com",
      "redirect": "string"
    }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/account/magic-link', {
      method: 'POST',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        email: 'you@example.com',
        redirect: 'string',
      }),
    });
    // No body: check response.ok.
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.post(
        'https://api.filudi.com/v1/account/magic-link',
        headers={'authorization': f'Bearer {player_token}'},
        json={
            'email': 'you@example.com',
            'redirect': 'string',
        },
    )
    response.raise_for_status()
    # No body: the status says it all.
    ```

  </Fragment>
</CodePanel>

**Email a sign-in link**

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Request body

`application/json`, required.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string (email) | yes |  |
| `redirect` | string | no | Path on the site to return to after verification |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 202 | none | Sent, or silently not sent for an unknown reason the client must not learn. |
| 403 | none | Accounts are off for this site. |
| 429 | [Error](/docs/api/reference/schemas/#schema-error) | Too many requests. Headers: `Retry-After`. |

<RouteHeading id="post-v1-account-magic-link-consume" method="POST" path="/v1/account/magic-link/consume" />

<CodePanel title="POST /v1/account/magic-link/consume">
  <Fragment slot="curl">

    ```sh
    curl -X POST 'https://api.filudi.com/v1/account/magic-link/consume' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{
      "token": "string"
    }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/account/magic-link/consume', {
      method: 'POST',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        token: 'string',
      }),
    });
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.post(
        'https://api.filudi.com/v1/account/magic-link/consume',
        headers={'authorization': f'Bearer {player_token}'},
        json={
            'token': 'string',
        },
    )
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**Exchange the link token for an account player token**

With a guest token, the API merges the guest into the account player (ADR 0005).

Credential, optional: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here. Without a token the answer is the anonymous one.

### Request body

`application/json`, required.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `token` | string | yes |  |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 200 | [ConsumeResponse](/docs/api/reference/schemas/#schema-consumeresponse) | Account player and token. |
| 400 | none | Link expired, used or unknown. |
| 403 | none | Accounts are off for this site. |

<RouteHeading id="post-v1-account-magic-link-consume-code" method="POST" path="/v1/account/magic-link/consume-code" />

<CodePanel title="POST /v1/account/magic-link/consume-code">
  <Fragment slot="curl">

    ```sh
    curl -X POST 'https://api.filudi.com/v1/account/magic-link/consume-code' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{
      "email": "you@example.com",
      "code": "string",
      "site": "filudi"
    }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/account/magic-link/consume-code', {
      method: 'POST',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        email: 'you@example.com',
        code: 'string',
        site: 'filudi',
      }),
    });
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.post(
        'https://api.filudi.com/v1/account/magic-link/consume-code',
        headers={'authorization': f'Bearer {player_token}'},
        json={
            'email': 'you@example.com',
            'code': 'string',
            'site': 'filudi',
        },
    )
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**Exchange the code of a sign-in email for an account player token**

Same effect as consuming the link (ADR 0011). The page that asked for the link sends the 6-digit
code the email shows; spaces are ignored. The API checks it against the most recent link of this
email on the site that is neither consumed nor expired. The fifth wrong code burns that link.
The site is the token's; without a token, `site` names it. An invalid or dead bearer counts as no
token, and `site` is then required. `redirect` is never set: it travels inside the link, not with
the code. 20 attempts per IP per hour.

Credential, optional: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here. Without a token the answer is the anonymous one.

### Request body

`application/json`, required.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string (email) | yes |  |
| `code` | string | yes | Six digits, "123456" or "123 456"; pattern `^[0-9 ]{6,7}$` |
| `site` | string | no | Site slug. Required without a player token; with one, it must be the token's site |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 200 | [ConsumeResponse](/docs/api/reference/schemas/#schema-consumeresponse) | Account player and token. |
| 400 | [Error](/docs/api/reference/schemas/#schema-error) | "This code is wrong, expired or already used. Ask for a new email." Also a missing or mismatched `site`, and an account scheduled for deletion. |
| 403 | none | Accounts are off for this site. |
| 404 | none | Unknown site slug. |
| 429 | [Error](/docs/api/reference/schemas/#schema-error) | Too many requests. Headers: `Retry-After`. |
| 503 | none | Redis is down; the rate limit cannot be checked. |

<RouteHeading id="post-v1-account-sign-in" method="POST" path="/v1/account/sign-in" />

<CodePanel title="POST /v1/account/sign-in">
  <Fragment slot="curl">

    ```sh
    curl -X POST 'https://api.filudi.com/v1/account/sign-in' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{
      "email": "you@example.com",
      "password": "string",
      "site": "filudi"
    }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/account/sign-in', {
      method: 'POST',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        email: 'you@example.com',
        password: 'string',
        site: 'filudi',
      }),
    });
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.post(
        'https://api.filudi.com/v1/account/sign-in',
        headers={'authorization': f'Bearer {player_token}'},
        json={
            'email': 'you@example.com',
            'password': 'string',
            'site': 'filudi',
        },
    )
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**Sign in with email and password**

For an account that set a password (ADR 0011). With a guest token of the site, the API merges the
guest into the account as consume does. An unknown email, an account without a password and a
wrong password get the same 401 in the same time. The site is the token's; without a token,
`site` names it. An invalid or dead bearer counts as no token, and `site` is then required.
10 attempts per email per 15 minutes and 30 per IP per hour, right or wrong.

Credential, optional: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here. Without a token the answer is the anonymous one.

### Request body

`application/json`, required.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string (email) | yes |  |
| `password` | string | yes | A password over 128 characters answers 401; the 8 to 128 rule applies when it is set; 1 to 1024 characters; write only |
| `site` | string | no | Site slug. Required without a player token; with one, it must be the token's site |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 200 | [ConsumeResponse](/docs/api/reference/schemas/#schema-consumeresponse) | Account player and token. |
| 400 | [Error](/docs/api/reference/schemas/#schema-error) | A missing or mismatched `site`, or an account scheduled for deletion. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | "Email or password is wrong.". |
| 403 | none | Accounts are off for this site. |
| 404 | none | Unknown site slug. |
| 429 | [Error](/docs/api/reference/schemas/#schema-error) | Too many requests. Headers: `Retry-After`. |
| 503 | none | Redis is down; the rate limit cannot be checked. |

<RouteHeading id="put-v1-account-password" method="PUT" path="/v1/account/password" />

<CodePanel title="PUT /v1/account/password">
  <Fragment slot="curl">

    ```sh
    curl -X PUT 'https://api.filudi.com/v1/account/password' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{
      "password": "string",
      "current": "string"
    }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/account/password', {
      method: 'PUT',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        password: 'string',
        current: 'string',
      }),
    });
    // No body: check response.ok.
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.put(
        'https://api.filudi.com/v1/account/password',
        headers={'authorization': f'Bearer {player_token}'},
        json={
            'password': 'string',
            'current': 'string',
        },
    )
    response.raise_for_status()
    # No body: the status says it all.
    ```

  </Fragment>
</CodePanel>

**Set or change the account's password**

Account players only (ADR 0011). 8 to 128 characters after NFC normalization, not equal to the
account's email (case-insensitive), no other rule. When a password exists, `current` is required
and checked. 20 attempts per player per hour, right or wrong. Other devices stay signed in.

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Request body

`application/json`, required.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `password` | string | yes | 8 to 128 characters; write only |
| `current` | string | no | Required when the account has a password; 1 to 128 characters; write only |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 204 | none | Saved. |
| 400 | [Error](/docs/api/reference/schemas/#schema-error) | Validation failed. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | Missing or invalid token, or `current` is wrong; then the body carries `details: { field: "current" }`. |
| 403 | none | A guest: "Add your email first: a password needs an account.". |
| 429 | [Error](/docs/api/reference/schemas/#schema-error) | Too many requests. Headers: `Retry-After`. |
| 503 | none | Redis is down; the rate limit cannot be checked. |

<RouteHeading id="delete-v1-account-password" method="DELETE" path="/v1/account/password" />

<CodePanel title="DELETE /v1/account/password">
  <Fragment slot="curl">

    ```sh
    curl -X DELETE 'https://api.filudi.com/v1/account/password' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN"
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/account/password', {
      method: 'DELETE',
      headers: { authorization: `Bearer ${playerToken}` },
    });
    // No body: check response.ok.
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.delete(
        'https://api.filudi.com/v1/account/password',
        headers={'authorization': f'Bearer {player_token}'},
    )
    response.raise_for_status()
    # No body: the status says it all.
    ```

  </Fragment>
</CodePanel>

**Remove the password; the account signs in by link or code only**

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 204 | none | Removed. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | Missing or invalid token. |
| 403 | none | A guest has no password. |

<RouteHeading id="get-v1-account-export" method="GET" path="/v1/account/export" />

<CodePanel title="GET /v1/account/export">
  <Fragment slot="curl">

    ```sh
    curl 'https://api.filudi.com/v1/account/export' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN"
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/account/export', {
      headers: { authorization: `Bearer ${playerToken}` },
    });
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.get(
        'https://api.filudi.com/v1/account/export',
        headers={'authorization': f'Bearer {player_token}'},
    )
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**Everything stored about the player**

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 200 | [PlayerExport](/docs/api/reference/schemas/#schema-playerexport) | JSON file. Headers: `Content-Disposition`. |

<RouteHeading id="delete-v1-account" method="DELETE" path="/v1/account" />

<CodePanel title="DELETE /v1/account">
  <Fragment slot="curl">

    ```sh
    curl -X DELETE 'https://api.filudi.com/v1/account' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN"
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/account', {
      method: 'DELETE',
      headers: { authorization: `Bearer ${playerToken}` },
    });
    // No body: check response.ok.
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.delete(
        'https://api.filudi.com/v1/account',
        headers={'authorization': f'Bearer {player_token}'},
    )
    response.raise_for_status()
    # No body: the status says it all.
    ```

  </Fragment>
</CodePanel>

**Delete the player and all data**

Scheduled; `cron:purge` removes the rows within 24 hours. The token stops working now.

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 202 | none | Deletion scheduled. |
