GPUs Pricing AI by Token Templates Blog API Contact Login Get Started

API Reference

Integrate top-tier GPUs into your application. Simple, fast, and reliable.

The API itself has no subscription: you pay for the GPU time you use, per hour, or per inference token consumed, always in Brazilian reais. Create your account, generate an API key, and get started in seconds.

Base URL

All endpoints live under the base URL below and respond in JSON:

https://gpubrazil.com

Authentication

Authenticate with an API key in the Authorization header. The key never expires and can be revoked at any time from your dashboard. Treat it like a password — anyone with the key can create and delete instances in your account.

Authorization: Bearer gpub_live_yourkeyhere

Alternatively, you can send the key in the x-api-key header.

Generate your API key in the dashboard

For security, API keys are generated and managed inside your account — in the API Keys section of the dashboard. The plaintext key is shown only once, in the authenticated area.

Open dashboard → API Keys

Endpoints

Full workflow

An instance's lifecycle is: choose the GPU → create → check status/connection → (optional) stop/start → delete. In the examples below, set your key in an environment variable:

export GPUB_API_KEY="gpub_live_yourkeyhere"

1. List GPUs and prices

Returns the catalog with the price per hour (in R$) and the gpu_key you use to create the instance. No authentication required.

curl -s "https://gpubrazil.com/api/gpus"

Resposta (resumo):
{
  "gpus": [
    { "model": "NVIDIA H100 PCIe 80GB", "gpu_key": "premium_H100-80G-PCIe",
      "pricePerHourBrl": 19.88, "provider": "premium" },
    { "model": "RTX 4090", "gpu_key": "economic_RTX_4090",
      "pricePerHourBrl": 3.34, "provider": "economic" }
  ]
}

Tip: use /api/gpus/available to also see the quantity available in real time.

Only the GPUs compatible with a template

1-click templates don't run on every machine: each one needs a minimum amount of video memory, and some only run on a Dedicated virtual machine instance. Add ?template=<id> to /api/gpus and the catalog comes back filtered, by the same rules the deploy applies.

curl -s "https://gpubrasil.com.br/api/gpus?template=llama-factory"

Response (excerpt):
{
  "template": { "id": "llama-factory", "name": "LLaMA-Factory",
                "minVramGb": 16, "diskGb": 80, "requiresVm": false },
  "gpus": [
    { "model": "RTX 4090", "gpu_key": "economic_RTX_4090", "gpuRamGb": 24,
      "pricePerHourBrl": 3.34, "templateMinGpuCount": 1 }
  ]
}

Template ids come from GET /api/templates. templateMinGpuCount is the minimum number of GPUs to request on deploy (the gpuCount field) for the template to fit — 1 for most, more than 1 for large models, which add up the memory across cards. An unknown id returns 400 with TEMPLATE_NOT_FOUND.

Filtering the catalogue

The catalogue has hundreds of entries. Instead of downloading it all and choosing in your code, ask for it already filtered:

# 4090 under R$ 4/h in the Americas, cheapest first
curl -s "https://gpubrazil.com/api/gpus?model=4090&max_price_brl=4®ion=NA&sort=price&limit=5"

# any card with 80 GB of video memory or more, Dedicated tier
curl -s "https://gpubrazil.com/api/gpus?min_vram_gb=80&tier=dedicada&sort=price"
ParameterWhat it does
tierdedicada, economica, spot (comma-separated list accepted).
model / qPart of the model name (4090, h100).
max_price_brl / min_price_brlHourly price ceiling and floor, in BRL.
min_vram_gbMinimum video memory per card.
regionMacro-region: NA (North America), EU, AP (Asia-Pacific) or GL (any region).
gpu_countHow many cards you want. Hides models that only ship in larger blocks — so you don't discover the restriction at deploy time.
availabletrue keeps only what has a free unit right now.
supports_templates / video_encoderOnly machines that run 1-click templates / that have a video encoder (streaming and video work).
sort / order / limitSort by price, vram or model; order=desc reverses; limit trims the list.

With a filter, the response gains count (what is left) and total (the catalogue size). Without filters the format is unchanged. Price and availability move during the day: query close to the moment you create the machine, not once a week.

2. Create an instance

Use the gpuModel = gpu_key obtained in step 1. The deploy runs in the background; the response comes back immediately with an instanceId and status creating.

curl -X POST "https://gpubrazil.com/api/instances/deploy" \
  -H "Authorization: Bearer $GPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "minha-vm",
    "gpuModel": "premium_H100-80G-PCIe",
    "vcpuCount": 8,
    "ramGb": 64,
    "storageGb": 100
  }'

