Getting Started with Managed Wallet API

Deploy on Akash programmatically using the Console API with a managed wallet.

Note: The Console API is actively developed. See the changelog (https://github.com/akash-network/console/releases) for upcoming breaking changes.


Getting Started

Create an API Key

  1. Visit console.akash.network
  2. Sign in with your account
  3. If the key is for a team, switch to that organization in the top bar. A key acts in the account it was created in.
  4. Navigate to Settings → API Keys
  5. Click “Create API Key” and pick when it expires: 30 days, 90 days, 1 year or a custom date. In a team you can also pick a Project to limit the key to it.
  6. Copy and save your API key securely

Warning: An API key can do everything your role allows in the account it was created in, unless you limit it to one project. Keep it secret.

Key Expiration

Every API key expires, at most one year after it is created. A key created through POST /v1/api-keys takes an optional data.expiresAt (ISO 8601 date-time) that must be in the future and at most one year from now. Without it, the key expires one year after it is created. Asking for a later date returns 400 Bad Request.

Console emails the key’s owner up to three reminders, 30, 7 and 1 day before the key expires. After that, requests made with the key return 401 Unauthorized.

To rotate a key:

  1. Create a new key under Settings → API Keys
  2. Update your integration to use the new key and confirm it works
  3. Revoke the old key from the API keys page, or with DELETE /v1/api-keys/{id}

Authentication

All requests must include the x-api-key header. Pass your API key directly in this header.

cURL example:

Terminal window
curl https://console-api.akash.network/v1/deployments \
-H "x-api-key: YOUR_API_KEY"

If the key is missing or invalid, the API returns 401 Unauthorized:

{ "error": "Unauthorized", "message": "Invalid API key" }

An expired key also returns 401 Unauthorized, with the message API key has expired.

Security practices:

  • Store the key in an environment variable (AKASH_API_KEY), never in source code.
  • Rotate keys in Console under Settings → API Keys before they expire, and right away if a key may be compromised.
  • Keys act with your role in their account, so treat them like passwords. Limit a key to one project when it only works there.

Organizations and projects

If you belong to a team, two optional headers matter:

  • x-organization-id: the organization (id or slug) a request runs in. An API key already decides it, so you only need the header to double-check: naming another organization answers 400 with code organization_mismatch.
  • x-project-id: limits a request to one project. Lists only show that project’s deployments, and new deployments go into it.
Terminal window
curl https://console-api.akash.network/v1/deployments \
-H "x-api-key: $AKASH_API_KEY" \
-H "x-project-id: $PROJECT_ID"

See Organizations and Projects for project-bound keys, deploying into a project and the error codes.

Response envelope

Every JSON response from the Console API is wrapped in a top-level data field, for example:

{ "data": { "dseq": "1234567", "manifest": "...", "signTx": { ... } } }

All examples in this guide show the full response body — extract the value you need from response.data.

Money fields

The Managed Wallet bills your Console account in USD (credit card). You add credits to the account, and Console funds your deployments from that balance on its own. In a team, deployments are funded from the team’s own balance, not yours. Creating a deployment takes no deposit, and keeping one running takes no top-up call. See How Funding Works for the full model, or read the current platform figures from GET /v1/deployment-funding-config.

The blockchain itself works in raw on-chain denoms (uact, uusdc, …). Wherever you see price.denom / price.amount in a bid response or an escrow_account.state.funds entry, those are raw chain values in micro-units (1 ACT = 1 000 000 uact). The SDL pricing block also uses chain denoms — the managed wallet handles the USD ↔ chain conversion for you.


Complete Deployment Workflow

1. Create Deployment

POST your SDL to create a deployment. Console funds it automatically from your account balance, so the request carries no deposit. The API returns a dseq (deployment sequence number) that identifies the deployment in all subsequent calls. The response also returns the broadcast transaction hash for the on-chain MsgCreateDeployment.

cURL:

Terminal window
curl -X POST https://console-api.akash.network/v1/deployments \
-H "x-api-key: $AKASH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"sdl": "<YOUR_SDL_YAML_AS_STRING>"
}
}'

JavaScript (fetch):

const res = await fetch("https://console-api.akash.network/v1/deployments", {
method: "POST",
headers: {
"x-api-key": process.env.AKASH_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ data: { sdl: YOUR_SDL_STRING } }),
});
const { data } = await res.json();
const dseq = data.dseq;

Response (201 Created):

