Guide

Zero Human Auth (site)

Passwordless headless login for AI agents on your product: your server creates the request, listens on SSE, and verifies a cryptographic passport — no browser OAuth at runtime.

You are building the site backend. This page has zero agent/PoW code.

Audience: Site backend integratorsHuman role: No human in runtime flow

When to use this guide

  • B2B SaaS or API where third-party bots authenticate without human clicks.
  • You control the server; the agent runs elsewhere (customer worker or your own bot process).
  • You want one SSE stream per site and local JWKS verification — not LIME on every request.

Before you start

  1. LIME account + Site in dashboard → site_token (st_…) on your server only.
  2. Internal channel to deliver login_request_id to the agent owner (queue, webhook, RPC).
  3. Agent owner has their own agent_token — you never store or send it.
  4. JWKS verifier (or lime-sites-sdk) on the backend.

Who does what

RoleResponsibility
You — site backendCreate request → pass ID → SSE → verify passport → session.
Agent worker (other team)PoW + approve — not implemented here.
Human ownerOne-time portal setup (site + agent registration).

Your code (site backend)

Only what you implement in this role. No PoW, SSE, or token logic from the partner side — that is in a separate guide.

  1. You (one-time setup)

    Store site_token on your server

    Dashboard → Sites → copy site_token into secrets. Every site API call uses header X-Site-Token. Never ship this to the browser or mobile app.

    Tip: If you leak site_token, rotate the site in the portal.

  2. Site backend

    Create a login request

    When an agent must sign in, POST with body {}. Persist data.login_request_id — you need it for SSE matching and for handing off to the agent.

    Your server — create login request
    # Site backend — start login (store site_token in env, never in frontend JS)
    curl -sS -X POST "https://lime.pics/api/v1/modules/agent-login/requests" \
      -H "Accept: application/json" \
      -H "Content-Type: application/json" \
      -H "X-Site-Token: {your_site_token}" \
      -d '{}'
  3. Site backend

    Deliver login_request_id to the agent owner

    Return the ID from your API, push to a job queue, or call the customer's webhook. You do not call approve or PoW endpoints — that is the agent worker's job.

    Tip: Contract with integrators: they run an agent worker with agent_token; you only pass the UUID.

  4. Site backend

    Wait on SSE for APPROVED

    While the agent works in parallel, your server holds GET /modules/agent-login/events with X-Site-Token. On approved + matching request_id, read agent_passport_jwt from the event payload.

    Your server — SSE until APPROVED
    # SSE until APPROVED or EXPIRED (one connection per site_id; reconnect after each event)
    curl -sS -N "https://lime.pics/api/v1/modules/agent-login/events" \
      -H "Accept: application/json" \
      -H "X-Site-Token: {your_site_token}"
  5. Site backend

    Verify passport and create session

    JWKS verify: iss=https://lime.pics, aud=lime-site-login, exp, request_id match. Then issue your app session for claims.sub.

    Your server — verify passport JWT
    # Verify agent_passport_jwt from APPROVED event
    # 1. Fetch JWKS (cache by kid)
    curl -sS "https://lime.pics/api/v1/core/.well-known/jwks.json"
    # 2. Validate RS256, iss, aud=lime-site-login, exp (≤60s default)
    # 3. Require request_id == login_request_id from your pending session record
    # 4. Map claims (sub, user_id) → your local user/session

What the agent worker does (not your code)

After you send login_request_id, the agent owner's infrastructure takes over. You only wait on SSE — do not implement PoW or approve on the site server.

  • Their worker receives login_request_id from your channel (not from LIME create response PoW fields — those are agent-only).
  • They call LIME with their agent_token, solve PoW, and POST approve.
  • LIME emits APPROVED on your SSE connection; you never see their agent_token.

What success looks like

SSE event approved with matching request_id; passport verified (aud=lime-site-login); local session created for agent_id in claims.sub.

Same agent_id as Agent Binding

If the user already linked a LIME agent via Agent Binding, headless login should resolve to that same identity — claims.sub is the canonical agent_id everywhere.

  • Match claims.sub to your binding row

    After passport verify, claims.sub is agent_id. Compare it to the agent_id you stored at bind time before opening the session.

  • MCP is a separate runtime

    Site login does not replace MCP federation. The same agent may also call MCP tools; RS operators read the same agent_id from MCP JWT sub.

Full runnable example

Site-only Python loop (lime-sites-sdk)
# pip install lime-sites-sdk
# https://github.com/Mawyxx/lime-site-sdk
# Full Python API: https://lime-sites-sdk.readthedocs.io/
import asyncio
from lime_sites import LimeSite

async def main() -> None:
    received = asyncio.Event()
    box: dict[str, object] = {}
    site = LimeSite()  # LIME_SITE_TOKEN; starts SSE dispatcher

    @site.on_login
    async def handle_login(request_id: str, passport: str | None) -> None:
        box["request_id"] = request_id
        box["passport"] = passport
        received.set()

    req = await site.create_login_request()
    await asyncio.wait_for(received.wait(), timeout=120)
    if box.get("passport"):
        verified = await site.verify_passport(
            str(box["passport"]),
            expected_request_id=req.request_id,
        )
        print(verified.valid)
    await site.aclose()

asyncio.run(main())

Related endpoints

Zero Human Auth (site) · LIME