Response:
{
  "success": true,
  "instance": { "instanceId": "abc123", "name": "minha-vm", "status": "creating" },
  "chargedAmount": 19.88,
  "currency": "R$",
  "message": "Instance is being created..."
}
FieldDefaultWhat it is
gpuModel (required)The gpu_key from the catalogue.
namegeneratedThe name you will operate the machine by. Up to 64 characters. See Manage by name.
gpuCount1How many cards. Some models only ship in blocks — the catalogue states the minimum in minGpuCount.
vcpuCount · ramGb · storageGb4 · 16 · 100Accepted minimums: 2 vCPU, 8 GB RAM, 40 GB disk.
sshKey or sshKeyNameThe full public key, or the name of one already registered. This is your way into the machine.
templateId1-click template. Use /api/gpus?template=<id> to see where it runs.
diskIdPersistent Disk to attach. Takes the id or name:<disk name>.
metadataYour labels (flat JSON object): job id, batch, environment. They double as list filters.
requireUniqueNamefalsetrue refuses creation (409 NAME_IN_USE, no charge) if an active machine already has that name.

The response returns instanceId immediately — keep it, or use the name you chose. The chargedAmount value is the prepaid first hour: the next charge only happens if the machine outlives it. If provisioning fails, that amount returns to your balance automatically.

3. Status and SSH connection details

Query by instanceId — or by the name you gave it, with name:<name>. When the status becomes running, the connection object provides the IP, port, and a ready-to-use SSH command (already with the correct login user for that machine).

curl -s "https://gpubrazil.com/api/instances/abc123" \
  -H "Authorization: Bearer $GPUB_API_KEY"

Response:
{
  "id": "abc123",
  "name": "minha-vm",
  "status": "running",
  "gpu_model": "premium_H100-80G-PCIe",
  "connection": {
    "ip": "203.0.113.42",
    "port": 22,
    "user": "ubuntu",
    "sshCommand": "ssh -i ~/.ssh/your_key -p 22 ubuntu@203.0.113.42"
  },
  "resources": { "vcpuCount": 8, "ramGb": 64, "storageGb": 100 },
  "pricing": { "hourlyRateBrl": 19.88 }
}

4. List and filter your instances

The listing takes filters. If you run dozens of machines at once, ask for only what matters instead of downloading everything and searching in your own code.

# everything
curl -s "https://gpubrazil.com/api/instances" -H "Authorization: Bearer $GPUB_API_KEY"

# only what is running, from the night batch, 10 per page, newest first
curl -s "https://gpubrazil.com/api/instances?status=running&metadata.batch=night&limit=10" \
  -H "Authorization: Bearer $GPUB_API_KEY"

Response:
{
  "data": [
    {
      "instance_id": "abc123",
      "name": "nightly-train",
      "status": "running",
      "tier": "economica",
      "gpu_model": "RTX_4090@NA",
      "gpu_model_label": "RTX 4090 · North America",
      "gpu_count": 1,
      "ip_address": "203.0.113.42",
      "ssh_port": 22,
      "ssh_user": "root",
      "ssh_key_name": "deploy-ci",
      "supports_pause": false,
      "disk_persists_on_stop": false,
      "price_per_hour_brl": 3.34,
      "daily_stopped_brl": 3.94,
      "metadata": { "job": "7f21", "batch": "night" },
      "template_id": null, "template_port": null, "template_url": null,
      "error_message": null, "deleting": false,
      "created_at": "2026-09-18 22:14:02"
    }
  ],
  "count": 1,
  "total": 1,
  "limit": 10,
  "offset": 0
}

Accepted filters

ParameterExampleWhat it does
name?name=nightly-trainExact name (case-insensitive).
q / name_contains?q=trainPart of the name. Great for pipeline prefixes (?q=job-2026-09).
status?status=running,stoppedOne or more statuses, comma-separated.
tier?tier=dedicada,spotdedicada, economica, spot or cpu.
gpu?gpu=4090Part of the GPU model.
template?template=llama-factoryOnly machines created with that 1-click template.
has_ip?has_ip=trueOnly those that already have an address to connect to.
metadata.<key>?metadata.job=7f21Filters by the labels you stored yourself.
created_after / created_before?created_after=2026-09-01Creation window (ISO date, UTC).
sort / order?sort=name&order=ascSorts by created_at (default), name, price or status.
limit / offset?limit=20&offset=40Pagination. limit goes up to 500; without it, everything comes back.