{
"data": {
"dseq": "1234567",
"manifest": "<computed manifest hash + manifest blob>",
"signTx": {
"code": 0,
"transactionHash": "ABCDEF...",
"rawLog": "[...]"
}
}
}

You don’t need to keep manifest. Console records the SDL and sends the provider its manifest when you create the lease.

In a team, add "projectId" to data to pick the project the deployment goes into. Without it, Console uses the only project you can deploy into (such as the project of a project-bound key or of the x-project-id header), otherwise the team’s default project. See Deploy into a project.


2. Wait for and Fetch Bids

List bids for your deployment. Bids typically arrive within 30–60 seconds. The response is wrapped in data and each bid is identified by a composite id (owner/dseq/gseq/oseq/provider/bseq), not a single opaque string.

cURL:

Terminal window
curl "https://console-api.akash.network/v1/bids?dseq=1234567" \
-H "x-api-key: $AKASH_API_KEY"

JavaScript (fetch):

const res = await fetch(
`https://console-api.akash.network/v1/bids?dseq=${dseq}`,
{ headers: { "x-api-key": process.env.AKASH_API_KEY } },
);
const { data: bids } = await res.json();

Response:

{
"data": [
{
"bid": {
"id": {
"owner": "akash1ownerxxx...",
"dseq": "1234567",
"gseq": 1,
"oseq": 1,
"provider": "akash1providerxxx...",
"bseq": 92
},
"state": "open",
"price": { "denom": "uact", "amount": "10000" },
"created_at": "92",
"resources_offer": [
{
"resources": {
"cpu": { "units": { "val": "500" } },
"memory": { "quantity": { "val": "536870912" } },
"storage": [],
"endpoints": []
},
"count": 1
}
]
},
"escrow_account": {
"id": { "scope": "bid", "xid": "..." },
"state": {
"owner": "akash1providerxxx...",
"state": "open",
"transferred": [],
"settled_at": "...",
"funds": [],
"deposits": []
}
}
}
]
}

Polling example:

async function waitForBids(dseq, { pollMs = 3000, maxAttempts = 20 } = {}) {
for (let i = 0; i < maxAttempts; i++) {
const res = await fetch(
`https://console-api.akash.network/v1/bids?dseq=${dseq}`,
{ headers: { "x-api-key": process.env.AKASH_API_KEY } },
);
const { data } = await res.json();
if (data.length > 0) return data;
await new Promise((r) => setTimeout(r, pollMs));
}
throw new Error("No bids received within timeout");
}

3. Create Lease

Accept one or more bids to activate the deployment lease(s). Console creates the leases, then sends each chosen provider the manifest built from the SDL it recorded when you created the deployment, so the request carries only the bids.

Pick the bid(s) you want, then build the leases[] array from the bid id (omitting owner and bseq). A manifest field is still accepted but deprecated, so leave it out.

cURL:

Terminal window
curl -X POST https://console-api.akash.network/v1/leases \
-H "x-api-key: $AKASH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leases": [
{ "dseq": "1234567", "gseq": 1, "oseq": 1, "provider": "akash1providerxxx..." }
]
}'

JavaScript (fetch):

const chosen = bids[0].bid.id;
const res = await fetch("https://console-api.akash.network/v1/leases", {
method: "POST",
headers: {
"x-api-key": process.env.AKASH_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
leases: [
{
dseq: chosen.dseq,
gseq: chosen.gseq,
oseq: chosen.oseq,
provider: chosen.provider,
},
],
}),
});
const { data } = await res.json();

Response (200 OK) is the same shape as GET /v1/deployments/{dseq} — the full deployment object including its now-active lease(s):

{
"data": {
"deployment": {
"id": { "owner": "akash1ownerxxx...", "dseq": "1234567" },
"state": "active",
"hash": "...",
"created_at": "92"
},
"leases": [
{
"id": {
"owner": "akash1ownerxxx...",
"dseq": "1234567",
"gseq": 1,
"oseq": 1,
"provider": "akash1providerxxx...",
"bseq": 92
},
"state": "active",
"price": { "denom": "uact", "amount": "10000" },
"created_at": "92",
"closed_on": "",
"status": null
}
],
"escrow_account": {
"id": { "scope": "deployment", "xid": "..." },
"state": {
"owner": "akash1ownerxxx...",
"state": "open",
"transferred": [],
"settled_at": "...",
"funds": [{ "denom": "uact", "amount": "5500000" }],
"deposits": []
}
}
}
}

