# Sign-in wire This page sets out the exact messages exchanged when someone signs in with a PCOC. The website shows a sign-in link. The user's app signs a challenge with the PCOC's key and sends it to the admission gate, and the website then asks the gate for the result. The private key signs on the user's device and never leaves it. You need this page only if you're building your own signing app or your own gate. If you use the hosted gate and an existing app, you can skip it. Source: [pcoc at commit aaa8946](https://github.com/groundinglab/pcoc/tree/aaa8946d241bf22a3004b71cf082267c757221a2/). Assumes: an object your verifier has measured ([Verifier and policy](verifier-and-policy.md)). Next: run the door yourself ([Integrate an admission gate](integrate-an-admission-gate.md)). 1. The flow. The website mints the link `https://groundinglab.io/sign-in/?v=1&nonce=<64hex>&exp=`. The client signs and POSTs once to /v1/sign-in. The website polls GET /v1/sign-in/result/{nonce}. The website owns the session; the Gate mints none. 2. What the client signs. challenge_bytes = `pcoc-gate-signin-v1` ‖ 0x00 ‖ len ‖ origin ‖ nonce(32) ‖ exp u64 BE ‖ object(32). That's 116 bytes when the origin is `https://groundinglab.io` (22 characters), and 113 bytes when the origin is `https://example.com` (19 characters). The BIP-340 message is SHA-256 of it, hashed once. A signature is in time when now < exp+5 and exp <= now+125 (a 120 s lifetime plus 5 s skew). Vector: sign-in.json. 3. The body has six keys: origin, nonce, exp (an integer), object (sha256 hex), object_b64 (padded std base64), signature (128 hex). A content type other than `application/json` is HTTP 415 and writes no website outcome. 4. What the client sees. The client sees one of three bodies: `{"delivery":"ok"}`, or HTTP 400 with `{"error":{"code":"bad_signature"}}` or `{"error":{"code":"malformed"}}`. Delivery OK is every completed response other than those two HTTP 400 bodies, including HTTP 413, 415, 429, and 503. The client shows `Sign-in submitted` for delivery OK. So a wrong content type gets HTTP 415, and the client counts that as delivery OK. 5. Poll and outcomes. The website polls for one of these result tokens: pending, accepted (+public_key, object_sha256, legs=N, span_s=S, and log_day and log_seq naming the decision-log line once it is written; each extra field is present only when set), malformed, bad_signature, too_short, rejected, earlier_version, too_few_waypoints_anchored (this build doesn't emit it). earlier_version is the website outcome for an earlier-version object; the verify job returns error.code earlier_version for the same case. There's a nine-step check order. Rows last 600 s after settling. A consumed row that is still pending is `{"outcome":"pending","submitted":true}`. This site treats that result as signed in and opens Welcome at once. It keeps reading. `accepted` replaces that session with the thorough one. A later refusal clears it. The phone's sentences stay `Sign-in submitted`, `bad signature`, and `malformed`. No extra outcome token is added. 6. Choosing a door. The hosted door for the example deployment, https://admission.groundinglab.io, is opt-in ([apps/android/README.md](https://github.com/groundinglab/pcoc/blob/aaa8946d241bf22a3004b71cf082267c757221a2/apps/android/README.md)); you can run your own Gate ([Integrate an admission gate](integrate-an-admission-gate.md)). Signed origin. Any site may run a gate. The signed `origin` is the requesting site's own web origin, compared byte for byte (SPEC §15.1 **Origin**). `https://groundinglab.io` is one example deployment. Each signing client is built for the deployments it serves: the Sign-in door URL for each accepted origin is part of that client's build. A client must not derive or guess a door from the origin, and must refuse a link whose origin has no configured door (SPEC §15.1 **Submit**). The reference Gate fixes its accepted origin to `https://groundinglab.io` (`src/wire.rs` `ORIGIN`); there is no setting for it. Any other value gets `malformed` at check-order step 2. Separately, `allowed_origins` and `extra_origins` are browser CORS settings: they govern which HTTP Origin may call the Gate, not the signed origin above. Both are empty by default, so only loopback origins are allowed ([src/config.rs](https://github.com/groundinglab/pcoc/blob/aaa8946d241bf22a3004b71cf082267c757221a2/services/admission-gate/src/config.rs); [src/http.rs](https://github.com/groundinglab/pcoc/blob/aaa8946d241bf22a3004b71cf082267c757221a2/services/admission-gate/src/http.rs) origin_allowed). 7. Routes ([src/http.rs](https://github.com/groundinglab/pcoc/blob/aaa8946d241bf22a3004b71cf082267c757221a2/services/admission-gate/src/http.rs):84-97): - GET /v1/health - GET /v1/policy - POST /v1/verify - GET /v1/verify/{job_id} - POST /v1/sign-in - GET /v1/sign-in/result/{nonce} - GET /latest.json - GET /log/{YYYY-MM-DD}/{seq} - GET /log/{YYYY-MM-DD}.jsonl Every route also answers OPTIONS (CORS preflight); anything else is 404 `not_found`.