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
- Visit console.akash.network
- Sign in with your account
- 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.
- Navigate to Settings → API Keys
- 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.
- 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:
- Create a new key under Settings → API Keys
- Update your integration to use the new key and confirm it works
- 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:
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 answers400with codeorganization_mismatch.x-project-id: limits a request to one project. Lists only show that project’s deployments, and new deployments go into it.
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:
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:
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:
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:
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:
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: trueprofiles: compute: web: resources: cpu: units: 0.5 memory: size: 512Mi storage: - size: 512Mi placement: dcloud: pricing: web: denom: uact amount: 10000deployment: 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: cpuprofiles: compute: web: resources: cpu: units: 0.5 memory: size: 512Mi storage: - size: 512Mi placement: dcloud: pricing: web: denom: uact amount: 10000deployment: web: dcloud: profile: web count: 1GPU + 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-gpuprofiles: 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: 100000deployment: inference: dcloud: profile: inference count: 1Warning: TEE services can only pull images from public registries. The
credentialsfield is not honored for confidential workloads, and a deployment that references a private image will fail. Make sure everyimagein a TEE service is publicly pullable.
TEE type reference
| Value | Description |
|---|---|
cpu | CPU-only confidential VM |
cpu-gpu | Confidential 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 variablesconst API_KEY = process.env.AKASH_API_KEY;
// Bad: hardcoded keyconst API_KEY = "ac.sk.mainnet.xxx...";Error Handling
Use HTTP status codes to determine recovery behavior.
| Status | Meaning | Common cause |
|---|---|---|
| 200 OK | Request succeeded | - |
| 201 Created | Resource created | POST /v1/deployments, POST /v2/deployment-settings |
| 400 Bad Request | Invalid input | Malformed SDL, missing required field |
| 401 Unauthorized | Auth failed | Missing, invalid or expired x-api-key |
| 403 Forbidden | Not allowed | Your role, the key’s project or a project budget does not allow it |
| 404 Not Found | Resource not found | dseq does not exist or is out of your reach |
| 429 Rate Limited | Rate limited | Polling interval too fast or burst of requests |
| 500 Internal Server Error | Server error | Retry 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
| Feature | Managed Wallet API | Akash SDK |
|---|---|---|
| Wallet Management | Managed by Console | You manage wallet |
| Authentication | API Key | Private key/mnemonic |
| Payment | Credit card (USD) | Crypto (AKT) |
| API Type | REST API | Native blockchain |
| Language | Any (HTTP) | Go, TypeScript |
| Setup | API key only | Wallet + blockchain setup |
| Best For | SaaS, web apps | Blockchain 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
v1orv2. 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
- API Reference - Complete endpoint documentation
- Quickstart - End-to-end deployment in five API calls
- Organizations and Projects - Teams, projects and project-bound keys through the API
- SDL Reference - SDL syntax guide
- Console Documentation - Console overview
- Confidential Compute (TEE) - Attestation and security model
Need Help?
- Discord: discord.akash.network
- GitHub Issues: console/issues
- Support: GitHub Support