> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://www.openfort.io/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

# Backend Wallets

Server-side wallets your backend controls, for automated signing and treasury operations.

## Create backend wallet

`POST /v2/accounts/backend`

Create a new backend wallet account.

Generates a new keypair securely in the backend wallet system.
The private key is stored encrypted and never exposed: Openfort holds it (`custody: Developer`)
and your backend signs through this API. For accounts controlled by a user's own signer, use User Accounts.

### Request body (required) (`application/json`)

- `chainType` `string` _(required)_: The chain type for the new wallet.
- `wallet` `string`: ID of the wallet (starts with `pla_`) to group the new account under. Omit to create a new wallet for it.

### Responses

#### `200`: The created backend wallet.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `id` `string` _(required)_: The created account ID (starts with `acc_`).
- `address` `string` _(required)_: The wallet address generated for this account.
- `walletId` `string` _(required)_: ID of the wallet (starts with `pla_`) this account is grouped under.
- `chainType` `string` _(required)_: The chain type the wallet is associated with.
- `createdAt` `integer <int32>` _(required)_: Creation timestamp (Unix epoch seconds).

#### `401`: Missing or invalid credentials.

### Example request

```bash
curl https://api.openfort.io/v2/accounts/backend \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "chainType": "EVM",
  "wallet": "pla_e0b84653-1741-4a3d-9e91-2b0fd2942f60"
}'
```

```ts
fetch('https://api.openfort.io/v2/accounts/backend', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    chainType: 'EVM',
    wallet: 'pla_e0b84653-1741-4a3d-9e91-2b0fd2942f60'
  })
})
```

## Delete backend wallet

`DELETE /v2/accounts/backend/{id}`

Delete a backend wallet.

Permanently deletes the backend wallet account and the private key Openfort holds for it.

### Path parameters

- `id` `string` _(required)_: The account ID (starts with `acc_`).

### Responses

#### `200`: The deleted backend wallet.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `id` `string` _(required)_: The deleted account ID (starts with `acc_`).
- `deleted` `boolean` _(required)_: Always `true`: the account was deleted.

#### `401`: Missing or invalid credentials.

#### `404`: Wallet not found.

### Example request

```bash
curl https://api.openfort.io/v2/accounts/backend/acc_e1b24353-1741-4a3d-9e91-2b0fd2942f60 \
  --request DELETE
```

```ts
fetch('https://api.openfort.io/v2/accounts/backend/acc_e1b24353-1741-4a3d-9e91-2b0fd2942f60', {
  method: 'DELETE'
})
```

## Sign data via backend wallet

`POST /v2/accounts/backend/{id}/sign`

Sign data via backend wallet.

Signs the provided data using the account's private key managed by the backend wallet.
The private key is securely stored and never exposed.

### Path parameters

- `id` `string` _(required)_: The account ID (starts with acc_).

### Request body (required) (`application/json`)

- `data` `string` _(required)_: The data to sign, hex-encoded. EVM wallets: a serialized transaction, an EIP-191 message preimage, an EIP-712 preimage, or a 32-byte hash. Solana wallets: the compiled transaction message bytes (what the SDK sends), a full wire-format transaction (only its message bytes are signed), or arbitrary message bytes. The detected kind selects the policy operation (`signEvmTransaction`, `signSolTransaction`, `signSolMessage`, ...).

### Responses

#### `200`: The signature.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `account` `string` _(required)_: The account ID that signed the data (starts with `acc_`).
- `signature` `string` _(required)_: The signature bytes (hex-encoded).

#### `401`: Missing or invalid credentials.

#### `404`: Account not found.

### Example request

```bash
curl https://api.openfort.io/v2/accounts/backend/acc_e1b24353-1741-4a3d-9e91-2b0fd2942f60/sign \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "data": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'
```

```ts
fetch('https://api.openfort.io/v2/accounts/backend/acc_e1b24353-1741-4a3d-9e91-2b0fd2942f60/sign', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    data: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  })
})
```

## Export private key (E2E encrypted)

`POST /v2/accounts/backend/{id}/export`

