Skip to content

Authentication & login

← docs home

Every OkLine request is signed with an X-Hmac header, so Node.js 18+ must be on your PATH (OkLine runs a bundled WASM module through a tiny Node bridge to compute the signature — see architecture). You also need Python 3.9+.

There are three ways to get an authenticated OkLine:

  1. Reuse a saved session — log in once, then load the same file forever (this also restores your E2EE keys).
  2. QR-code login — scan a code with the LINE app, no password.
  3. E-mail + password — the classic RSA credential flow.

🔒 Tokens are credentials. An access token (and a saved session file) grants full access to your LINE account. Never commit, log, paste or share them. OkLine redacts them by default in recorded output, and tokens.json is matched by the project .gitignore.


The easiest, most reliable pattern: log in once, save a session file, then load it on every later run. The session file keeps your access token, refresh token, certificate, mid — and the unwrapped E2EE (Letter Sealing) keychain — so encrypted messages keep working across runs without re-scanning a QR code.

Save a session after logging in

from okline import OkLine
from okline.qrterm import print_qr

api = OkLine()
api.qr_login(on_qr=print_qr, on_pin=lambda pin: print("Confirm this PIN on your phone:", pin))

api.save_tokens("tokens.json")  # writes credentials + E2EE keychain

api.qr_login(...) (on the OkLine object) drives the QR flow and loads your E2EE keys for this session, so a following save_tokens() persists them.

Reuse it next time — instant, no scan

from okline import OkLine

api = OkLine.from_tokens_file("tokens.json")  # restores tokens AND E2EE keys
print(api.get_profile())
print("E2EE ready:", api.e2ee.is_ready())  # True — encrypted chats work

That is all you need. Prefer this over hand-writing JSONsave_tokens / from_tokens_file use the right field names and carry the E2EE keychain, which a manual json.dump of {accessToken, refreshToken} cannot.

When a client is built with from_tokens_file, OkLine auto-saves the file whenever the access token is refreshed (see Token refresh), so the stored session stays valid.

The CLI does exactly this for you: okline login logs in once and saves ./tokens.json; every other command reuses it automatically. See CLI.


2. QR-code login

Scan a QR with the LINE app on your phone — no password needed. OkLine renders the QR as ASCII right in your terminal and drives the whole flow (for a step-by-step walkthrough with troubleshooting, see the dedicated QR login guide):

from okline import OkLine
from okline.qrterm import print_qr

api = OkLine()
result = api.qr_login(
    on_qr=lambda url: print_qr(url),  # draw the QR (scan it)
    on_pin=lambda pin: print("PIN:", pin),  # show the PIN to confirm
    wait_seconds=180,  # how long to wait for you
)
print("logged in:", bool(result.access_token))

api.save_tokens("tokens.json")  # so you don't have to scan again next time

What happens under the hood:

createSession → createQrCode → (generate a Curve25519 key inside the WASM)
  → show QR  =  callbackUrl + ?secret=<pubkey>&e2eeVersion=1
  → checkQrCodeVerified (you scan) → verifyCertificate / PIN flow
  → checkPinCodeVerified (you confirm the PIN) → qrCodeLoginV2 → tokens

The ?secret=…&e2eeVersion=1 on the QR URL is mandatory — without it the LINE app shows "an error occurred" after scanning. OkLine generates the Curve25519 keypair inside the real ltsm.wasm and appends it automatically, so you only render the URL on_qr hands you.

Light-background terminal? Pass print_qr(url, invert=True). QR garbled on Windows? Run chcp 65001 first. For an inline graphical QR, install the extra: pip install "okline[qr]".

Skip the PIN on later logins

qr_login returns a certificate. Save it and pass it next time to skip the PIN step (verifyCertificate succeeds for a known device):

result = api.qr_login(on_qr=print_qr, certificate=saved_certificate)

result.certificate is also written into your session file by save_tokens, so loading with from_tokens_file carries it for you.


3. E-mail + password (RSA)

This mirrors the extension exactly: it fetches an RSA key, encrypts your credentials and calls loginV2.

from okline import OkLine

api = OkLine()
result = api.auth.email_login("me@example.com", "secret", with_e2ee=False)

if result.success:
    print("access token:", result.access_token[:12], "…")
    print(api.get_profile())
elif result.type == 3:  # REQUIRE_DEVICE_CONFIRM
    print("Confirm this PIN on your phone:", result.pin_code)
else:
    print("needs verification:", result.type, result.display_message)

email_login returns a LoginResult with fields: .success, .type (LoginResultType), .access_token, .refresh_token, .certificate, .mid, .pin_code, .verifier, .display_message, .raw. On success the tokens are adopted into the client automatically, so you can call API methods right away.

with_e2ee=True (the default) negotiates Letter Sealing (E2EE). The RSA password blob is built and encrypted for you in okline/crypto.py.

After a successful login you can persist the session the same way:

api.save_tokens("tokens.json")

Token refresh

Automatic: if a refresh_token is set, a 401 from the server triggers a token refresh and the original request is retried transparently. When the client was built with from_tokens_file, the refreshed token is also written back to the file.

You can also refresh manually:

new_access = api.auth.refresh_access_token()

To build a client directly from tokens you already hold (auto-refresh enabled as soon as a refresh token is present):

api = OkLine(access_token="...", refresh_token="...")

Proactive renewal schedule (auto_refresh_schedule)

Every login and token-refresh response carries a tokenV3IssueResult telling the client when the token should be renewed (tokenIssueTimeEpochSec + durationUntilRefreshInSec). The extension arms a setTimeout(renewToken, ...) on every issuance (its tT class); OkLine ports that as an opt-in background timer:

api = OkLine.from_tokens_file("tokens.json", auto_refresh_schedule=True)
# or OkLine(..., auto_refresh_schedule=True)

With the flag set, a daemon threading.Timer is armed after every login and every refresh (minus up to ~1 s of jitter, matching the extension), silently renews the token when it fires, re-arms itself from the new schedule, and — when the client came from a session file — saves the refreshed token back to it. A failed silent renewal only logs a warning: the old token keeps working until the reactive 119/401 refresh path fires. If an SSE operation stream is active, it is reconnected with the fresh token, exactly like the extension. The schedule persists in the session file (camelCase keys), so from_tokens_file(..., auto_refresh_schedule=True) re-arms the timer without a fresh login. Cancel it with api.cancel_refresh_schedule() (also called by api.close()).

Refresh retries (10202) and kickout (10201)

refresh_access_token() follows the server-provided refreshApiRetryPolicy (recorded from the last login/refresh): if the refresh endpoint answers with gateway code 10202 (AUTH_RETRY_REQUIRED), the call is retried with the policy's jittered exponential backoff (initialDelayInMillis * multiplier^n, each sleep jittered by jitterRate, capped at maxDelayInMillis). Note the backoff sleeps are blocking — budget up to maxDelayInMillis per retry sequence. Gateway code 10201 (AUTH_INVALID_REQUEST) is a hard kickout: it raises LineAuthError and you must log in again. Without a stored policy the refresh is a single attempt (pre-2.9 behaviour).


Logout

Invalidate the current session on the server:

api.auth.logout()

From the CLI, okline logout does this and deletes the local tokens.json. You can also remove the device from LINE app → Settings → Account → logged-in devices.


See also: Getting started · Messaging · Receiving events / bots · CLI.