Filters combine. count is what came in this page and total is how much the filter matched overall — the gap between them is what tells you to paginate. The localInstances field is still in the response with the same content as data, for backwards compatibility; in new code, use data.

Fields worth knowing

FieldWhy it exists
ssh_userThe login user for that machine, as reported at provisioning. Don't infer it from the machine type: machines in the same tier can have different logins.
ssh_key_nameWhich of your registered keys was installed — the matching private key is the one for ssh -i.
supports_pauseIf false, this machine cannot hibernate: /stop returns 409. To end it, use DELETE.
disk_persists_on_stopIf false, hibernating discards the disk and the machine comes back blank. Check before stopping.
template_urlReal address of the template interface, with the port that actually answers (on container machines it is randomised and is not the catalogue one). null while there is no real address yet.
deleting / status: "deleting"Cancellation already accepted, waiting for the destruction to finish. Hourly billing has already stopped — do not repeat the DELETE.
error_messagePlain-language reason for the failure, when status is error.

5. Manage by name, without listing everything

Every route that takes :instanceId also takes name:<name>. You name the machine at creation and operate it by that name from then on — no need to store the id we generate, and no need to download the whole list to find it before every command.

# create it with a name of your own
curl -X POST "https://gpubrazil.com/api/instances/deploy" \
  -H "Authorization: Bearer $GPUB_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "nightly-train", "gpuModel": "economic_RTX_4090", "requireUniqueName": true }'

# from then on, operate it by name, on any route
H="Authorization: Bearer $GPUB_API_KEY"
curl -s           "https://gpubrazil.com/api/instances/name:nightly-train"      -H "$H"
curl -s -X POST   "https://gpubrazil.com/api/instances/name:nightly-train/stop" -H "$H"
curl -s -X DELETE "https://gpubrazil.com/api/instances/name:nightly-train"      -H "$H"

Rules worth knowing before you automate:

RuleWhy
The id always wins over the name. Without the name: prefix we look for an id first, and only treat the text as a name if no id matches.Existing integrations that use ids don't change behaviour, not even if someone names a machine after another machine's id.
A name only addresses active machines.Names repeat over time (a pipeline recreates worker every day). Matching a destroyed machine would send commands into the void.
Case doesn't matter: name:Train-01 and name:train-01 resolve the same.Whoever types the name shouldn't have to remember the capitalisation.
Two active machines with the same name = 409 AMBIGUOUS_NAME, with the list of candidates. We never pick one for you."Delete train-01" hitting the wrong machine is damage that cannot be undone.
HTTP 409 — two active machines with the same name
{
  "code": "AMBIGUOUS_NAME",
  "error": "There are 2 active instances named \"nightly-train\". Use the instance id (see \"matches\") or rename one of them.",
  "matches": [
    { "id": "abc123", "name": "nightly-train", "status": "running", "gpu_model_label": "RTX 4090", "created_at": "2026-09-18 22:14:02" },
    { "id": "def456", "name": "nightly-train", "status": "creating", "gpu_model_label": "RTX 4090", "created_at": "2026-09-19 03:40:11" }
  ]
}

To keep the name a unique address, send "requireUniqueName": true on deploy: if an active machine already has that name, creation is refused with 409 NAME_IN_USEnothing is charged — and the response tells you which machine holds the name. Without that field the old behaviour still applies (repeated names are accepted).

6. Rename and label

PATCH /api/instances/:ref fixes the name and stores your own labels (metadata) on the machine — job id, batch, environment, whatever your pipeline needs to find later.

curl -X PATCH "https://gpubrazil.com/api/instances/name:nightly-train" \
  -H "Authorization: Bearer $GPUB_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "nightly-train-v2", "metadata": { "job": "7f21", "batch": "night", "attempt": 2 } }'

Response:
{ "success": true, "instance": { "id": "abc123", "name": "nightly-train-v2",
  "metadata": { "job": "7f21", "batch": "night", "attempt": 2 }, "status": "running", ... } }
FieldRules
nameUp to 64 characters: letters, digits, space, dot, @, hyphen and underscore. Cannot start with name: or creating- (addressing prefixes). A name already used by another active machine of yours returns 409 NAME_IN_USE.
metadataFlat JSON object: up to 20 keys, values of type string, number or boolean (no nesting), 2 KB total. Send null to clear the labels.

Renaming is only allowed once the machine actually exists (status running/stopped). During provisioning the answer is 409 RENAME_TOO_EARLY: while the machine is being created the name is still the key we use internally to reconcile it, and changing it there would open a window for the machine to slip outside our billing control. Wait for running — seconds to a few minutes.

