target_session_token PoP (same-device merge)
A PoP credential for merging without OTP when both logi account sessions are active in the same user-agent. The cross-device case uses OTP.
Flow
- Issue a token from the Target (B) session → the raw string is shown once (5-minute TTL).
- The same browser frontend passes the token to the Requester (A) session (postMessage / sessionStorage / etc. — your choice).
- The Requester (A) submits
target_session_tokento/me/merge→ it's consumed atomically as single-use. - Audit:
identity_links.merged_via = "session_token".
API
Issue a token
http
POST /api/v1/me/merge/session-token
Authorization: <target session cookie or PAK>
Content-Type: application/json
{ "idempotency_key": "uuid-optional" }Response (200):
json
{
"session_token": "ltgt_8f3a...c91",
"expires_at": "2026-05-11T13:25:00Z"
}- The plaintext token is shown once in the response. The server stores only the SHA-256 hash.
- TTL: issuance time + 5 minutes.
- Calling the endpoint again with the same
idempotency_keyreturns the same unused token.
Use the token (merge)
http
POST /api/v1/me/merge
Authorization: <requester session cookie or PAK>
Content-Type: application/json
# Option A — cross-device OTP
{ "target_user_id": 42, "otp_code": "123456" }
# Option B — same-device session_token
{ "target_session_token": "ltgt_8f3a...c91" }Response (200):
json
{
"ok": true,
"identity_link_id": 1287,
"primary_user_id": 17,
"linked_user_id": 42,
"merged_via": "session_token"
}Error codes
| Status | Code | Meaning |
|---|---|---|
| 422 | conflicting_credentials | Both otp_code and target_session_token were sent |
| 422 | missing_credentials | Both are empty |
| 422 | self_merge_forbidden | The token's target user_id == the requester's user_id |
| 401 | token_expired | The 5-minute TTL was exceeded |
| 401 | token_consumed | The token was already used |
| 401 | invalid_token | Hash mismatch or nonexistent |
Security model
| Item | Behavior |
|---|---|
| Storage | Only the SHA-256 hash is stored in merge_session_tokens.token_digest |
| Single-use | consumed_at is set atomically; a second attempt returns token_consumed |
| TTL | Issuance + 5 minutes |
| Target binding | The token is bound to the issuing session's user_id |
| Self-merge block | If requester == target, returns 422 self_merge_forbidden |
| Audit | identity_links.merged_via = "session_token" |
OTP vs session_token
| OTP | session_token | |
|---|---|---|
| When to use | cross-device | same-device |
| PoP medium | a 6-digit code to a verified email | the target session cookie + a hash token |
| TTL | 10 minutes | 5 minutes |
| Reuse | discarded after one use | atomic single-use |
Issuing with a PAK (server-to-server / CI/CD)
Requirements
- The PAK holds the
account:mergescope. - Specify
via: "pak"in the request body (the scope alone does not auto-promote). issued_via = "pak"is recorded on the token record and in the audit log.
curl
bash
curl -X POST https://api.1pass.dev/api/v1/me/merge/session-token \
-H "Authorization: Bearer logi_pak_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" \
-H "Content-Type: application/json" \
-d '{"via":"pak","idempotency_key":"ci-merge-2026-05-11-001"}'Errors
via=pakwithout the scope →403 insufficient_scope({"error":"insufficient_scope","required":"account:merge"}).- An unknown
viavalue →400 invalid_via. - If an
account:mergePAK is lost, revoke it immediately.
Limitations
- iframe / cross-origin: the target session cookie may not be sent due to the SameSite policy → issue/pass within the same top-level origin, or fall back to OTP.
- When the cross-device intent is clear, OTP is recommended.