Una clave, tu saldo y una llamada. Vendes cuentas premium sin comprar inventario ni tocar la entrega.
$ curl -X POST resellme.xyz/api/reseller/v1/orders \
-H "Authorization: Bearer sk_live_…" \
-d '{"items":[{"product_id":"netflix","quantity":1}]}'
{ "status": "completed",
"keys": ["correo@dominio.com:contraseña"],
"balance": 121.17 }La generas en tu panel. Empieza por sk_live_.
Cada pedido se descuenta al instante.
La credencial llega en la misma respuesta.
Tu clave viaja en cada petición. Es una contraseña: quien la tenga gasta tu saldo.
URL base
https://resellme.xyz/api/reseller/v1
Mándala como Authorization: Bearer, o como x-api-key si tu herramienta no deja poner cabeceras.
curl https://resellme.xyz/api/reseller/v1/products \
-H "Authorization: Bearer sk_live_tu_clave"Todas cuelgan de https://resellme.xyz/api/reseller/v1
El stock que ves es el que se puede pedir. En productos de tipo keys equivale a las credenciales sin usar.
curl https://resellme.xyz/api/reseller/v1/products \
-H "Authorization: Bearer sk_live_tu_clave"{
"currency": "EUR",
"count": 42,
"products": [
{
"id": "netflix",
"name": "Netflix",
"delivery_type": "keys",
"price": 8.51,
"stock": null,
"variants": [
{ "id": "premium", "name": "Premium", "price": 8.51, "stock": 37 }
]
}
]
}Tu descuento sube solo según las unidades que vendes cada mes natural.
curl https://resellme.xyz/api/reseller/v1/balance \
-H "Authorization: Bearer sk_live_tu_clave"{
"balance": 128.40,
"currency": "EUR",
"tier": {
"percent": 15,
"sales_this_month": 312,
"next_tier_at": 500,
"to_next_tier": 188
}
}Unidades vendidas al mes y su descuento.
El precio que devuelve la API es tu COSTE: lo que te cobramos a ti (ya con tu descuento de nivel). El precio de VENTA a tu cliente lo pones tú, en tu propia tienda o bot.
Más abajo, las guías de Discord y Telegram incluyen un comando /precio que consulta el coste en vivo y calcula el precio de venta con tu margen.
Mandas los productos y recibes las keys en la respuesta. El cobro y el descuento se calculan en el servidor.
curl -X POST https://resellme.xyz/api/reseller/v1/orders \
-H "Authorization: Bearer sk_live_tu_clave" \
-H "Content-Type: application/json" \
-d '{"items":[{"product_id":"netflix","variant_id":"premium","quantity":1}]}'{
"order_id": "ord_9F2K7",
"status": "completed",
"total": 7.23,
"discount_percent": 15,
"balance": 121.17,
"items": [
{ "product": "Netflix", "quantity": 1,
"keys": ["correo@dominio.com:contraseña"] }
]
}La forma más rápida de probar la API sin escribir código. Sirve para automatizar con scripts de shell.
Cada producto tiene un id. Lo necesitas para comprar. Con jq lo dejas a la vista.
curl -s https://resellme.xyz/api/reseller/v1/products \
-H "Authorization: Bearer sk_live_tu_clave" \
| jq '.products[] | { id, name, price, stock }'curl -s -X POST https://resellme.xyz/api/reseller/v1/orders \
-H "Authorization: Bearer sk_live_tu_clave" \
-H "Content-Type: application/json" \
-d '{"items":[{"product_id":"PRODUCT_ID","quantity":1}]}' \
| jq '.items[].keys'Un proyecto de cero con un cliente reutilizable. Vale para un script suelto, un backend o la base de un bot.
Necesitas Node 18 o superior (trae fetch integrado). Guardamos la clave en un archivo de entorno, nunca en el código.
mkdir mi-integracion && cd mi-integracion
npm init -y
npm pkg set type=module
npm install dotenvRESELLER_KEY=sk_live_tu_claveimport "dotenv/config";
const BASE = "https://resellme.xyz/api/reseller/v1";
const KEY = process.env.RESELLER_KEY;
export async function api(path, options = {}) {
const res = await fetch(BASE + path, {
...options,
headers: {
Authorization: "Bearer " + KEY,
"Content-Type": "application/json",
...(options.headers || {}),
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || ("HTTP " + res.status));
return data;
}import { api } from "./client.js";
const cat = await api("/products");
for (const p of cat.products) {
console.log(p.id, p.name, p.price, cat.currency, "stock:", p.stock);
}import { api } from "./client.js";
try {
const order = await api("/orders", {
method: "POST",
body: JSON.stringify({ items: [{ product_id: "PRODUCT_ID", quantity: 1 }] }),
});
console.log("Pedido", order.order_id, "estado", order.status);
for (const item of order.items) {
console.log(item.product, item.keys.join(", "));
}
console.log("Saldo restante:", order.balance);
} catch (e) {
console.error("No se pudo comprar:", e.message);
}El mismo recorrido con Python y la librería requests.
python3 -m venv .venv
source .venv/bin/activate
pip install requests
export RESELLER_KEY=sk_live_tu_claveimport os
import requests
BASE = "https://resellme.xyz/api/reseller/v1"
KEY = os.environ["RESELLER_KEY"]
HEADERS = {"Authorization": "Bearer " + KEY, "Content-Type": "application/json"}
def api(path, method="GET", body=None):
r = requests.request(method, BASE + path, headers=HEADERS, json=body, timeout=30)
data = r.json()
if not r.ok:
raise RuntimeError(data.get("error", "HTTP " + str(r.status_code)))
return datafrom client import api
cat = api("/products")
for p in cat["products"]:
print(p["id"], p["name"], p["price"], cat["currency"], "stock:", p["stock"])from client import api
try:
order = api("/orders", "POST", {"items": [{"product_id": "PRODUCT_ID", "quantity": 1}]})
print("Pedido", order["order_id"], "estado", order["status"])
for item in order["items"]:
print(item["product"], ", ".join(item["keys"]))
print("Saldo restante:", order["balance"])
except RuntimeError as e:
print("No se pudo comprar:", e)De la app vacía a un bot que consulta stock, calcula tu precio de venta y compra desde un comando. En discord.js y en discord.py.
RESELLER_KEY=sk_live_tu_clave
DISCORD_TOKEN=el_token_del_bot
DISCORD_APP_ID=el_id_de_la_aplicacionimport { Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder } from "discord.js";
const BASE = "https://resellme.xyz/api/reseller/v1";
const KEY = process.env.RESELLER_KEY;
const MARKUP = 1.4; // tu margen: precio de venta = coste x 1.4 (+40%)
async function api(path, options = {}) {
const res = await fetch(BASE + path, {
...options,
headers: { Authorization: "Bearer " + KEY, "Content-Type": "application/json", ...(options.headers || {}) },
});
return res.json();
}
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on("interactionCreate", async (i) => {
if (!i.isChatInputCommand()) return;
if (i.commandName === "saldo") {
await i.deferReply({ ephemeral: true });
const b = await api("/balance");
await i.editReply("Saldo: " + b.balance + " " + b.currency);
}
if (i.commandName === "stock") {
await i.deferReply({ ephemeral: true });
const cat = await api("/products");
const lines = cat.products.slice(0, 15).map((p) => p.name + " · " + p.price + " " + cat.currency);
await i.editReply(lines.join("\n") || "Catálogo vacío");
}
// Precio: lee nuestro coste en vivo y calcula tu precio de venta con MARKUP
if (i.commandName === "precio") {
await i.deferReply({ ephemeral: true });
const productId = i.options.getString("product_id", true);
const cat = await api("/products");
const p = cat.products.find((x) => x.id === productId);
if (!p) return i.editReply("Producto no encontrado");
const sell = (p.price * MARKUP).toFixed(2);
await i.editReply(
p.name + "\n" +
"Coste: " + p.price + " " + cat.currency + "\n" +
"Tu venta: " + sell + " " + cat.currency + " (+" + Math.round((MARKUP - 1) * 100) + "%)"
);
}
if (i.commandName === "comprar") {
await i.deferReply({ ephemeral: true });
const productId = i.options.getString("product_id", true);
const order = await api("/orders", { method: "POST", body: JSON.stringify({ items: [{ product_id: productId, quantity: 1 }] }) });
if (order.error) return i.editReply("No se pudo comprar: " + order.error);
const keys = order.items.flatMap((it) => it.keys).join("\n");
await i.editReply("Entregado:\n" + keys + "\nSaldo: " + order.balance);
}
});
const commands = [
new SlashCommandBuilder().setName("saldo").setDescription("Ver tu saldo"),
new SlashCommandBuilder().setName("stock").setDescription("Ver el catálogo"),
new SlashCommandBuilder().setName("precio").setDescription("Precio de venta con tu margen")
.addStringOption((o) => o.setName("product_id").setDescription("Id del producto").setRequired(true)),
new SlashCommandBuilder().setName("comprar").setDescription("Comprar un producto")
.addStringOption((o) => o.setName("product_id").setDescription("Id del producto").setRequired(true)),
].map((c) => c.toJSON());
const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN);
await rest.put(Routes.applicationCommands(process.env.DISCORD_APP_ID), { body: commands });
client.login(process.env.DISCORD_TOKEN);Un bot con los mismos comandos, hablando con BotFather. En Node y en Python.
RESELLER_KEY=sk_live_tu_clave
TELEGRAM_TOKEN=el_token_de_botfatherimport TelegramBot from "node-telegram-bot-api";
const BASE = "https://resellme.xyz/api/reseller/v1";
const KEY = process.env.RESELLER_KEY;
const MARKUP = 1.4; // tu margen: venta = coste x 1.4 (+40%)
const bot = new TelegramBot(process.env.TELEGRAM_TOKEN, { polling: true });
async function api(path, options = {}) {
const res = await fetch(BASE + path, {
...options,
headers: { Authorization: "Bearer " + KEY, "Content-Type": "application/json", ...(options.headers || {}) },
});
return res.json();
}
bot.onText(/\/stock/, async (msg) => {
const cat = await api("/products");
const lines = cat.products.slice(0, 15).map((p) => p.name + " · " + p.price + " " + cat.currency);
bot.sendMessage(msg.chat.id, lines.join("\n") || "Catálogo vacío");
});
// Precio de venta con tu margen: /precio PRODUCT_ID
bot.onText(/\/precio (.+)/, async (msg, match) => {
const cat = await api("/products");
const p = cat.products.find((x) => x.id === match[1].trim());
if (!p) return bot.sendMessage(msg.chat.id, "Producto no encontrado");
const sell = (p.price * MARKUP).toFixed(2);
bot.sendMessage(msg.chat.id, p.name + "\nCoste: " + p.price + " " + cat.currency + "\nTu venta: " + sell + " " + cat.currency);
});
bot.onText(/\/comprar (.+)/, async (msg, match) => {
const order = await api("/orders", { method: "POST", body: JSON.stringify({ items: [{ product_id: match[1].trim(), quantity: 1 }] }) });
if (order.error) return bot.sendMessage(msg.chat.id, "Error: " + order.error);
const keys = order.items.flatMap((it) => it.keys).join("\n");
bot.sendMessage(msg.chat.id, "Entregado:\n" + keys);
});La integración estrella. Tú vendes en tu tienda de SellAuth y nosotros ponemos el stock: cuando alguien compra, SellAuth nos pide la credencial, la cobramos de tu saldo y la devolvemos para que se entregue sola.
comprador -> tu tienda de SellAuth -> webhook de ResellMe
|
cobro de tu saldo + entrega
|
tu tienda de SellAuth <- credencial (texto plano) <- ResellMe
|
comprador recibe su cuentaPOST https://resellme.xyz/api/reseller/webhook/tu_token
x-signature: 9f2a... (HMAC SHA256 del cuerpo)
idempotency-key: inv_10042 (opcional, se respeta en reintentos)
Content-Type: application/json
{
"event": "INVOICE.ITEM.DELIVER-DYNAMIC",
"id": 10042,
"email": "comprador@correo.com",
"item": { "id": 55, "product_id": 4211, "variant_id": 9954 }
}200 OK
Content-Type: text/plain
correo@dominio.com:contraseña
# una credencial por línea.
# para productos de archivo devolvemos la URL.
# si algo falla respondemos con un código != 200
# y un mensaje neutro que SellAuth muestra al comprador.Calculamos el HMAC SHA256 del cuerpo crudo con tu secreto y lo comparamos con la cabecera x-signature header. Si no cuadra, rechazamos con 401. Así es como se genera, por si montas tu propio emisor:
import crypto from "crypto";
const firma = crypto.createHmac("sha256", SECRET).update(rawBody).digest("hex");
// se envía en la cabecera: x-signature: <firma>| Situación | Qué hacemos |
|---|---|
| Entrega correcta | Respondemos 200 con la credencial. SellAuth la muestra al comprador. |
| Sin saldo o sin stock | Respondemos con un código de error y un mensaje neutro. No se cobra. |
| Producto sin mapear | Respondemos 400 y registramos el intento en tu panel para que lo enlaces. |
| Fallo temporal (5xx) | SellAuth reintenta. La idempotencia evita cobrar dos veces. |
Cada error llega como JSON con un campo error legible.