Labels come back ready (as an object, not a string) in GET /api/instances, and double as a filter: ?metadata.batch=night.

7. What this machine has cost so far

GET /api/instances/:ref/usage returns the full bill for that instance — including the prepaid first hour, which no hourly usage counter shows.

curl -s "https://gpubrazil.com/api/instances/name:nightly-train/usage" \
  -H "Authorization: Bearer $GPUB_API_KEY"

Response:
{
  "id": "abc123",
  "name": "nightly-train",
  "status": "running",
  "currency": "BRL",
  "hourly_rate_brl": 3.34,
  "daily_stopped_brl": 3.94,
  "total_charged_brl": 13.36,
  "breakdown": { "hourly_usage_brl": 10.02, "reservations_net_brl": 3.34 },
  "created_at": "2026-09-18 22:14:02",
  "last_charged_at": "2026-09-19 01:14:02",
  "age_hours": 4.12
}
FieldWhat it is
total_charged_brlEverything already taken from your balance for this machine, refunds included. This is the number for a cost report.
breakdown.hourly_usage_brlWhole hours already closed and charged.
breakdown.reservations_net_brlThe hour prepaid at creation (and on each restart), minus refunds.
daily_stopped_brlWhat one day costs with the machine hibernated. It is not 1× the hourly price: the GPU is released, but the disk stays reserved. This is the number that decides between hibernating and destroying.
last_charged_atLast hour closed. The next charge happens one hour after this mark.

Amounts are in BRL, the currency the machine is priced and billed in.

8. Stop and start (optional)

Stopping halts hourly billing, but does not bring the cost to zero: while stopped, the instance is billed a daily fee equivalent to 1 hour of usage. Important: not every machine preserves its disk when stopped — on some Dedicated machines the disk is discarded and the instance comes back blank on start, losing everything installed on it. Check the disk_persists_on_stop field in GET /api/instances before stopping. To end billing entirely, use DELETE.

Not every machine can hibernate: where the card is released on stop, there would be no way to guarantee the same machine back. The supports_pause field says which ones accept it — on the others, /stop returns 409 instead of destroying a machine you didn't ask to destroy.

curl -X POST "https://gpubrazil.com/api/instances/name:my-vm/stop" \
  -H "Authorization: Bearer $GPUB_API_KEY"

curl -X POST "https://gpubrazil.com/api/instances/name:my-vm/start" \
  -H "Authorization: Bearer $GPUB_API_KEY"

9. Delete an instance

Destroys the instance at the provider and ends billing. This is the endpoint you asked about — it exists and it's permanent.

curl -X DELETE "https://gpubrazil.com/api/instances/name:my-vm" \
  -H "Authorization: Bearer $GPUB_API_KEY"

Response:
{ "success": true, "message": "Instance removed" }

If the machine is still being created, the destruction may be refused (HTTP 409) — try again in a few minutes. When the answer comes back as 202 (or the instance shows status: "deleting"), the request has been accepted and hourly billing has already stopped: do not repeat the DELETE, the destruction finishes on its own.

Other account resources

Everything below uses the same API key and the same balance. And, as with instances, disks and SSH keys can also be addressed by the name you gave them.

CPU machines (no GPU)

For preprocessing, queues, scraping and services that don't need a video card. These are real dedicated machines, including an option in São Paulo.

# catalogue (public)
curl -s "https://gpubrazil.com/api/cpus"

# create
curl -X POST "https://gpubrazil.com/api/cpus/deploy" \
  -H "Authorization: Bearer $GPUB_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "etl-queue", "cpu_key": "cpu_c3-small-x86@SAO2", "sshKeyName": "deploy-ci",
        "metadata": { "batch": "etl" }, "requireUniqueName": true }'

Once created, a CPU machine is listed and operated through the same instance routes (list, status, rename, delete), with tier: "cpu". It is bare metal, so provisioning takes a few minutes — longer than a GPU. An SSH key is mandatory (it is the only way in).

Persistent Disk

Storage that outlives the machine. You attach a disk at creation and the contents of /workspace/disco are synced; attach the same disk to the next machine and your data is back.

# create a 100 GB disk
curl -X POST "https://gpubrazil.com/api/disks" \
  -H "Authorization: Bearer $GPUB_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "training-data", "sizeGb": 100 }'

# list / find by name
curl -s "https://gpubrazil.com/api/disks?name=training-data" -H "Authorization: Bearer $GPUB_API_KEY"