Export private key with E2E encryption via backend wallet.

Exports the account's private key encrypted using RSA-4096 OAEP SHA-256.
The client must provide their ephemeral RSA-4096 public key (base64 SPKI DER format).
The response contains the encrypted private key that can be decrypted with the client's private key.

### Path parameters

- `id` `string` _(required)_: The account ID (starts with acc_).

### Request body (required) (`application/json`)

- `encryptionKey` `string` _(required)_: Client's ephemeral RSA-4096 public key for end-to-end encryption (base64 SPKI DER format). The backend wallet will encrypt the private key using RSA-OAEP SHA-256.

### Responses

#### `200`: The encrypted private key.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `encryptedPrivateKey` `string` _(required)_: The private key encrypted with RSA-OAEP SHA-256 using your ephemeral public key (base64-encoded). Decrypt using your ephemeral RSA private key.

#### `401`: Missing or invalid credentials.

#### `404`: Account not found.

### Example request

```bash
curl https://api.openfort.io/v2/accounts/backend/acc_e1b24353-1741-4a3d-9e91-2b0fd2942f60/export \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "encryptionKey": "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA..."
}'
```

```ts
fetch('https://api.openfort.io/v2/accounts/backend/acc_e1b24353-1741-4a3d-9e91-2b0fd2942f60/export', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    encryptionKey: 'MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA...'
  })
})
```

## Import private key (E2E encrypted)

`POST /v2/accounts/backend/import`

Import private key with E2E encryption via backend wallet.

Imports a private key into the backend wallet system.
The private key must be encrypted using RSA-4096 OAEP SHA-256 with the server's
static import public key (obtain out-of-band from SDK or documentation).

### Request body (required) (`application/json`)

- `encryptedPrivateKey` `string` _(required)_: The private key encrypted with RSA-OAEP SHA-256 using the server's static import public key. Obtain the server's import public key out-of-band (e.g., from SDK or documentation).
- `chainType` `string`: The chain type for the imported wallet.
- `wallet` `string`: ID of the wallet (starts with `pla_`) to group the new account under. Omit to create a new wallet for it.

### Responses

#### `200`: The imported backend wallet.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `id` `string` _(required)_: The created account ID (starts with `acc_`).
- `address` `string` _(required)_: The wallet address derived from the imported private key.
- `walletId` `string` _(required)_: ID of the wallet (starts with `pla_`) this account is grouped under.
- `chainType` `string`: The chain type the wallet is associated with.
- `createdAt` `integer <int32>` _(required)_: Creation timestamp (Unix epoch seconds).

#### `400`: Invalid key material.

#### `401`: Missing or invalid credentials.

### Example request

```bash
curl https://api.openfort.io/v2/accounts/backend/import \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "encryptedPrivateKey": "base64_encrypted_data...",
  "chainType": "EVM",
  "wallet": "pla_e0b84653-1741-4a3d-9e91-2b0fd2942f60"
}'
```

```ts
fetch('https://api.openfort.io/v2/accounts/backend/import', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    encryptedPrivateKey: 'base64_encrypted_data...',
    chainType: 'EVM',
    wallet: 'pla_e0b84653-1741-4a3d-9e91-2b0fd2942f60'
  })
})
```

## Register wallet secret

`POST /v2/accounts/backend/register-secret`

Register a new wallet secret (authentication key).

Registers an ECDSA P-256 public key that will be used to verify
X-Wallet-Auth JWT signatures. This is required before using WALLET_AUTH
for other backend wallet operations.

Uses provided-key authentication: the request must include a walletAuthToken
JWT signed by the private key corresponding to the publicKey being registered.
This proves possession of the private key without transmitting it.

Note: Only ONE active secret is allowed per project. This call fails if an
active secret already exists; use rotateWalletSecret to replace an existing secret.

### Request body (required) (`application/json`)

