Guides & Tutorials6 min read

List One GPU Machine on Several Marketplaces Without Renting It Twice

Most GPU hosts list the same machine on more than one marketplace. The risk is two renters on one GPU. Here is a small watcher that keeps Paralon and your other marketplace in step through the Paralon provider API: rented here, it unlists there; busy there, it takes the node off Paralon; free on both, it lists it everywhere again. With the one case it cannot prevent, and how a spot node resolves it.

15 s
default poll: the only window in which both sides can rent
~0.3 s
for the watcher to take a node off Paralon, measured
2 min
notice a spot reclaim gives the Paralon renter in a race
One glowing gold cube on a dark grid with two paths of light to two gates: the left one lit, the right one switched off with a small green light, one machine taking one route at a time

If you rent out GPUs for a living, you probably list them in more than one place. Demand moves between marketplaces, and an idle machine on one of them may be booked on another. The catch is the obvious one: if two marketplaces rent the same GPU in the same hour, one renter gets a machine that is already busy.

This guide keeps one machine on Paralon and on another marketplace at the same time, safely. It is a short Python watcher built on the Paralon provider API: when one side rents the machine, the other side stops offering it, and when it is free again it goes back on both.

What you need

  • A machine already running as a Paralon node, and its node id (from GET /api/v1/nodes or the dashboard).
  • A Paralon API key with the My nodes permission (Console → API keys). It needs nothing else.
  • A way to tell when the other marketplace has a job on the machine, and its command to list or unlist the machine.
  • Python 3.8 or newer with requests.

The script is in our open-source provider tools repository on GitHub (MIT).

The rule it follows

Every 15 seconds the watcher asks two questions, and acts on the answer:

Rented on Paralon?Busy on the other marketplace?It does
nonoputs the machine back on both, if either side was off
yesnounlists it on the other marketplace
noyestakes it off Paralon (available: false)
yesyesa race: see When both rent at once

Taking a machine off a market never interrupts a rental that is already running there. It only stops the next one.

The Paralon side

Two calls are all it needs. The node's state tells whether it is rented:

def paralon_node() -> dict:
    r = requests.get(f"{API}/nodes/{NODE}", headers=H, timeout=30)
    r.raise_for_status()
    return r.json()  # node["state"] == "rented" while a renter is on it

And one PATCH takes it off the market or puts it back:

def set_available_paralon(available: bool) -> dict:
    r = requests.patch(f"{API}/nodes/{NODE}", headers=H,
                       json={"available": available}, timeout=30)
    r.raise_for_status()
    return r.json()

The answer is the node after the change, so the script can log exactly what happened.

The other marketplace's side

Every marketplace is different, so this part is two functions you fill in. The first answers "is a job running on this machine right now?":

def busy_elsewhere() -> bool:
    # Pick the check that fits your setup:
    # - its job containers:   docker ps --filter "label=<their label>" -q
    # - its CLI or API:       ask whether this machine has an active rental
    # - a file it writes:     os.path.exists("/run/other-market/busy")
    return os.path.exists(os.environ.get("BUSY_FLAG", "/tmp/busy-elsewhere"))

Checking the machine itself (its containers or GPU processes) is usually the most reliable: it is true the moment the job starts, whatever the other marketplace's API says. The default looks at a flag file, so you can try the whole loop by touching and removing a file.

The second lists or unlists the machine there. Put the marketplace's own command in OTHER_LIST_CMD and OTHER_UNLIST_CMD; until you do, the script only logs what it would have done:

export OTHER_UNLIST_CMD="<your marketplace's unlist command for this machine>"
export OTHER_LIST_CMD="<its list command, with your price>"

Run it

pip install requests
export PARALON_API_KEY=prlc_your_key
export PARALON_NODE_ID=<your node id>
python3 dual_list.py

Run it as a service (systemd or a restart: unless-stopped container) on the machine or anywhere with network access; it only calls APIs. An error from the API is logged and retried at the next poll, so a short outage never flips anything.

What we measured

We ran the watcher against one of our own nodes, with the flag file standing in for the other marketplace:

--- busy elsewhere
21:43:24 busy elsewhere: off the Paralon market
step took 461 ms
paralon available: False
--- free again
21:43:25 free: back on the Paralon market
paralon available: True

The 461 ms includes starting Python; the two API calls in a step take about 0.25 s together from a server in Germany (a read is about 120 ms at the median, a change about 130 ms). In practice the delay that matters is the poll interval: with the default 15 seconds, the machine stays offered on the second marketplace for at most about 15 seconds after the first one rents it.

When both rent at once

That 15-second window is the one case the watcher cannot prevent: two renters, on two marketplaces, both starting inside the same poll. Shorten POLL_SECONDS to make it rarer; the calls are cheap. For the case itself, the script's resolve_conflict() uses what Paralon offers:

  • On a spot node, it reclaims the Paralon rental with POST /nodes/{id}/reclaim. The Paralon renter gets two minutes' notice, is billed only until it stops, and knew when renting that a spot machine can be taken back. This is the clean way out, which is why a machine you also list elsewhere fits best as a spot node on Paralon: cheaper for the renter, and you keep the right to take it back.
  • On an on-demand node there is no reclaim: an on-demand renter was promised the machine until they stop. The script logs the conflict, and you decide by the other marketplace's rules.

Which way round you set it is a choice about where your best renters are. If Paralon should win races, keep the node on demand and make the other marketplace's side the one that gives way.

Many machines

The watcher handles one node. For a fleet, run one per machine (each is a few megabytes of memory), or loop over your nodes in one process: GET /api/v1/nodes returns them all in one call, and each change is one PATCH. Every request counts against the key's per-minute limit, so poll the list once per interval rather than each node separately.

The full script, with the conflict handling and the logging, is dual_list.py. The provider API docs list every field a node returns.

Frequently asked questions

Is it allowed to list the same GPU on Paralon and on another marketplace?

On Paralon, yes: the machine is yours. Check the other marketplace's terms for its own rules. What matters is that one GPU never has two renters at once, which is what the watcher in this guide prevents.

How does the script take a node off Paralon?

It sends PATCH /nodes/{id} with {"available": false} to the Paralon provider API. New rentals stop at once; a rental already running on Paralon goes on to its end. {"available": true} puts the node back.

How does the script know the machine is busy on the other marketplace?

You decide, in one function: the other marketplace's job containers on the machine, its CLI or API, or a file its job writes. The script ships with a flag-file check so you can try it without touching anything.

Can both marketplaces still rent the machine at the same moment?

Only if both rentals start inside the same poll window, 15 seconds by default. If the Paralon node is a spot node, the script reclaims the Paralon side: that renter gets two minutes' notice and pays only until it stops. On an on-demand node there is no reclaim, so the script logs the conflict for you to decide.

Which key does the script need?

A Paralon API key with the My nodes permission (Console, API keys), in PARALON_API_KEY, and the node id in PARALON_NODE_ID. The key needs nothing else: no inference, no rentals.

On the network

A GPU with JupyterLab on it, in about a minute

Billed for the minutes it runs, on a machine we benchmarked before listing it. Or call open models through an OpenAI-compatible API.

Keep reading

Related articles