# attach it when creating a machine — by the disk NAME
curl -X POST "https://gpubrazil.com/api/instances/deploy" \
  -H "Authorization: Bearer $GPUB_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "train-01", "gpuModel": "economic_RTX_4090", "diskId": "name:training-data" }'

# rename / delete (deleting destroys the data)
curl -X PATCH  "https://gpubrazil.com/api/disks/name:training-data" -H "Authorization: Bearer $GPUB_API_KEY" \
  -H "Content-Type: application/json" -d '{ "name": "training-data-2026" }'
curl -X DELETE "https://gpubrazil.com/api/disks/name:training-data-2026" -H "Authorization: Bearer $GPUB_API_KEY"

A disk is billed by the size you contracted (not by what you use) for as long as it exists, even with no machine attached. Two disks with the same name make any by-name command return 409 AMBIGUOUS_NAME instead of picking one — deleting the wrong disk is data loss that does not come back.

SSH keys

Register the key once and then just refer to it by name on deploy, with sshKeyName — no need to carry the key material inside your pipeline.

curl -X POST "https://gpubrazil.com/api/ssh-keys" \
  -H "Authorization: Bearer $GPUB_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "deploy-ci", "publicKey": "ssh-ed25519 AAAAC3Nza... ci@company" }'

curl -s "https://gpubrazil.com/api/ssh-keys" -H "Authorization: Bearer $GPUB_API_KEY"

# delete by name
curl -X DELETE "https://gpubrazil.com/api/ssh-keys/name:deploy-ci" -H "Authorization: Bearer $GPUB_API_KEY"

On deploy, send sshKey (the full public key) or sshKeyName (the name of a registered one). A name that doesn't exist returns 400 SSH_KEY_NOT_FOUND and creates nothing — a machine you pay for and cannot enter is the worst possible outcome. The instance's ssh_key_name field tells you which key ended up installed.

Balance and statement

curl -s "https://gpubrazil.com/api/user/balance" -H "Authorization: Bearer $GPUB_API_KEY"
{ "balance_brl": 482.15, "user": { "id": 42, "name": "...", "email": "..." } }

# statement, filtered
curl -s "https://gpubrazil.com/api/transactions?type=reservation,usage&limit=100" \
  -H "Authorization: Bearer $GPUB_API_KEY"
