> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tilion.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Log in once with a profile

> Save a signed-in browser as a profile and start later sessions from it.

Create a profile, sign in during one session, and close that session to save. Later sessions start signed in. Set `TILION_API_KEY` first.

## Save the login

This example creates the profile, opens a session with `save: "replace"`, and prints a [live-view](https://docs.tilion.dev/guides/live-view.md) URL. Sign in through that URL, then let the script close the session.

```python Python theme={"dark"}
import os
import requests

base = "https://back.tilion.dev"
headers = {"Authorization": f"Bearer {os.environ['TILION_API_KEY']}"}

created = requests.post(
    f"{base}/v1/profiles", headers=headers, json={"name": "shop-login"}, timeout=30,
)
if created.status_code != 409:  # 409 profile_conflict: the name already exists
    created.raise_for_status()

session = requests.post(
    f"{base}/v1/session", headers=headers,
    json={"profile": {"name": "shop-login", "save": "replace"}}, timeout=120,
)
session.raise_for_status()
sid = session.json()["session_id"]

view = requests.post(f"{base}/v1/session/{sid}/view-token", headers=headers, timeout=30)
view.raise_for_status()
print("Sign in here:", base + view.json()["live_view_url"])
input("Press Enter after signing in: ")

closed = requests.delete(f"{base}/v1/session/{sid}", headers=headers, timeout=120)
closed.raise_for_status()
print("Profile save:", closed.headers.get("X-Tilion-Profile-Save"))
```

```javascript Node.js theme={"dark"}
import { createInterface } from "node:readline/promises";

const base = "https://back.tilion.dev";
const headers = {
  Authorization: `Bearer ${process.env.TILION_API_KEY}`,
  "Content-Type": "application/json",
};

const created = await fetch(`${base}/v1/profiles`, {
  method: "POST", headers,
  body: JSON.stringify({ name: "shop-login" }),
  signal: AbortSignal.timeout(30_000),
});
// 409 profile_conflict: the name already exists
if (!created.ok && created.status !== 409) throw new Error(await created.text());

const session = await fetch(`${base}/v1/session`, {
  method: "POST", headers,
  body: JSON.stringify({ profile: { name: "shop-login", save: "replace" } }),
  signal: AbortSignal.timeout(120_000),
});
if (!session.ok) throw new Error(await session.text());
const { session_id: sid } = await session.json();

const view = await fetch(`${base}/v1/session/${sid}/view-token`, {
  method: "POST", headers, signal: AbortSignal.timeout(30_000),
});
if (!view.ok) throw new Error(await view.text());
console.log("Sign in here:", base + (await view.json()).live_view_url);

const prompt = createInterface({ input: process.stdin, output: process.stdout });
await prompt.question("Press Enter after signing in: ");
prompt.close();

const closed = await fetch(`${base}/v1/session/${sid}`, {
  method: "DELETE", headers, signal: AbortSignal.timeout(120_000),
});
if (!closed.ok) throw new Error(await closed.text());
console.log("Profile save:", closed.headers.get("X-Tilion-Profile-Save"));
```

```bash curl theme={"dark"}
# Requires jq.
curl --silent --show-error -X POST https://back.tilion.dev/v1/profiles \
  -H "Authorization: Bearer $TILION_API_KEY" -H "Content-Type: application/json" \
  -d '{"name": "shop-login"}'

SID=$(curl --fail-with-body --silent --show-error -X POST https://back.tilion.dev/v1/session \
  -H "Authorization: Bearer $TILION_API_KEY" -H "Content-Type: application/json" \
  -d '{"profile": {"name": "shop-login", "save": "replace"}}' | jq -r .session_id)

curl --fail-with-body --silent --show-error -X POST \
  "https://back.tilion.dev/v1/session/$SID/view-token" \
  -H "Authorization: Bearer $TILION_API_KEY" \
  | jq -r '"https://back.tilion.dev" + .live_view_url'

# Sign in through the printed URL, then close the session.
curl --fail-with-body -i -X DELETE "https://back.tilion.dev/v1/session/$SID" \
  -H "Authorization: Bearer $TILION_API_KEY"
```

`X-Tilion-Profile-Save: saved` confirms the new version. For any other value, read `profile_save` from `GET /v1/session/{id}/status`, which has the reason for an hour after the session ends.

## Start signed in

Name the profile on create. The default `save` is `none`, so the session reads the profile and leaves it unchanged.

```python Python theme={"dark"}
import os
import requests

base = "https://back.tilion.dev"
headers = {"Authorization": f"Bearer {os.environ['TILION_API_KEY']}"}

session = requests.post(
    f"{base}/v1/session", headers=headers,
    json={"profile": {"name": "shop-login"}}, timeout=120,
)
session.raise_for_status()
body = session.json()
print("Version:", body["profile"]["version"])
print("Restored:", body["profile"]["restored"])
```

```javascript Node.js theme={"dark"}
const response = await fetch("https://back.tilion.dev/v1/session", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TILION_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ profile: { name: "shop-login" } }),
  signal: AbortSignal.timeout(120_000),
});
if (!response.ok) throw new Error(await response.text());
const { profile } = await response.json();
console.log("Version:", profile.version);
console.log("Restored:", profile.restored);
```

```bash curl theme={"dark"}
# Requires jq.
curl --fail-with-body --silent --show-error -X POST https://back.tilion.dev/v1/session \
  -H "Authorization: Bearer $TILION_API_KEY" -H "Content-Type: application/json" \
  -d '{"profile": {"name": "shop-login"}}' | jq '{session_id, profile}'
```

Authentication still depends on the site accepting the restored state. Close each session when finished.

> [!NOTE]
>
> sessionStorage comes back in a new session's first tab, in every mode. A save reads it only from the site each tab is on at that moment, so if a site keeps its login there, end the session on that site. See [What is restored](https://docs.tilion.dev/sessions/profiles.md#what-is-restored).

## Choose a mode

* Keep the default for a site that needs cookies and page storage.
* Add `"full": true` in the `profile` block when a site also needs its service workers, caches or the same device. The create takes one Chrome launch longer.
* Use `save: "merge"` when several sessions write at the same time. With `replace`, the first session to end saves and later ones lose their changes.

See [Browser profiles](https://docs.tilion.dev/sessions/profiles.md) for what each mode restores, limits, and two sessions on one profile.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.