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/nodesor 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 |
|---|---|---|
| no | no | puts the machine back on both, if either side was off |
| yes | no | unlists it on the other marketplace |
| no | yes | takes it off Paralon (available: false) |
| yes | yes | a 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.