If a provider can’t be reached, the call answers 502. With code provider_unreachable no lease was created, so pick another bid. With code manifest_not_delivered the lease exists but the provider didn’t take the manifest: send the same request again to retry, or close the deployment to stop paying for it.


4. Add Deposit to Deployment (Deprecated)

Deprecated: Console tops your deployments up automatically for as long as your account has credits, so there is nothing for this endpoint to do. It still accepts requests for existing integrations and will be removed in a future release. Add credits to your account instead. See How Funding Works.

cURL:

Terminal window
curl -X POST https://console-api.akash.network/v1/deposit-deployment \
-H "x-api-key: $AKASH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "data": { "dseq": "1234567", "deposit": 0.5 } }'

JavaScript (fetch):

const res = await fetch(
"https://console-api.akash.network/v1/deposit-deployment",
{
method: "POST",
headers: {
"x-api-key": process.env.AKASH_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ data: { dseq, deposit: 0.5 } }),
},
);
const { data } = await res.json();

Response (200 OK): the full deployment object after the top-up.

{
"data": {
"deployment": { "...": "..." },
"leases": [],
"escrow_account": { "...": "..." }
}
}

5. Close Deployment

Close a deployment. Whatever it has not yet spent returns to your Console account balance.

cURL:

Terminal window
curl -X DELETE "https://console-api.akash.network/v1/deployments/1234567" \
-H "x-api-key: $AKASH_API_KEY"

JavaScript (fetch):

const res = await fetch(
`https://console-api.akash.network/v1/deployments/${dseq}`,
{
method: "DELETE",
headers: { "x-api-key": process.env.AKASH_API_KEY },
},
);
const { data } = await res.json();

Response (200 OK):

{ "data": { "success": true } }

Settlement happens on-chain, so the credited amount takes a short while to appear. Call GET /v1/deployments/{dseq} afterward to inspect the final state, or check your balance in the Console UI.


Complete Example Script

This example runs the full managed-wallet deployment flow in one script.

