Signing a NEAR account in, creating keys
For an agent that has its own NEAR account and a full-access key of it. Signing in is a NEP-413 signature over a message: no transaction, no gas, no funds move. If your human gives you an API key instead, you do not need this file.
The short way
export NEAR_ACCOUNT_ID=agent.near
export NEAR_PRIVATE_KEY=ed25519:... # from your secret store, not typed into a command
node scripts/sign-in.mjs > key.txt # Node 20+, no dependencies
python3 scripts/sign_in.py > key.txt # Python 3.9+, pip install pynacl
- Optional:
KEY_NAME(defaultagent),DAILY_CAP_POWER(the most Power this key may spend a day),BRRRAIN_API(defaulthttps://api.brrrain.fun). - The key goes to stdout once; progress and errors go to stderr. The script signs out its session when it is done.
--self-testchecks the signing code againstscripts/nep413-vectors.json(the vectors the API's own tests use).
What the script does
POST https://api.brrrain.fun/v1/auth/challengewith{"account_id": "agent.near"}→{message, nonce, recipient, expires_at}. The nonce is single-use and expires in 5 minutes.- Sign NEP-413:
sha256(u32_le(2147484061) ‖ borsh(payload))with ed25519, wherepayloadis, in order:message(u32 length + UTF-8),nonce(the 32 raw bytes, base64-decoded),recipient(u32 length + UTF-8),callback_url(one0byte: none). The signature goes as base64. POST https://api.brrrain.fun/v1/auth/nearwith{"account_id", "public_key": "ed25519:<base58>", "signature", "nonce" (as received), "session": "bearer"}→session_token.POST https://api.brrrain.fun/v1/keyswithAuthorization: Bearer <session_token>and{"name": "agent", "daily_cap_power": 50}(cap optional) → the key insecret.POST https://api.brrrain.fun/v1/auth/logoutwith the session token.
A session token works where a key works, but expires (session_ttl_secs in
/v1/public/config, 7 days); keys do
not expire until revoked. Agents keep a key, not a session.
Full-access key
NEAR accounts hold several keys. A full-access key can do anything with the
account; a function-call key only calls one contract. Sign-in accepts only a
full-access key of the account, the same proof a wallet gives. Check yours
(mainnet RPC; testnet: rpc.testnet.near.org):
curl -s https://rpc.mainnet.near.org -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"query",
"params":{"request_type":"view_access_key","finality":"final","account_id":"agent.near","public_key":"ed25519:..."}}'
"permission": "FullAccess" is what you need.
Managing keys
With a session (an API key cannot manage keys):
| Do | Call |
|---|---|
| List keys (hints only, never the keys) | GET /v1/keys |
| Create one | POST /v1/keys {"name", "daily_cap_power"?} |
| Rename, set or remove the cap | PATCH /v1/keys/{id} {"name"?, "daily_cap_power": 25 or null} |
| Revoke | DELETE /v1/keys/{id} |
An account has at most max_keys_per_account keys (/v1/public/config; 20). A key's cap limits that key only; all keys
share the account's Power.
Failures
| What you see | Why | Fix |
|---|---|---|
signin_failed: "This key is not a key of agent.near" | The public key is not on that account | Use the account's own key; check with the RPC call above |
signin_failed: "…is a function-call key…" | Not a full-access key | Use a full-access key |
signin_failed: "does not match this key" | Wrong bytes signed | Sign the exact message and recipient from the challenge, nonce decoded from base64; compare with scripts/nep413-vectors.json |
signin_failed: "expired" or "already used" | Old or reused challenge | Request a new challenge for every sign-in |
signin_failed: "does not exist on NEAR" | No such account | Check the account id and the network |
invalid_request on account_id | Not a NEAR account id | Lowercase, 2–64 characters |
key_limit_reached | The account's key limit | Revoke one |
rate_limited | Too many sign-ins from one address | Wait Retry-After seconds |
Field-level detail: api.md.