curl -s "https://gpubrazil.com/api/transactions?q=nightly-train" -H "Authorization: Bearer $GPUB_API_KEY"
ParameterWhat it does
typedeposit, reservation, usage, refund — comma-separated list accepted.
qSearches the entry description (that's where the machine name shows up).
created_after / created_beforeDate window, for closing the month.
limit / offsetPagination (default 50, max 500).

Statement convention: a charge is a negative amount and a credit is positive. Dates are in UTC. The response is a plain array.

Ready-made recipes

Create it, wait until it's ready, connect

Deploy answers immediately with status: "creating"; the machine becomes usable moments later. Poll by name until the status turns running with an IP:

NAME="train-$(date +%s)"

curl -sX POST "https://gpubrazil.com/api/instances/deploy" \
  -H "Authorization: Bearer $GPUB_API_KEY" -H "Content-Type: application/json" \
  -d "{\"name\":\"$NAME\",\"gpuModel\":\"economic_RTX_4090\",\"sshKeyName\":\"deploy-ci\",\"requireUniqueName\":true}"

# wait up to 15 min, asking every 10 s
for i in $(seq 1 90); do
  J=$(curl -s "https://gpubrazil.com/api/instances/name:$NAME" -H "Authorization: Bearer $GPUB_API_KEY")
  S=$(echo "$J" | python3 -c "import json,sys; print(json.load(sys.stdin)['status'])")
  [ "$S" = "running" ] && echo "$J" | python3 -c "import json,sys; print(json.load(sys.stdin)['connection']['sshCommand'])" && break
  [ "$S" = "error" ]   && echo "$J" | python3 -c "import json,sys; print('failed:', json.load(sys.stdin)['error_message'])" && break
  sleep 10
done

Polling every 5 to 10 seconds is plenty. The connection.sshCommand field comes assembled with the right login user for that machine; just swap in the path to your private key after -i.

Tear down a whole batch by name prefix

Filter by part of the name (or by label) and destroy each one by the id the listing already gave you.

# everything starting with "job-2026-09-19"
curl -s "https://gpubrazil.com/api/instances?q=job-2026-09-19&status=running" \
  -H "Authorization: Bearer $GPUB_API_KEY" \
| python3 -c "import json,sys; [print(i['instance_id']) for i in json.load(sys.stdin)['data']]" \
| while read ID; do
    curl -sX DELETE "https://gpubrazil.com/api/instances/$ID" -H "Authorization: Bearer $GPUB_API_KEY"
  done

The same works by label: ?metadata.batch=night. For batches, prefer deleting by the id from the listing: it is immune to repeated names.

How much each machine has cost

for NAME in $(curl -s "https://gpubrazil.com/api/instances?status=running" \
  -H "Authorization: Bearer $GPUB_API_KEY" \
  | python3 -c "import json,sys; [print(i['name']) for i in json.load(sys.stdin)['data']]"); do
    curl -s "https://gpubrazil.com/api/instances/name:$NAME/usage" -H "Authorization: Bearer $GPUB_API_KEY" \
    | python3 -c "import json,sys; d=json.load(sys.stdin); print(f\"{d['name']}: R\$ {d['total_charged_brl']:.2f} ({d['age_hours']:.1f}h)\")"
done

Status vocabulary

statusWhat it meansIs it billing?
creatingProvisioning in progress. No IP yet.Yes — the first hour is prepaid at creation.
runningUp and usable. connection carries IP, port and user.Yes, hourly.
stoppedHibernated (only where supports_pause is true).Yes, a storage day rate — see daily_stopped_brl.
deletingCancellation accepted, waiting for destruction to complete.No. Do not repeat the DELETE.
errorIt failed. The reason is in error_message.No — whatever was reserved is refunded automatically.
stopped_no_balanceShut down for insufficient balance.Same as stopped. Top up and call /start.

Usage limits

LimitValueWhen you hit it
Machine creation40 per 10 minutes, per account429, with Retry-After in seconds. It is a guard against retry loops, not a commercial cap: if you need more, just ask.
Read requestsno fixed limitWe ask for common sense: 5 to 10 s between polls while a machine boots, and filters instead of listing everything in a loop.
Registered SSH keys10 per account400 when registering the 11th.
Disk size10 GB to 10 TB400 outside that range.

Inference by the token (OpenAI-compatible)

Not every project needs a whole GPU running. You can also call open-weight models and pay per token consumed, using the same gpub_live_ API key and the same balance in reais that pays for the GPUs. There is no subscription, no monthly fee and no minimum token purchase: billing is proportional to the input and output tokens of each call, and the amount charged comes back inside the response itself.

The API is compatible with OpenAI's. Any SDK, framework or tool that already talks to it works here by changing only the base URL and the key:

https://gpubrasil.com.br/v1

This is the canonical API host, and it serves both languages. We do not use your prompts or your responses to train models. If your use case requires full control over the weights, the logs and the lifecycle of what you send, run the model on a dedicated GPU of your own, with no third-party API in the path.

1. List models and prices

Returns the available models, the context window size and the price per million tokens, in Brazilian reais. Each model has an id — what goes in the model field — a commercial name for display, and a prateleira (shelf).

There are three shelves: fronteira (brand-name models — Claude, GPT and Gemini), confidenciais (running inside a hardware-sealed enclave) and essenciais (open-weight, best price per token). All of them answer through the same OpenAI format, including the ones that would natively require a different request shape.

curl -s "https://gpubrasil.com.br/v1/models" \
  -H "Authorization: Bearer $GPUB_API_KEY"

Response (excerpt):
{
  "object": "list",
  "data": [
    { "id": "gpub-fast", "object": "model", "owned_by": "gpubrasil",
      "name": "DeepSeek V4 Flash", "prateleira": "essenciais", "context_length": 1000000,
      "pricing": { "currency": "BRL", "inputPerMillion": 0.59, "cachedInputPerMillion": 0.118, "outputPerMillion": 1.29 } },
    { "id": "claude-sonnet-5", "object": "model", "owned_by": "gpubrasil",
      "name": "Claude Sonnet 5", "prateleira": "fronteira", "context_length": 1000000,
      "pricing": { "currency": "BRL", "inputPerMillion": 20.40, "cachedInputPerMillion": 2.04, "outputPerMillion": 102.00 } },
    { "id": "gpub-selado-nemotron", "object": "model", "owned_by": "gpubrasil",
      "name": "Nemotron 3 Nano", "prateleira": "confidenciais", "context_length": 128000,
      "pricing": { "currency": "BRL", "inputPerMillion": 0.15, "cachedInputPerMillion": 0.015, "outputPerMillion": 0.60 } }
  ]
}

The example above is trimmed to three rows, one per shelf; the call returns the whole catalogue. The full, always-current list is at AI models. Need the price table without authenticating (for a pricing page, say)? Use GET /api/inference/models, which is public.

2. Chat completion

The format is identical to OpenAI's. The only difference is the extra usage.cost_brl field: the cost in reais of that call, already computed by the server and already deducted from your balance, so you never have to redo the math on the client.

curl -X POST "https://gpubrasil.com.br/v1/chat/completions" \
  -H "Authorization: Bearer $GPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpub-plus",
    "messages": [
      { "role": "system", "content": "You are a concise assistant." },
      { "role": "user", "content": "Explain what a GPU is in two sentences." }
    ],
    "max_tokens": 300
  }'

