REST API
HTTP API for jobs, models, hosts, wallets, and API keys. Base URL: https://metalcloud.space/api/v1.
Authentication
Send an API key as a bearer token. Keys start with mc_ and are created in the API keys dashboard. The full key is returned once, on create.
Authorization: Bearer mc_your_key
Job routes also accept the same key in X-API-Key. Login returns a JWT you can send the same way. A missing or unknown token returns 401.
{
"success": false,
"message": "Invalid or expired token"
}Jobs
/api/v1/jobs is the customer job API. Create a job with the Python client or with JSON.
from metalcloud import Client client = Client(api_key="mc_...") job = client.jobs.submit( type="inference", model_id="mlx-community/Llama-3.2-1B", input={"prompt": "Explain unified memory in one sentence."}, priority="normal", min_memory_gb=16, ) result = job.wait()
model_id must be a repo installed on a host you can reach. See the model library. The example id is the shape hosts report. It is not a promise that every Mac has that model cached.
| Method | Path | Purpose |
|---|---|---|
| POST | /jobs | Submit a job |
| GET | /jobs | List your jobs. Query: status, type, limit (max 100), offset |
| GET | /jobs/{uuid} | Job status, host, billing, input, and output |
| POST | /jobs/estimate | Cost estimate before you submit |
| GET | /jobs/queue | Queue status |
| POST | /jobs/{uuid}/cancel | Cancel a pending, queued, assigned, or running job |
| POST | /jobs/{uuid}/retry | Submit a cancelled job again, or a failed inference job |
| GET | /jobs/{uuid}/logs | Logs. Query: level, limit (max 500) |
| GET | /jobs/{uuid}/output | Output once the job is completed or failed |
Create body
| Field | Rules |
|---|---|
type | inference, training, fine_tuning, batch, or container. Default inference. Customers normally send inference. |
model_id | Up to 255 characters. Letters, numbers, and - _ / : . |
input | JSON object, 10 MB maximum. Put prompt and optional max_tokens here. |
priority | low, normal, high, or critical |
min_memory_gb | 8 to 512 |
min_gpu_cores | 8 to 80 |
max_duration_minutes | 1 to 1440. Default 60 |
host_uuids | Optional list of host UUIDs, 50 maximum |
required_chip | Optional chip, for example M3_Ultra or M5_Max |
Send X-Idempotency-Key (16 to 64 characters) to make a retry return the job created in the last 24 hours instead of a second job. For inference and batch, a max_tokens value equal to the model context length is dropped, and any larger value is cut to the job duration at 25 tokens per second.
A successful create returns success: true and data.job with uuid, type, priority, and status (assigned when a host is free, otherwise queued), plus data.estimate. Status values are pending, queued, assigned, running, completed, failed, cancelled, and timeout.
You can have 10 jobs in pending, queued, assigned, or running at once. Limits per hour: 60 submits, 300 lists, 2,400 status reads, 30 cancels. No matching host returns 503. A balance below the maximum estimate returns 402 with required and available.
Models
These routes need a bearer token. hosts is a comma-separated list of host UUIDs, 50 maximum. Details are in the model library.
| Method | Path | Purpose |
|---|---|---|
| GET | /models?hosts={uuids} | Models cached on those hosts while they are online |
| GET | /models/search?hosts={uuids}&q= | Search public MLX repos. q is optional and at most 200 characters |
| GET | /models/context?repo_id= | Context length published in the model config. 404 when the model does not publish one |
| POST | /hosts/{uuid}/model-downloads | Body {"repo_id": "org/name"}. Starts a download on that host |
| GET | /hosts/{uuid}/model-downloads | Active downloads for a host |
| GET | /model-downloads/{download_id} | One download |
| POST | /model-downloads/{download_id}/cancel | Cancel a download |
| POST | /hosts/{uuid}/model-removals | Body {"repo_id": "org/name"}. Deletes that model on the host when you downloaded the copy that is installed there |
| GET | /hosts/{uuid}/model-removals | Your active removals on that host |
| GET | /model-removals/{removal_id} | One of your removals |
Hosts
GET /hosts lists machines with a heartbeat in the last two minutes. GET /hosts/{uuid} returns one active host, including chip_model, memory_gb, gpu_cores, and hourly_rate_gbp. POST /hosts registers a machine and needs a bearer token. Required fields: name, chip_model, memory_gb, gpu_cores, cpu_cores, hourly_rate_gbp.
Wallet and account
| Method | Path | Purpose |
|---|---|---|
| GET | /health | No auth. status, version, database state |
| POST | /auth/register | email, password, name, optional role (customer, host, or both) |
| POST | /auth/login | email and password. Returns a token |
| POST | /auth/forgot-password | Start a password reset |
| POST | /auth/reset-password | Finish a password reset |
| GET | /auth/me | The signed-in account |
| GET | /wallet | balance, pending, lifetime_earned, lifetime_spent, in GBP |
| GET | /wallet/transactions | Recent transactions |
| GET | /dashboard/stats | Job counts, compute hours, balance, and spend |
Add funds in the billing dashboard. Rates are on the billing page.
API keys
| Method | Path | Purpose |
|---|---|---|
| GET | /api-keys | List keys. Secrets are not returned |
| POST | /api-keys | Body {"name": "my-app"}. The token is shown once |
| DELETE | /api-keys/{id} | Revoke a key |
Host dashboard
These routes need a host or admin account.
| Method | Path | Purpose |
|---|---|---|
| GET | /host/machines | Macs linked to the account, including online state |
| GET | /host/stats | Host totals |
| GET | /host/jobs | Jobs run on your machines |
| GET | /host/earnings | Earnings. Optional month=YYYY-MM |
| GET | /host/payouts | Payout history |
| POST | /host/payouts | Request a payout |
Machine setup for hosts is in host setup.