# Store credentials

Keep the service website, login and issued secrets on the same Decoy account. Use the [pairing guide](/agent-recipes.md) to connect, then check the connection's scopes and effective resource grants. A public encryption key does not grant account access. Save useful task context through [Memories](/agent-memories.md); credentials do not belong there.

## Use the same service account

Reuse an authorized account ID. To create a service address and its account, follow [Create a service account and read it back](/agent-recipes.md#5-create-one-test-address-and-read-it-back). Include the confirmed service_url and service_name, then retain the returned account_id, address ID and decoy_email in secure durable storage. Read the stored name/site through the caller's metadata_seal; a create-response echo is not independent persistence evidence. Keep secrets out of URLs and labels.

Creation access covers this connection's own accounts and addresses; token scopes still apply. Existing accounts and Personal Alias need their own grants. Creating the Decoy account is separate from completing signup at the external service. Keep the address while the service uses it for recovery or ongoing mail.

<a id="keep-saving-simple"></a>
Follow the [conversation contract](/agent-experience.md): complete an authorized save without another schema or permission interview, keep evidence internally and describe the actual result simply. Report partial completion when it affects what the person can rely on.

<a id="runtime-and-installation"></a>
<a id="access-and-what-this-version-can-verify"></a>
<a id="1-save-a-service-account-and-its-password"></a>
## Store an initial login password

Use this operation only for an authorized account with no password. A passwordless service needs no invented password. This route stores a login password; it is not an API-key or custom-field writer and does not change the password at the external service.

Run the public [agent-memory.mjs helper](/examples/agent-memory.mjs) in the agent/provider's trusted Node.js 18+ runtime. Its sealPassword function encrypts the exact password bytes locally. Only the encrypted envelope goes to Decoy; the agent/provider handling the password can read it. An HTTP-only runtime needs an existing trusted integration that runs this helper; do not send plaintext to an encryption proxy.

```js
import { sealPassword } from './agent-memory.mjs';
```

In this example, `get(path)` and `post(path, body)` are your runtime's authenticated JSON request functions against `https://api.decoys.me`. They attach the bearer token from secure storage, throw on non-success HTTP responses, and return parsed JSON. `post` sends JSON with Content-Type application/json. `accountId` is the retained authorized account ID; `password` is the actual authorized login password, kept out of chat and logs. The path is constructed from that same ID throughout.

<!-- example: save-password-http -->
```js
if (typeof password !== 'string' || password.length === 0) {
  throw new Error('An initial login password must be a non-empty string.');
}
const accountPath = `/api/agent/accounts/${encodeURIComponent(accountId)}`;
const before = await get(`${accountPath}/credential/status`);
if (before.has_password !== false) {
  throw new Error('Stop: this account is not confirmed to have no password.');
}
const { public_key } = await get('/api/agent/accounts/public-key');
const receipt = await post(`${accountPath}/credential`, {
  encrypted_password: sealPassword(password, public_key),
});
const status = await get(`${accountPath}/credential/status`);
if (receipt.has_password !== true || status.has_password !== true ||
    typeof status.password_last_changed !== 'string') {
  throw new Error('The password write and status check are not both confirmed.');
}
// Confirmed: the write was accepted and this account reports a stored password.
// This is not a decrypted content read or evidence of app visibility.
```

The successful result is a write receipt plus a separate status response with `has_password: true` and `password_last_changed`. Neither proves that the saved value equals the submitted password or that the app displays it. The created-only flow does not provide a working password read-back through this procedure. A separate, already-authorized content/key path can be used only when it actually supplies an independent fetch and decryption; do not broaden access or re-pair for verification.

The status preflight is not an atomic first-write guarantee: a second POST replaces the stored encrypted password. Do not race another writer, overwrite an existing password, or retry an ambiguous write blindly. Stop and reconcile its outcome. Do not report password recovery, later recall, app visibility or AutoFill as confirmed without evidence for that result.

<a id="2-create-a-disposable-identity-and-save-its-login"></a>
## Complete the service signup

Use the confirmed Decoy address for the authorized external signup. If the service uses a password, store its actual password on the returned account_id using the operation above. Read verification mail through the [sealed inbox flow](/agent-recipes.md#6-read-a-real-inbound-message). Check any separately issued API key according to the next section. Report external signup, password storage and API-key storage as separate results when any part is incomplete.

## 3. API keys and other secret fields

An API key belongs in a distinct named Hidden field on the same account, separate from its login password. The [account field reference](/agent-fields.md) lists the app's field types and value conventions.

The standard created-only HTTP flow and default MCP tools do not provide a supported same-account custom-field save, read or update procedure. A field's existence in the app is not an agent write method. Do not substitute a record editor that cannot preserve the exact secret and all unrelated content. A key or grant alone does not establish a supported writer.

When this connection cannot complete the API-key save:

- Report that the key could not be saved in Decoy; continue independent authorized work.
- Do not create another account for the key or put it in the login-password slot.
- Keep it out of chat, memory, notes, labels and temporary files.
- Preserve it only in already-approved secure runtime storage when available; otherwise ask the person to store it themselves without pasting it into chat.

For a manual save, first establish where the person can actually obtain the issued key through a supported private path. Give verified retrieval instructions; do not assume a dashboard exists, that it reveals the key again, or that a one-time key can be recovered later.

A blocked Decoy save does not by itself block using the issued key for the authorized task. If the trusted task runtime can use it privately, continue that work without writing the key to logs, source files or an unapproved store. Track API connection and Decoy storage as separate outcomes. Ask for a manual save only to address the unsaved key; do not present that step as unblocking an integration you have not attempted.

Do not obtain whole-vault access or pair again to bypass a missing field writer. Storing a replacement value in Decoy does not rotate or revoke it at its provider.

## 4. Save memories

Use the [memory tools](/agent-memories.md). Saving requires Save memories permission; each memory is encrypted locally with its own key. Read the saved memory independently, update its existing ID when facts change, and reuse the same connection and key in later sessions. Delete within an authorized forget request. Do not create an account named Memories or put credentials in memory text or metadata.

For a service-specific fact, the MCP app_id argument is an optional label hint. It does not create or choose an account container.

<a id="supported-value-shapes"></a>
<a id="reading-conflicts-retries-and-restart"></a>
## Confirm the result and resume safely

Use the [field reference](/agent-fields.md) for field meanings. The password operation above sends only encrypted_password; do not add invented plaintext fields or interchange its envelope with a sealed account record.

| Evidence | What it confirms |
| --- | --- |
| Successful password POST | Decoy accepted an encrypted value on that account |
| Separate credential-status GET | The account reports a password and a change timestamp |
| Independent authorized content fetch and decryption | The fetched value, if the read succeeds and matches exactly |
| Observation in the actual app or extension | The value is usable on that inspected surface |

A local input or cache is not stored-content evidence. Keep the token, refresh token, agent key and account IDs in secure durable storage; follow [connection reuse and recovery](/agent-recipes.md#7-reuse-the-connection) after a restart. Refresh when indicated. Use additional access only for the missing authorized task, not as a workaround for a missing implementation or key path. If a request times out, its outcome is unknown until reconciled; do not duplicate the account or silently overwrite data.