Response:
{
  "id": "chatcmpl-8f2c1b",
  "object": "chat.completion",
  "created": 1754870400,
  "model": "gpub-plus",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "A GPU is a processor..." },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 500,
    "completion_tokens": 300,
    "total_tokens": 800,
    "cost_brl": 0.0085,
    "balance_brl_after": 92.15
  }
}

The response's model field echoes what you asked for (here, gpub-plus), not the technical id — call it by nickname and the nickname is what comes back. cost_brl (cost of the call) and balance_brl_after (balance after it) are our additions inside the usage object; the X-Request-Id response header identifies the request on our side, so keep it if you ever need to open a ticket. Official SDKs ignore fields they do not know, so these extras break no existing integration. For plain text continuation (no chat roles), the endpoint is POST /v1/completions, with prompt instead of messages.

Image and audio input. Use the standard OpenAI image_url block inside content — it works on every model that accepts images, on any shelf, with no change to your code. For audio, the block is input_audio. If the model you picked does not accept what you sent, the response is 400 with the reason; we never accept the call and silently drop the part the model cannot read. Some models need the image inline (data:<mime>;base64,…) rather than a URL, and the error says when that is the case.

curl -X POST "https://gpubrazil.com/v1/chat/completions" \
  -H "Authorization: Bearer $GPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "max_tokens": 300,
    "messages": [{
      "role": "user",
      "content": [
        { "type": "text", "text": "What is wrong with this diagram?" },
        { "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0KG..." } }
      ]
    }]
  }'

3. Streaming

Send "stream": true to receive the answer in chunks, as standard server-sent events. To also receive the token counts and the cost, ask for stream_options: {"include_usage": true}: usage arrives in a frame of its own, right before the final [DONE].

curl -N -X POST "https://gpubrasil.com.br/v1/chat/completions" \
  -H "Authorization: Bearer $GPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpub-fast",
    "messages": [{ "role": "user", "content": "Count to three." }],
    "stream": true,
    "stream_options": { "include_usage": true }
  }'

Response (text/event-stream):
data: {"id":"chatcmpl-3a91","object":"chat.completion.chunk","model":"gpub-fast","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl-3a91","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"One"},"finish_reason":null}]}

data: {"id":"chatcmpl-3a91","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":", two"},"finish_reason":null}]}

data: {"id":"chatcmpl-3a91","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":", three."},"finish_reason":null}]}

data: {"id":"chatcmpl-3a91","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-3a91","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":9,"total_tokens":23,"cost_brl":0.0000155,"balance_brl_after":92.15}}

data: [DONE]

Without stream_options, the usage frame is not sent and you end up without the cost_brl for that call. Consumption is still recorded server-side and shows up in /api/inference/usage. Stop reading when you see data: [DONE], which is not JSON.

4. Official OpenAI SDK

No need to switch libraries. Point the official SDK at our base URL and use your API key; the rest of your code stays the same.

Python (pip install openai)

from openai import OpenAI

client = OpenAI(
    base_url="https://gpubrasil.com.br/v1",
    api_key="gpub_live_yourkeyhere",
)

r = client.chat.completions.create(
    model="gpub-mini",
    messages=[{"role": "user", "content": "Hello!"}],
)

print(r.choices[0].message.content)
# cost_brl is an extra field of ours; the Python SDK exposes it in model_extra
print(r.usage.model_extra["cost_brl"])
Node.js (npm i openai)

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://gpubrasil.com.br/v1',
  apiKey: process.env.GPUB_API_KEY,
});

const r = await client.chat.completions.create({
  model: 'gpub-mini',
  messages: [{ role: 'user', content: 'Hello!' }],
});

console.log(r.choices[0].message.content);
console.log(r.usage.cost_brl); // cost in reais of this call

The same applies to any tool that accepts an OpenAI-compatible endpoint: fill in the base URL with https://gpubrasil.com.br/v1 and the key with your gpub_live_.

5. Image generation

