LIME

Bind a signed-in user to an agent

Binding stores a durable user↔agent_id link; it never opens a new site session cookie.

  • 20 min
  • Intermediate

Done when

The signed-in account record holds a verified agent_id.

  • PENDING binding_id belonged to the current user
  • Passport aud=lime-binding verified
  • Account UPSERT stores agent_id; no new session cookie

Run step 1

You need

  • Signed-in user (your session already exists)
  • PENDING row keyed by binding_id + user_id
  • LIME_SITE_TOKEN on the backend
  • Absolute https redirect_uri you control (managed tiers)

Steps

  1. Create a pending binding

    create_binding_request with redirect_uri. Persist binding_id as PENDING for the signed-in user, then send the browser to connect_url.

    import asyncio
    from lime_sites import LimeSite
    
    LIME_SITE_TOKEN = "LIME_SITE_TOKEN_HERE"
    REDIRECT_URI = "https://yoursite.example/binding-callback"
    
    async def main() -> None:
        site = LimeSite(site_token=LIME_SITE_TOKEN)
        req = await site.create_binding_request(redirect_uri=REDIRECT_URI)
        print(req.binding_id)
        print(req.connect_url)
    
        await site.aclose()
    
    asyncio.run(main())

    AssertResponse includes binding_id and connect_url; PENDING row stored for user_id.

  2. Verify passport and save agent_id

    On callback `?binding_code=`, exchange via `POST /bindings/exchange` with Site Token (never JWT in URL), then verify_binding_passport (aud=lime-binding). Match binding_id to your PENDING row and the signed-in user, then UPSERT agent_id.

    import asyncio
    from lime_sites import LimeSite
    
    LIME_SITE_TOKEN = "LIME_SITE_TOKEN_HERE"
    PASSPORT_JWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
    
    async def main() -> None:
        site = LimeSite(site_token=LIME_SITE_TOKEN)
        # After ?binding_code= callback: POST /bindings/exchange with Site Token → passport JWT.
        # Crypto only: signature, aud=lime-binding, TTL, non-empty binding_id claim.
        verified = await site.verify_binding_passport(PASSPORT_JWT)
        binding_id = verified.claims["binding_id"]
        agent_id = verified.claims["agent_id"]
        # App-owned: load PENDING by binding_id, enforce user_id, UPSERT agent_id.
        print(binding_id, agent_id)
        await site.aclose()
    
    asyncio.run(main())

    AssertAccount row stores verified agent_id; binding_id correlated to the session user.

Protocol

Crypto ≠ business rule

Signature proves the token is real. Your app must still prove this binding_id belongs to the signed-in user before saving agent_id. Redirect host is pinned to registered sites.domain; still correlate binding_id to your session.

PENDING(binding_id, user_id) → match session → UPSERT agent_id

Verify

  • Assert: after exchange, passport binding_id matches PENDING for the signed-in user; agent_id saved.
  • Negative: binding_id owned by another user_id is rejected in your app.
  • Negative: completed binding_id re-use → 409 BINDING_ALREADY_COMPLETED.
  • Negative: reused binding_code → 400 BINDING_CODE_INVALID.

Wire failures

ScopeStatusCodeWhenWhat to do
LIME API422REQUEST_VALIDATION_ERRORredirect_uri shape is invalidUse absolute http(s) URI with a host; https required in staging/production
LIME API400BINDING_INVALID_REQUESTBinding request rejected (incl. managed http→https policy)Fix redirect_uri and recreate the binding
LIME API410BINDING_REQUEST_EXPIREDBinding TTL elapsedCreate a new binding request for the user
LIME API409BINDING_ALREADY_COMPLETEDBinding already completed (outside reissue grace)Start a new binding; do not reuse the passport
LIME API403BINDING_AGENT_NOT_OWNEDOwner tried to complete with an agent they do not ownPick an agent from the owner profile on Connect

Related API

Bind a signed-in user to an agent · LIME