- `publicKey` `string` _(required)_: ECDSA P-256 public key for wallet authentication (PEM format). This will be used to verify X-Wallet-Auth JWT signatures.
- `walletAuthToken` `string` _(required)_: JWT signed with the private key corresponding to publicKey. This proves possession of the private key without transmitting it. JWT must include: uris (matching request path), reqHash (SHA-256 of request body), iat (issued at), nbf (not before), and optionally exp (expiration).
- `keyId` `string`: Key identifier for the secret. Used to identify this key in X-Wallet-Auth JWT headers.

### Responses

#### `200`: The registered wallet secret.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `keyId` `string` _(required)_: The key ID for the registered secret.
- `registeredAt` `integer <int32>` _(required)_: Timestamp when the secret was registered (Unix epoch seconds).

#### `401`: Missing or invalid credentials.

### Example request

```bash
curl https://api.openfort.io/v2/accounts/backend/register-secret \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...",
  "walletAuthToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
  "keyId": "ws_1234567890"
}'
```

```ts
fetch('https://api.openfort.io/v2/accounts/backend/register-secret', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    publicKey: '-----BEGIN PUBLIC KEY-----
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...',
    walletAuthToken: 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...',
    keyId: 'ws_1234567890'
  })
})
```

## Revoke wallet secret

`POST /v2/accounts/backend/revoke-secret`

Revoke a wallet secret (authentication key).

Permanently revokes a wallet secret so it can no longer be used
for X-Wallet-Auth JWT signing.

### Request body (required) (`application/json`)

- `keyId` `string` _(required)_: Key identifier of the secret to revoke.

### Responses

#### `200`: The revoked wallet secret.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `keyId` `string` _(required)_: The key ID of the revoked secret.
- `revoked` `boolean` _(required)_: Whether the secret was successfully revoked.
- `revokedAt` `integer <int32>` _(required)_: Timestamp when the secret was revoked (Unix epoch seconds).

#### `401`: Missing or invalid credentials.

#### `404`: Secret not found.

### Example request

```bash
curl https://api.openfort.io/v2/accounts/backend/revoke-secret \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "keyId": "ws_1234567890"
}'
```

```ts
fetch('https://api.openfort.io/v2/accounts/backend/revoke-secret', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    keyId: 'ws_1234567890'
  })
})
```

## Rotate wallet secret

`POST /v2/accounts/backend/rotate-secrets`

Rotate wallet secret (authentication key).

Replaces the current wallet secret (ECDSA P-256 public key) used for
X-Wallet-Auth JWT signing. The old secret will be marked as "rotated"
and immediately becomes unusable (no grace period).

Uses provided-key authentication: the request must include a walletAuthToken
JWT signed by the private key corresponding to the NEW publicKey being registered.
This proves possession of the new private key without transmitting it.

Requires an AAL2 session (MFA step-up). Users must enroll MFA and
complete step-up authentication before calling this endpoint.

### Request body (required) (`application/json`)

- `newPublicKey` `string` _(required)_: New ECDSA P-256 public key for wallet authentication (PEM format). This will replace the current wallet secret used for X-Wallet-Auth JWT signing.
- `walletAuthToken` `string` _(required)_: JWT signed with the private key corresponding to newPublicKey. This proves possession of the private key without transmitting it. JWT must include: uris (matching request path), reqHash (SHA-256 of request body), iat (issued at), nbf (not before), and optionally exp (expiration).
- `newKeyId` `string`: Key identifier for the new secret. Used to identify this key in X-Wallet-Auth JWT headers.

### Responses

#### `200`: The rotated wallet secrets.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `success` `boolean` _(required)_: Whether the rotation was successful.
- `rotatedAt` `integer <int32>` _(required)_: Timestamp when the rotation occurred (Unix epoch seconds).

#### `401`: Missing or invalid credentials.

#### `403`: MFA required.

### Example request

```bash
curl https://api.openfort.io/v2/accounts/backend/rotate-secrets \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{
  "newPublicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...",
  "walletAuthToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
  "newKeyId": "ws_1234567890"
}'
```

```ts
fetch('https://api.openfort.io/v2/accounts/backend/rotate-secrets', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    newPublicKey: '-----BEGIN PUBLIC KEY-----
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...',
    walletAuthToken: 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...',
    newKeyId: 'ws_1234567890'
  })
})
```
