{"openapi":"3.0.3","info":{"title":"TOPUP — API B2B (sandbox)","version":"1.0.0","description":"Recargas de saldo movil.\n\n**Esto es el sandbox.** El contrato es identico al de produccion y el\ncodigo que funcione aqui funciona alli sin cambiar una linea: lo unico\nque cambia es a que servidor apuntas. Las recargas no se cobran y no\nllegan a ningun telefono, y el saldo es ficticio.\n\n**Todas las peticiones van firmadas.** No hay token portador: el secreto no\nviaja nunca, viaja una firma de cada peticion concreta. Un `Bearer` que\naparece en un log o en el historial de una terminal es acceso al dinero de\nun cliente.\n\nLa firma vale 300 segundos y el nonce no se puede repetir dentro de esa\nventana: sin lo segundo, quien capture una peticion valida puede reproducirla.\n\nLos importes son **cadenas de texto**, no numeros. Es a proposito.\n\nPara enterarte de como acaban las recargas sin preguntar en bucle, pidenos\nque te demos de alta un webhook: `docs/cliente/WEBHOOKS.md`."},"servers":[{"url":"https://topup-sandbox.nexo2one.com"}],"security":[{"firmaTopup":[]}],"components":{"securitySchemes":{"firmaTopup":{"type":"apiKey","in":"header","name":"X-Topup-Signature","description":"Cuatro cabeceras, todas obligatorias:\n\n| Cabecera | Que es |\n|---|---|\n| `X-Topup-Key` | Tu Key ID, el que empieza por `tk_`. |\n| `X-Topup-Timestamp` | Segundos desde epoch. |\n| `X-Topup-Nonce` | Un valor distinto en cada peticion. Un UUID vale. |\n| `X-Topup-Signature` | `hex(HMAC-SHA256(secreto, cadena_canonica))`. |\n\nLa cadena canonica son cinco lineas separadas por `\\n`:\n\n```\nMETODO\nRUTA\nTIMESTAMP\nNONCE\nSHA256(cuerpo)\n```\n\nLa ruta va **sin dominio y sin query**. El hash del cuerpo se calcula\ntambien cuando el cuerpo esta vacio: es el sha256 de la cadena vacia."}},"schemas":{"Recarga":{"type":"object","properties":{"id":{"type":"string","description":"Referencia de la recarga.","example":"top_9f2c1a..."},"estado":{"type":"string","enum":["pendiente","completada","fallida","en_revision"]},"sku":{"type":"string","example":"CU-226475"},"destino":{"type":"string","example":"5355512345"},"precio":{"type":"string","description":"Cadena, no numero: leerlo como coma flotante redondea dinero.","example":"22.00"},"divisa":{"type":"string","example":"USD"},"creada":{"type":"string","format":"date-time"},"resuelta":{"type":"string","format":"date-time","nullable":true},"motivo":{"type":"string","nullable":true,"enum":["DESTINO_DUPLICADO_RECIENTE","DESTINO_INVALIDO","PRODUCTO_NO_DISPONIBLE","PROVEEDOR_NO_DISPONIBLE","NO_ENTREGADA","FALLO_DEL_PROVEEDOR"],"description":"Solo cuando el estado es `fallida`."}}},"Error":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}},"paths":{"/v1/products":{"get":{"summary":"Catalogo disponible","description":"Lo que se puede vender ahora mismo, con tu precio.","responses":{"200":{"description":"El catalogo.","content":{"application/json":{"schema":{"type":"object","properties":{"datos":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"CU-226475"},"nombre":{"type":"string","example":"CubaCel Cuba 500 CUP"},"pais":{"type":"string","example":"CU"},"operadora":{"type":"string","example":"CubaCel Cuba"},"importe_destino":{"type":"string","example":"500"},"divisa_destino":{"type":"string","example":"CUP"},"precio":{"type":"string","example":"22.00"},"divisa":{"type":"string","example":"USD"}}}}}}}}},"401":{"description":"Firma invalida o ausente.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}},"429":{"description":"Limite por minuto.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}}}}},"/v1/balance":{"get":{"summary":"Tu saldo","description":"Tres cifras, y la que importa para poder pedir es `disponible`.\n\n`reservado` es lo comprometido en recargas que todavia estan en vuelo:\nel saldo se aparta antes de mandar nada al operador, y no se libera\nhasta saber que paso.","responses":{"200":{"description":"El saldo.","content":{"application/json":{"schema":{"type":"object","properties":{"datos":{"type":"object","properties":{"saldo":{"type":"string","example":"1500.00"},"reservado":{"type":"string","example":"44.00"},"disponible":{"type":"string","example":"1456.00"},"divisa":{"type":"string","example":"USD"}}}}}}}},"401":{"description":"Firma invalida o ausente.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}}}}},"/v1/topups":{"get":{"summary":"Tus ultimas recargas","description":"Las cien mas recientes, de la mas nueva a la mas vieja.","responses":{"200":{"description":"Las recargas.","content":{"application/json":{"schema":{"type":"object","properties":{"datos":{"type":"array","items":{"$ref":"#/components/schemas/Recarga"}}}}}}},"401":{"description":"Firma invalida o ausente.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}}}},"post":{"summary":"Crear una recarga","description":"**`Idempotency-Key` es obligatoria**, no recomendada. La eliges tu, y si\nrepites la llamada con la misma clave te devolvemos la orden que ya\nexiste con un `200` en vez de crear otra. Es lo que te protege de cobrar\ndos veces cuando una respuesta se pierde por el camino.\n\nSi repites la clave **cambiando el sku o el destino**, contestamos `409`:\neso no es un reintento, es otra cosa con la misma etiqueta.\n\nLa respuesta llega casi siempre en `pendiente`. Enterarse de como acaba\nes para lo que estan los webhooks.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":200},"example":"mi-pedido-00042"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","destino"],"properties":{"sku":{"type":"string","example":"CU-226475"},"destino":{"type":"string","description":"El numero de destino, en formato nacional del pais del sku.","example":"5355512345"}}}}}},"responses":{"200":{"description":"La misma `Idempotency-Key` con los mismos datos: te devolvemos la orden que ya existia.","content":{"application/json":{"schema":{"type":"object","properties":{"datos":{"$ref":"#/components/schemas/Recarga"}}}}}},"201":{"description":"Recarga creada.","content":{"application/json":{"schema":{"type":"object","properties":{"datos":{"$ref":"#/components/schemas/Recarga"}}}}}},"400":{"description":"Falta la Idempotency-Key, o el cuerpo no vale.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}},"403":{"description":"Cuenta suspendida.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}},"409":{"description":"Esa Idempotency-Key ya se uso con otros datos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}},"422":{"description":"Sin saldo, producto no disponible o limite de importe.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}},"429":{"description":"Limite de recargas por minuto.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}}}}},"/v1/topups/{id}":{"get":{"summary":"Una recarga","description":"**Esta es la fuente de verdad.** Un webhook adelanta lo que esto te\ndiria; no lo sustituye. Si los dos se contradicen, manda esto.\n\nUna recarga de otro cliente se responde igual que una que no existe.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"La recarga.","content":{"application/json":{"schema":{"type":"object","properties":{"datos":{"$ref":"#/components/schemas/Recarga"}}}}}},"404":{"description":"No existe esa recarga, o no es tuya.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","required":["codigo","mensaje","peticion_id"],"properties":{"codigo":{"type":"string","enum":["FIRMA_INVALIDA","TIMESTAMP_FUERA_DE_VENTANA","NONCE_REPETIDO","IP_NO_AUTORIZADA","CREDENCIAL_DESCONOCIDA","CLIENTE_DESCONECTADO","IDEMPOTENCY_KEY_REQUERIDA","IDEMPOTENCY_KEY_CONFLICTO","CUERPO_INVALIDO","SALDO_INSUFICIENTE","PRODUCTO_NO_DISPONIBLE","RECARGA_NO_ENCONTRADA","LIMITE_EXCEDIDO"]},"mensaje":{"type":"string","description":"Para una persona. No programes contra esto."},"peticion_id":{"type":"string","description":"Identificador de la peticion. Incluyelo si nos escribes."}}}}}}}}}}}}}