Pulsed Media Customer API
A Pulsed Media API key returns every service on the account as JSON in one request: seedboxes, storage boxes and dedicated servers, each with status, next due date, billing cycle and price. Each seedbox and storage box also reports its username, login address and six usage figures, the same ones its service page shows (data sent, data received, CPU hours, disk used, disk quota and torrents loaded), so several boxes are checked with one request instead of one login per box. A separate order key lets a script order new services, and the API never returns a password.
At a glance
| Task | Request | Needs |
|---|---|---|
| Read every service on the account | GET https://pulsedmedia.com/remote/pmapi-feed.php |
A read key |
| Get a price quote, then order a new service | POST https://pulsedmedia.com/remote/pmapi-order.php |
An order key |
| Create, replace or revoke the keys | Client area: Services → API access | The account owner |
The key goes in a header: X-API-Key: YOUR_KEY, or Authorization: Bearer YOUR_KEY. Both endpoints answer in JSON over HTTPS.
Quick start
- Log in to the client area and open Services → API access. Active seedbox and storage box service pages also link it as API access in the help line, and the direct address is
https://pulsedmedia.com/clients/index.php?m=pmapi. - Click Create an API key. This read key cannot place orders; ordering uses a separate order key, described under ordering. The key is shown once, so copy it before leaving the page.
- Read your services:
curl -H "X-API-Key: YOUR_KEY" https://pulsedmedia.com/remote/pmapi-feed.php
Only the account owner can create, replace or revoke a key. Users added to the account, and staff logged in to the client area on the owner's behalf, see the page but not the key controls.
The services feed
GET (or HEAD) on the feed returns one JSON object. A shortened example:
{
"client_id": 12345,
"generated_at": "2026-10-22T04:00:00Z",
"currency": "EUR",
"services": [
{
"id": 1001,
"product": "M1000 S",
"group": "Shared Seedboxes",
"domain": "server1.pulsedmedia.com",
"status": "Active",
"registered": "2025-01-10",
"next_due": "2026-10-25",
"billing_cycle": "Monthly",
"recurring_amount": "6.99",
"seedbox": {
"username": "alice",
"login_url": "https://server1.pulsedmedia.com/user-alice/",
"permanent_address": "http://0123456789abcdef.mcx.fi/",
"usage": {
"tracked_since": "2025-01-10",
"sent_bytes": 52000000000000,
"received_bytes": null,
"cpu_hours": 12.5,
"cpu_since": "2026-09-01",
"disk_used_bytes": 1500000000000,
"disk_quota_bytes": 2000000000000,
"torrents": 812
}
}
}
]
}
Account fields
| Field | Type | Meaning |
|---|---|---|
client_id |
integer | The account the key belongs to |
generated_at |
string | When the answer was built, UTC, ISO 8601 |
currency |
string | Currency code of the prices, for example EUR
|
services |
array | Every service on the account, in service id order; empty when there are none |
Service fields
| Field | Type | Meaning |
|---|---|---|
id |
integer | Service id, the same number the client area shows |
product |
string | Product name |
group |
string or null | Product group |
domain |
string | For seedboxes and storage boxes, the server's hostname; may be empty for other products |
status |
string | Pending, Active, Completed, Suspended, Terminated, Cancelled or Fraud
|
registered, next_due |
string or null | Dates as YYYY-MM-DD; null when not set
|
billing_cycle |
string | As the client area shows it: Monthly, Quarterly, Semi-Annually, Annually, Biennially, Triennially, One Time or Free Account
|
recurring_amount |
string | Recurring price with two decimals, in the account currency |
seedbox |
object | Present on seedboxes and storage boxes that have a username |
The feed lists terminated and cancelled services too. Filter on status when only live services matter.
Seedbox and storage box fields
| Field | Type | Meaning |
|---|---|---|
username |
string | The service login |
login_url |
string | The web dashboard address |
permanent_address |
string | An address that follows the service when it moves to another server; see Permalinks on PMSS |
usage |
object or null | The latest recorded usage, the same figures the service page shows; null when none is recorded yet |
usage field |
Type | Meaning |
|---|---|---|
tracked_since |
string | First day of the recorded totals |
sent_bytes |
integer | Data sent since tracked_since
|
received_bytes |
integer or null | Data received; null where the server did not record it |
cpu_hours |
number or null | CPU time since cpu_since, one decimal (for example 12.5); null when no valid figure exists
|
cpu_since |
string or null | First day the CPU figure covers |
disk_used_bytes |
integer | Disk in use at the latest reading |
disk_quota_bytes |
integer or null | Disk quota at the latest reading |
torrents |
integer or null | Torrents loaded at the latest reading |
cpu_hours is a decimal number. A client that decodes it as an integer will reject the whole answer, so decode it as a float. Responses carry Cache-Control: no-store.
Recipes
These use jq 1.6 or later, with the key in the PM_KEY environment variable. The first two skip services where the field they need is null; the third prints - for a missing due date.
Services due in the next seven days, or already overdue:
curl -s -H "X-API-Key: $PM_KEY" https://pulsedmedia.com/remote/pmapi-feed.php |
jq -r '.services[] | select(.status == "Active" and .next_due != null)
| select((.next_due | strptime("%Y-%m-%d") | mktime) <= now + 7*86400)
| "\(.next_due) \(.product) \(.recurring_amount)"'
Disk use as a percentage of quota, per seedbox or storage box:
curl -s -H "X-API-Key: $PM_KEY" https://pulsedmedia.com/remote/pmapi-feed.php |
jq -r '.services[] | select(.status == "Active" and .seedbox.usage.disk_quota_bytes != null)
| "\(.seedbox.username) \(.seedbox.usage.disk_used_bytes * 100 / .seedbox.usage.disk_quota_bytes | floor)%"'
Every service as one tab-separated line:
curl -s -H "X-API-Key: $PM_KEY" https://pulsedmedia.com/remote/pmapi-feed.php | jq -r '.services[] | [.id, .status, .product, (.next_due // "-")] | @tsv'
Ordering through the API
Ordering needs an order key: click Create an order key on the API access page. The order key can place orders but cannot read services, and the read key cannot place orders, so a script that only reads never needs a key that can spend money. A request without confirm returns a quote and what the product needs. The same request with "confirm": true places the order, so a script that already knows the product can skip the quote. Replace 123 below with the id of a product that products.json lists as available. As written, this request returns the quote; add "confirm":true to the JSON to place the order.
curl -s -H "X-API-Key: YOUR_ORDER_KEY" -H "Content-Type: application/json" \
-d '{"pid":123,"billingcycle":"monthly","customfields":{"Username":"myname"},"accept_tos":true}' \
https://pulsedmedia.com/remote/pmapi-order.php
| Field | Type | Meaning |
|---|---|---|
pid |
integer | Product id: the id in products.json, one of the public data feeds
|
billingcycle |
string | One of the product's billing_cycles keys in products.json, for example monthly
|
customfields |
object | Field name to value, for example "Username": "myname" on seedboxes
|
configoptions |
object | Option name to choice name, or true/false for yes/no options. Options left out keep the order form's default
|
accept_tos |
boolean | true accepts the terms of service. When the order form requires acceptance, a confirmed order without it is refused; a quote does not need it
|
confirm |
boolean | true places the order; left out or false returns only the quote
|
expect_first_payment |
string | Optional. The quote's first_payment, for example "12.50". A confirm whose first payment is no longer this amount is refused with 409 and the new quote, and nothing is ordered
|
expect_currency |
string | Optional. The quote's currency code, for example "EUR". A confirm in another currency is refused the same way
|
The quote answer carries quote (product, billing cycle, first_payment, currency, a link to the terms) and product_needs: the custom fields with whether each is required and its choices, and the configurable options with their choices and defaults. The confirmed order answers 201 with order_id, invoice_id, pay_url and the same quote.
- New orders only, one product per request, for products listed on the order form and in stock. Products with a quantity option cannot be ordered this way.
- The order goes through the same checks as the order form does for a logged-in customer, and the usual order and invoice emails go out.
- The API takes no payment. As with every invoice, any credit on the account is applied first; the rest is paid from the invoice like any other order, and the payment method can be changed there.
- At most 5 unpaid orders on the account and 5 orders in 24 hours, counting orders placed any way, and one order request per account at a time.
- Unknown fields are refused, and the request body may be at most 8192 bytes.
Status codes
Errors come back as JSON with an error field.
| Code | Meaning |
|---|---|
| 200 | Feed answer, or an order quote |
| 201 | Order placed |
| 400 | Anything after ? in the URL; the body is not a JSON object, has a top-level field not listed above, lacks pid or billingcycle, has a value of the wrong type, or is over 8192 bytes
|
| 401 | Key missing, malformed or unknown; or the account is not active, or the key's creator no longer owns it |
| 403 | The key cannot be used here: a read key sent to the order endpoint, or an order key sent to the feed |
| 404 | The product does not exist or cannot be ordered through the API |
| 405 | Wrong method: the feed takes GET and HEAD, ordering takes POST
|
| 409 | Out of stock; or, on a confirm with expect_first_payment or expect_currency, the price or currency is no longer the one expected. Nothing is ordered, and the answer carries the new quote
|
| 422 | The order cannot be placed as sent: a billing cycle the product does not offer, an option or custom field name the product does not have, a product with a quantity option, a missing required field, terms not accepted, or a refused order-form check. The answer lists the problems and product_needs
|
| 429 | 5 unpaid orders, 5 orders in 24 hours, or another order request from the account is still running |
| 500 | Temporarily unavailable. On the order endpoint, no order was placed |
| 502 | The order could not be placed. Check the account's invoices before trying again |
| 503 | The API, or ordering through it, is switched off, or the feed is temporarily unavailable |
How the key is protected
- An account has one read key and, if the owner creates one, one order key. Pulsed Media stores only a hash of each, so a lost key cannot be shown again, only replaced.
- Replace the key and Replace the order key issue a new key of that kind, and the old one stops at once. Revoke the key and Revoke the order key stop it at once. Replacing or revoking one key never touches the other.
- The key works only while the account is active and the user who created it still owns the account.
- Changing the account password does not stop the key. After a suspected leak, replace or revoke it on the API access page.
- The page shows when each key was created and when it was last used.
- The key is read from a header only. A URL with anything after
?is refused, so the key never sits in a URL. - Use
https://. A request tohttp://is redirected, but by then the key has crossed the network unencrypted; replace a key sent that way. - The read key can only read, and the order key can only order. The order key places orders invoiced to the account and paid at once from any credit on it, within the limits above, so give it only to the tool that orders. A key created before 22 October 2026 with Allow this key to place orders ticked can both read and order until it is replaced or revoked; the page says so next to it, and its replacement is a read key.
- Web pages cannot call the API directly from a visitor's browser, because cross-origin requests are not supported. Call it from a script or a server.
Tools that use it
PMSS Fleet Manager, a community-made dashboard, reads plan prices and renewal dates through the API key and shows them next to each box's disk and traffic. Pulsed Media did not write it and does not maintain it.
mcxKokko is Pulsed Media's open-source skill pack for OpenClaw and other AI agents that read AgentSkills folders. Its account skill lists services with the read key. Its pm-order skill quotes a service and orders it with the order key only after the owner approves the command on OpenClaw's approval card, and the server refuses the order if the price is not the one on the card.
Every Pulsed Media seedbox and storage box account comes with this API, and a box ordered later needs no new setup: it is in the next answer, under the same key. See the seedbox plans.
See also
- Pulsed Media Public Data Feeds: keyless JSON feeds for the product catalog, prices and stock
- PMSS Fleet Manager: a community dashboard for several seedboxes that uses this API
- Seedbox: Pulsed Media's core product
- Storage Boxes: storage boxes, which appear in the same feed
- Pulsed Media: company overview and product catalog