> **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.

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

- `chainType` `string` _(required)_: The chain type for the new wallet.
- `wallet` `string`: The wallet ID to associate with this wallet (starts with `pla_`).

### Responses

#### `200`: Successful response.

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)_: The wallet ID
- `chainType` `string` _(required)_: The chain type the wallet is associated with.
- `createdAt` `integer <int32>` _(required)_: Creation timestamp (Unix epoch seconds).

#### `401`: Unauthorized.

### 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 a backend wallet and its associated private key.

### Path parameters

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

### Responses

#### `200`: Successful response.

Body (`application/json`):

- `object` `string` _(required)_: The type of object.
- `id` `string` _(required)_: The deleted wallet ID.
- `deleted` `boolean` _(required)_: Whether the wallet was deleted.

#### `401`: Unauthorized.

#### `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 transaction data or message hash).

### Responses

#### `200`: Successful response.

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`: Unauthorized.

#### `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`: Successful response.

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`: Unauthorized.

#### `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`: The wallet ID to associate with this wallet (starts with `pla_`).

### Responses

#### `200`: Successful response.

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)_: The wallet ID
- `chainType` `string`: The chain type the wallet is associated with.
- `createdAt` `integer <int32>` _(required)_: Creation timestamp (Unix epoch seconds).

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

#### `401`: Unauthorized.

### 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`: Successful response.

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`: Unauthorized.

### 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`: Successful response.

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`: Unauthorized.

#### `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`: Successful response.

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`: Unauthorized.

#### `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'
  })
})
```