Models that generate images have their own surface: POST /v1/images/generations, in the same OpenAI format, with the same key and the same balance. They do not answer on /v1/chat/completions — asking for one there returns 400 pointing here, and vice versa. Each model's endpoint field in GET /v1/models tells you which surface it uses.

curl -X POST "https://gpubrazil.com/v1/images/generations" \
  -H "Authorization: Bearer $GPUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpub-imagem",
    "prompt": "a tree-lined square at sunrise, watercolor style",
    "size": "1024x1024"
  }'

Response:
{
  "created": 1789807470,
  "data": [ { "b64_json": "/9j/4AAQSkZJRgABAQ..." } ]
}

The image comes back as b64_json (base64), ready to write to a file. n must be 1: these models produce one image per call, and asking for more returns 400 before any amount is held from your balance. size accepts a fixed list of sizes — sending one outside it returns 400 with the whole list in the error. Some models do not accept size at all; omit the field for those.

Billing. It depends on the model, and each price is in GET /v1/models: some charge per image (perImage field, in BRL) and others charge per image token (imageOutputPerMillion). The count always comes from what was delivered, never from what was requested — a response with no image is not charged.

Limits, context and balance

Context window. Input and output together must fit within the context_length of the chosen model, which today ranges from 128,000 to 1,000,000 tokens depending on the model. Going over returns 400, with no charge. Each model's value is in GET /v1/models — read it from there instead of hard-coding it, because models get added and windows change.

Response size. max_tokens caps how many tokens the model may generate. Without it, the model decides where to stop within the window, and since output costs more than input on every model, setting a ceiling in production is worth it.

Balance. Before forwarding the call, the server estimates its cost. If your balance does not cover that estimate, the request is refused with 402 and nothing is spent. Top up in the dashboard and try again. Inference over the API is unlocked after your first confirmed deposit: before that the call comes back with 403 and code: "deposit_required" — to try the models without depositing, use the playground in the dashboard. Too many calls in a short window return 429; a momentary outage returns 503.

HTTP 402
{
  "error": {
    "message": "Insufficient balance to cover the estimated cost of this call.",
    "type": "insufficient_quota",
    "param": null,
    "code": "insufficient_balance"
  }
}

Errors follow the OpenAI envelope (error.message, error.type, error.param, error.code), so existing libraries already know how to read them. Note that type and code differ: a short balance comes back as type: "insufficient_quota" with code: "insufficient_balance". Branch on the HTTP status. To track spend and volume, use GET /api/inference/usage?days=30, which returns consumption per day and per model.

Response Codes

200 · OK

Successful request

201 · Created

Instance/resource created

400 · Error

Invalid parameters or insufficient balance

401 · Unauthorized

API key missing, invalid, or revoked

403 · Forbidden

Action not allowed for this credential

402 · Insufficient balance

Balance does not cover the estimated cost of the call

429 · Rate limit

Too many requests in a short window

404 · Not Found

Instance/resource does not exist

409 · Conflict

Instance still being created — try again later

500 · Internal Error

Server error

The code field in errors

Besides the HTTP status, errors from the instance routes carry a stable code — branch on it, not on the message text (which changes with language and wording).

codeHTTPWhat to do
INSTANCE_NOT_FOUND404That id/name isn't among your active machines. A name only addresses a live machine.
AMBIGUOUS_NAME409Two or more active machines share the name. Use one of the ids in matches, or rename.
NAME_IN_USE409The name already belongs to another active machine of yours. Nothing was created or charged.
RENAME_TOO_EARLY409Wait for the machine to leave creating before renaming.
INVALID_NAME · INVALID_METADATA400Name or labels outside the rules. Nothing was created or charged.
SSH_KEY_NOT_FOUND400sshKeyName matches none of your keys. See GET /api/ssh-keys.
DELETE_PENDING409The machine is already being destroyed; stop/start no longer applies.
PRICE_CHANGED409The price moved between your catalogue read and the deploy. Read the catalogue again and retry — we never charge above what you were shown.
TEMPLATE_NOT_FOUND · TEMPLATE_REQUIRES_GPU400Unknown template, or one incompatible with the machine you picked. Use /api/gpus?template=<id> to list only what runs it.
INSUFFICIENT_BALANCE402Your balance doesn't cover the first hour. The response says how much is missing.
DISK_NOT_FOUND · DISK_UNSUPPORTED_MACHINE404 / 400Unknown disk, or a machine type that doesn't take a Persistent Disk yet.

General rule: every refusal happens before any charge. If the answer was 4xx, nothing left your balance. And if a machine fails after being created, the reservation is refunded automatically — you don't have to ask.