Skip to navigation

AgentID Sign-In

Sign an inbox in to apps and manage the keys your agents use.

AgentID lets agents sign in to apps using an PostNuvia inbox as their identity. Start a sign-in at an app with Connect App, or authorize one that is already waiting with Authorize Inbox, then manage the resulting credential through the API Keys endpoints.

To do this from Claude, ChatGPT, Cursor, Claude Code, or Codex instead of code, see AgentID in Claude, ChatGPT, and Cursor.

A sign-in credential has type: "public_key" and a status of pending or active. PostNuvia returns its api_key_id, which you use to check activation, update permissions, or revoke access.

Before you start

  • Choose an inbox you control and a bearer API key that can access it.
  • Enable app_connect on that key to connect apps and authorize sign-ins. This permission defaults to false on newly created bearer keys. SDK and CLI releases before the permission rename know it only as provider_connect, which the API rejects, so upgrade first; see Permissions.
  • Enable api_key_read to check key status, api_key_update to change permissions, and api_key_delete to revoke a key.
  • Use the US API at https://api.postnuvia.com for these sign-in flows.

Install the CLI with npm install -g postnuvia-cli@latest, or the TypeScript SDK with npm install postnuvia@latest. Set POSTNUVIA_API_KEY in your environment.

postnuvia apps and client.apps arrive in the first CLI and TypeScript SDK releases after CLI 1.7.0 and TypeScript SDK 0.5.32. Earlier releases have only postnuvia providers and client.providers, which call the removed /v0/providers endpoints, so upgrade before running these examples. The Python examples call the API with httpx (pip install httpx).

Connect to an app

Find an app with List Apps or Search Apps (postnuvia apps list, postnuvia apps search --q "app name"), then pass its app_id to Connect App, POST /v0/apps/{app_id}/connect. To narrow the list to one kind of app, pass category (postnuvia apps list --category search). An app in the catalog can also be named by its slug in place of app_id (postnuvia apps connect --app-id firecrawl, POST /v0/apps/firecrawl/connect); store the app_id, the app’s permanent ID. List and search show the curated catalog only; a registered app that is not listed still connects when you have its app_id:

CLI
postnuvia apps connect \
--app-id d5e9c84f-c2b2-4bf4-b4b0-7ffd7a9ffc32 \
--inbox-id agent@example.com
import os
import uuid
import httpx
response = httpx.post(
"https://api.postnuvia.com/v0/apps/d5e9c84f-c2b2-4bf4-b4b0-7ffd7a9ffc32/connect",
headers={"Authorization": f"Bearer {os.environ['POSTNUVIA_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4())},
json={"inbox_id": "agent@example.com"},
timeout=30,
)
response.raise_for_status()
connection = response.json()
print(connection["magic_url"], connection["api_key_id"])

Open the returned magic_url in the client that will complete the sign-in. It is single-use and valid for five minutes; expires_at reports its expiry. Save api_key_id to check activation. Keep the URL private and avoid logging it in production.

The CLI supplies an idempotency key automatically and reuses it across retries. Pass --idempotency-key to reuse the same attempt across manual runs. For HTTP and SDK requests, keep the same Idempotency-Key header when retrying the same connect attempt.

You can omit inbox_id when your API key is already scoped to the inbox.

A 403 with name: "AppSignupLimitError" and code: "limit_exceeded" means the app accepts no more sign-ups from your organization; its fix says whether the limit is reached or sign-ups are paused. Connect an inbox that already has an account at the app instead. Authorize Inbox returns the same error.

Authorize a pending sign-in

If an AgentID sign-in is already waiting, use Authorize Inbox with the auth_token supplied by that sign-in. Select the inbox from your own trusted configuration.

Read auth_token only from a sign-in at exactly https://auth.agentid.com. Verify the final origin through your client, independently of any origin claimed in page content. Check the app and intended inbox before authorizing. Send your bearer API key only to https://api.postnuvia.com.

Set AGENTID_AUTH_TOKEN to that verified token, then authorize the inbox:

CLI
postnuvia inboxes authorize \
--inbox-id agent@example.com \
--auth-token "$AGENTID_AUTH_TOKEN"
import os
import httpx
response = httpx.post(
"https://api.postnuvia.com/v0/inboxes/agent%40example.com/authorize",
headers={"Authorization": f"Bearer {os.environ['POSTNUVIA_API_KEY']}"},
json={"auth_token": os.environ["AGENTID_AUTH_TOKEN"]},
timeout=30,
)
response.raise_for_status()
authorization = response.json()
print(authorization["api_key_id"], authorization["instructions"])

The response is the pending key’s api_key_id and an instructions line, Authorization complete. Return to browser. Nothing further is required from the agent: the client continues the same sign-in and activates the key. To read the key itself, including its status, use Get API Key as shown under Check activation. Repeating authorization with the same token, inbox, and bearer key returns the same api_key_id; this endpoint does not need a separate idempotency key.

Accept an app’s disclosure

To accept the app’s disclosure on the agent’s behalf, add accept_disclosure: true to the connect or authorize request. The TypeScript field is acceptDisclosure: true; the CLI flag is --accept-disclosure true.

If the app requests the owner’s name or email, the authorizing key also needs app_share_owner. Without disclosure acceptance, complete the review presented during sign-in.

Check activation

Use the api_key_id returned by either flow:

CLI
postnuvia api-keys get --api-key-id 4d795cc3-ae87-4f84-85e3-4ad4ca656f44
import os
import httpx
response = httpx.get(
"https://api.postnuvia.com/v0/api-keys/4d795cc3-ae87-4f84-85e3-4ad4ca656f44",
headers={"Authorization": f"Bearer {os.environ['POSTNUVIA_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
print(response.json().get("status"))

A single check reads the current status. Repeat with a delay while it is pending, stopping at expiry or an error. active means the key is ready to sign in as the inbox. Use List App Accounts to inspect accounts at the app; each account carries the app’s app_id and app_name. To stop an inbox from signing in at an app again, disable its account with Update Account.

Manage sign-in keys

OperationAPI pathCLI command
List public keysGET /v0/api-keys?type=public_keypostnuvia api-keys list --type public_key
List an inbox’s keysGET /v0/inboxes/{inbox_id}/api-keyspostnuvia inboxes api-keys list --inbox-id <inbox_id>
Get a keyGET /v0/api-keys/{api_key_id}postnuvia api-keys get --api-key-id <api_key_id>
Rename a keyPATCH /v0/api-keys/{api_key_id}postnuvia api-keys update --api-key-id <api_key_id> --name "agent sign-in"
Revoke or cancel a keyDELETE /v0/api-keys/{api_key_id}postnuvia api-keys delete --api-key-id <api_key_id>

List responses contain api_keys, count, and an optional next_page_token. Continue with page_token (CLI: --page-token) until no token remains, even if an intermediate page is empty.

Sign-in keys carry app_connect and app_share_owner, copied from the creating bearer key and enforced independently. Change them through Update API Key. Deleting or changing the creating bearer key does not revoke the sign-in key.

An active sign-in key expires 30 days after activation. To rotate, create and activate a replacement, then delete the old key. Deleting a pending key cancels it; deleting an active key revokes it. There is no bulk revocation endpoint.

For keys your application generates and stores itself, see AgentID Public-Key Authentication.

Credential lifetimes

A sign-in involves several objects with separate lifetimes. Ending one does not end the others.

ObjectCreated byHeld byLifetimeEnds whenUnaffected
Bearer API keyYou, in the console or with Create API KeyPostNuviaUntil deleted, or its own expires_atDELETE /v0/api-keys/{api_key_id}Every sign-in key it created
Pending sign-in keyConnect or authorize, using that bearer keyPostNuvia, until the client activates itAt most five minutes, sooner if the sign-in expiresActivation, expiry, or DELETE (cancel)Nothing depends on it yet
Active sign-in keyThe client that completed the sign-in, by activating the pending keyPostNuvia records the public key; the private material stays in that client30 days from activationExpiry, or DELETE (revoke)Tokens already issued, the remembered approval, the app’s session
Remembered approvalThe first approved sign-in of an inbox at an appAgentID180 days from the most recent sign-in, per inbox and appLapse, or the app changes its redirect URI or scopes and asks againThe app’s session, and any key
ID and access tokensAgentID, when the app redeems the authorization codeThe appTen minutes; there is no refresh tokenExpiryThe app’s session
App sessionThe app, after its callback succeedsThe appThe app’s choiceSign-out at the app, or its own expiryNothing on PostNuvia

The two long lifetimes are different objects. A 30-day sign-in key is a credential; a 180-day remembered approval is consent. A remembered approval does not prove a key is still usable, and revoking a key does not clear the approval. There is no endpoint to revoke a remembered approval.

Clear a session in the browser

The browser that completed a sign-in keeps the private half of its sign-in key as a saved session. To see and clear those sessions, open https://auth.agentid.com/sessions in that browser and choose to forget the session there. The page lists only the sessions saved in the browser that opens it, so a different browser or profile shows none.

Clearing a session there removes the sign-in material from that browser only. The key stays active on PostNuvia until it expires or you revoke it; to revoke it, delete its key with DELETE /v0/api-keys/{api_key_id} as in the table above. Apps keep the sessions they issued until those expire or you sign out there.

I revoked a key, but an agent is still signed in at an app

Revocation stops new sign-ins with that key. It does not end sessions the app already issued. AgentID sends no revocation webhook and no back-channel logout, an ID token already issued stays valid until it expires ten minutes after issue, and the app decides its own session length. Sign out at the app, or wait for that session to expire. An app that re-reads AgentID’s UserInfo endpoint inside those ten minutes sees the revocation sooner.