> ## Documentation Index
> Fetch the complete documentation index at: https://www.1password.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Get the user's approval

> Send an approval link to the person's device, wait for their decision in the 1Password app, and read which logins they granted.

export const StatusBadge = ({children}) => <span className="op-status-badge not-prose">{children}</span>;

<StatusBadge>Partner preview</StatusBadge>

Creating an access request doesn't show anything to the person. They open the approval prompt in the 1Password app on their device using the link returned with the request. After they decide, you check the request's status to learn what they granted.

## Deliver the approval link

The app link must open on the person's device, where their 1Password app runs, rather than in your agent's browser. Show an approval button in the app or chat where the person interacts with your agent, and open the link when they select it.

* **Get the link in code, never through the model.** Pass it from the create response to your app without putting it in a prompt or a model's context.
* **Treat it as sensitive.** Today the link carries your goal, reasons, keywords, and websites as readable base64url JSON. Don't log it, and send it to the person's device only over an authenticated, encrypted channel.
* **Check that your surface opens it.** On iOS and Android, open it with the system URL handler. In a web app, make it a link the person taps on a device that has 1Password. Some chat and messaging surfaces don't make `onepassword://` links tappable, so test yours.

Use the `appLink` from the `createAccessRequest` response exactly as returned. Treat it as opaque: don't parse it, change it, or build it yourself, because its format can change.

Opening the link doesn't grant access or tell you what the person decided. Check [the request's status](#wait-for-the-decision) for the outcome.

## What the person sees

1. The link opens the 1Password app. The person must be signed in to the account they connected. If the app is locked, they unlock it with Face ID, Touch ID, or their account password.
2. The app shows which integration is asking, the goal, and each website and reason, with suggested logins from the person's own vault. Suggestions are ranked by website and your keywords.
3. The person chooses which logins to grant for each entry. They can leave an entry ungranted, search for a different login, or add one you didn't ask for. Then they approve or deny the request.

Only logins in the person's own vault can be granted: Personal in an Individual account, Private in a Family account. Logins in shared vaults aren't offered.

The unlock prompt and the approval prompt each close after 2 minutes. If the person doesn't finish in time, create a new request. Opening the link again while that request is already open has no effect.

In the approval prompt, 1Password shows your integration's name and icon. The app reads them from your OAuth client registration, which is in your Business account. Approval can fail if that account is inactive. See [Go to production](/agentic-autofill/partners/production#register-your-production-oauth-client).

### Where the person can approve

| Where | Status |
| - | - |
| 1Password for Mac, Windows, and Linux | Available on the Nightly release channel, on 1password.com. |
| 1Password for iOS and Android | In development. Not available to test yet. |
| Inside your own mobile app, without switching to 1Password | In development for iOS and Android. Ask your 1Password contact if you're building a mobile app. |

## Wait for the decision

Call `api.agenticAutofill.v1.getAccessRequestStatus` with the request ID. The call returns the request's current state right away. It doesn't wait for the person to decide, so your code owns polling, backoff, and timeouts: call it again while the state is `pending`.

```javascript theme={null}
async function waitForDecision(accessRequestUUID, { timeoutMs = 180_000, intervalMs = 2_000 } = {}) {
  const deadline = Date.now() + timeoutMs;
  while (true) {
    const response = await api.agenticAutofill.v1.getAccessRequestStatus({
      accessToken,
      integrationKey,
      accessRequestUUID,
    });
    if (!response.success || response.result.state !== "pending") return response;
    if (Date.now() >= deadline) return response; // Still pending
    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }
}

const status = await waitForDecision(accessRequest.id);
```

The approval prompt closes after 2 minutes, so a local timeout of a few minutes covers a person who approves in time. Your timeout doesn't change or cancel the request: check again later to keep waiting. Back off on `rateLimitExceeded`.

You can run the loop inside the extension's service worker, as above, or in your orchestrator with one CDP call per check. Either way, keep the CDP session attached while you poll. An extension service worker can suspend after about 30 seconds of inactivity, and an attached DevTools session keeps it running.

1Password keeps the request, so you don't have to hold a browser open while the person decides. You can check the status later from a new browser session, with the same person's token and integration key.

| State | Meaning |
| - | - |
| `pending` | Still waiting for the person's decision. |
| `resolved` | The person approved at least one login. |
| `denied` | The person declined. Nothing was granted. |
| `failed` | 1Password couldn't process the request. Nothing was granted. |

`resolved`, `denied`, and `failed` are final. A request never leaves a final state, so stop checking once you see one. On `denied`, respect the person's decision and continue without those logins. On `failed`, surface the failure, and create a new request if the task still needs a login.

## Read what the person granted

A resolved status lists one entry per granted login. Each has the `entryId` it satisfies and a `reference` object, `{ reference: string }`. The reference identifies a granted login. It isn't the login itself, and you shouldn't parse it.

```javascript theme={null}
{
  success: true,
  result: {
    path: "accounts/<account>/credential-broker-access-requests/<request-id>/status",
    state: "resolved",
    resolved: [
      {
        entryId: "<entry-id>",
        reference: {
          reference: "accounts/<account>/capability/login/configurations/<configuration-id>",
        },
      },
    ],
  },
}
```

The person decides what to grant, so the result may not match your entries one to one:

* A requested entry may have no corresponding grant because the person granted nothing for it.
* A granted login may have no `entryId`, because the person added a login you didn't ask for.
* Grants may come back in a different order from your entries.

Match on `entryId`, never on position or type. Two login entries produce two login references, and only the entry ID tells them apart.

```typescript theme={null}
const granted = new Map(
  status.resolved
    .filter((r) => r.entryId)
    .map((r) => [r.entryId, r.reference]),
);

for (const entry of accessRequest.entries) {
  const reference = granted.get(entry.id);
  if (!reference) {
    // The person granted nothing for this website. Tell the agent to skip it
    // or ask the person another way.
    continue;
  }
  // Store reference with this connection, then fill with it.
}
```

For every state other than `resolved`, `resolved` is empty.

Store each reference with the connection it came from. A reference works only with the token and integration key of the connection that created the request. To fill, pass the string inside the reference object, `reference.reference`, as `resourcePath`, unchanged.

## How long a grant lasts

The person approves once per login, not once per fill. After approval, your agent can fill that login as often as the task needs, with no further prompts. A multi-page sign-in can fill the same reference on each page.

* At launch, a grant stays valid for 30 days. Configurable lifetimes are planned.
* A grant isn't tied to a chat session. If your product promises approval per session, create a new request for each session and don't reuse references from an earlier one.
* A grant ends early if you disconnect the person. When a fill fails because access ended, create a new request.


## Related topics

- [user](/cli/reference/management-commands/user.md)
- [Get a user](/users-api/get-user.md)
- [Get the User resource type](/api-reference/scim/get-the-user-resource-type.md)