const API_BASE_URL = "https://console-api.akash.network";
const API_KEY = process.env.AKASH_API_KEY;
if (!API_KEY) {
throw new Error("Set AKASH_API_KEY before running this script.");
}
const SDL = `version: "2.0"
services:
web:
image: nginx:stable
expose:
- port: 80
as: 80
to:
- global: true
profiles:
compute:
web:
resources:
cpu:
units: 0.5
memory:
size: 512Mi
storage:
- size: 512Mi
placement:
dcloud:
pricing:
web:
denom: uact
amount: 10000
deployment:
web:
dcloud:
profile: web
count: 1`;
async function apiRequest(path, options = {}) {
const res = await fetch(`${API_BASE_URL}${path}`, {
...options,
headers: {
"x-api-key": API_KEY,
"Content-Type": "application/json",
...(options.headers || {}),
},
});
if (!res.ok) {
const body = await res.text();
throw new Error(`HTTP ${res.status}: ${body}`);
}
return res.json();
}
async function waitForBids(dseq, { pollMs = 3000, maxAttempts = 20 } = {}) {
for (let i = 0; i < maxAttempts; i++) {
const { data } = await apiRequest(`/v1/bids?dseq=${dseq}`);
if (data.length > 0) return data;
await new Promise((resolve) => setTimeout(resolve, pollMs));
}
throw new Error("No bids received within timeout");
}
async function main() {
const create = await apiRequest("/v1/deployments", {
method: "POST",
body: JSON.stringify({ data: { sdl: SDL } }),
});
const { dseq, manifest } = create.data;
const bids = await waitForBids(dseq);
const chosenId = bids[0].bid.id;
await apiRequest("/v1/leases", {
method: "POST",
body: JSON.stringify({
manifest,
leases: [
{
dseq: chosenId.dseq,
gseq: chosenId.gseq,
oseq: chosenId.oseq,
provider: chosenId.provider,
},
],
}),
});
const status = await apiRequest(`/v1/deployments/${dseq}`);
console.log("Deployment status:", status);
const closed = await apiRequest(`/v1/deployments/${dseq}`, {
method: "DELETE",
});
console.log("Closed deployment:", closed);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

Confidential Compute (TEE)

Run your workload inside a hardware-isolated Trusted Execution Environment (TEE) by adding a single params.tee field to a service in your SDL. Set it to cpu for a CPU-only confidential VM, or cpu-gpu to combine confidential compute with GPU acceleration. The provider selects the underlying hardware platform (AMD SEV-SNP or Intel TDX) and handles attestation automatically — you only choose the TEE option, nothing else.

Confidential Compute is requested entirely through the SDL, so there are no extra API parameters: pass the SDL below as data.sdl on POST /v1/deployments exactly as in Step 1.

CPU-only Confidential Compute

version: "2.1"
services:
web:
image: nginx:stable
expose:
- port: 80
as: 80
to:
- global: true
params:
tee: cpu
profiles:
compute:
web:
resources:
cpu:
units: 0.5
memory:
size: 512Mi
storage:
- size: 512Mi
placement:
dcloud:
pricing:
web:
denom: uact
amount: 10000
deployment:
web:
dcloud:
profile: web
count: 1

GPU + Confidential Compute

The cpu-gpu option requires GPU resources in the compute profile — a cpu-gpu deployment without a gpu block is rejected.

version: "2.1"
services:
inference:
image: my-model:latest
expose:
- port: 8080
as: 8080
to:
- global: true
params:
tee: cpu-gpu
profiles:
compute:
inference:
resources:
cpu:
units: 8
memory:
size: 32Gi
storage:
- size: 100Gi
gpu:
units: 1
attributes:
vendor:
nvidia:
- model: h100
placement:
dcloud:
pricing:
inference:
denom: uact
amount: 100000
deployment:
inference:
dcloud:
profile: inference
count: 1

Warning: TEE services can only pull images from public registries. The credentials field is not honored for confidential workloads, and a deployment that references a private image will fail. Make sure every image in a TEE service is publicly pullable.

TEE type reference

ValueDescription
cpuCPU-only confidential VM
cpu-gpuConfidential VM with NVIDIA GPU CC (requires GPU resources)

The provider selects the actual hardware platform (AMD SEV-SNP or Intel TDX) and the runtime automatically at deployment time — you do not choose or configure it.

All services within the same deployment group must use the same TEE type (or none at all); mixing TEE types in a single group is rejected.

For attestation, the security model, and supported hardware, see the Confidential Compute (TEE) core concepts and the SDL advanced features reference.


Best Practices

Security

  • Never commit API keys to version control
  • Use environment variables for API keys
  • Rotate keys regularly as a security best practice
  • Limit a key to one project when it only deploys there
// Good: use environment variables
const API_KEY = process.env.AKASH_API_KEY;
// Bad: hardcoded key
const API_KEY = "ac.sk.mainnet.xxx...";

Error Handling

Use HTTP status codes to determine recovery behavior.

StatusMeaningCommon cause
200 OKRequest succeeded-
201 CreatedResource createdPOST /v1/deployments, POST /v2/deployment-settings
400 Bad RequestInvalid inputMalformed SDL, missing required field
401 UnauthorizedAuth failedMissing, invalid or expired x-api-key
403 ForbiddenNot allowedYour role, the key’s project or a project budget does not allow it
404 Not FoundResource not founddseq does not exist or is out of your reach
429 Rate LimitedRate limitedPolling interval too fast or burst of requests
500 Internal Server ErrorServer errorRetry with exponential backoff

Error response shape. When the API knows the precise reason, code carries it:

{
"error": "ForbiddenError",
"message": "The project is not reachable in this organization",
"code": "project_forbidden",
"type": "client_error"
}

The organization and project codes are organization_forbidden, organization_mismatch, project_forbidden, project_required, project_budget_exceeded, project_limit_reached, organization_limit_reached and session_required. See Errors for what each one means.

Polling for Bids

Poll every 3 seconds for 30–60 seconds, then back off or return a timeout error.


Managed Wallet vs SDK

FeatureManaged Wallet APIAkash SDK
Wallet ManagementManaged by ConsoleYou manage wallet
AuthenticationAPI KeyPrivate key/mnemonic
PaymentCredit card (USD)Crypto (AKT)
API TypeREST APINative blockchain
LanguageAny (HTTP)Go, TypeScript
SetupAPI key onlyWallet + blockchain setup
Best ForSaaS, web appsBlockchain apps, CLI tools

Limitations

  • Payment method: Credit card only. Existing wallets cannot be linked to a Managed Wallet account at this time.
  • Wallet access: Console manages the wallet. You cannot export private keys or sign arbitrary transactions.
  • API stability: Pin integrations to v1 or v2. Versions are independent, and breaking changes are announced in the changelog (https://github.com/akash-network/console/releases).
  • For production workloads without managed wallet constraints: use the Akash SDK or CLI with your own wallet.

Resources


Need Help?