¿Cómo autenticarte en la API de Skipo?
1) API Key
Una API Key es requerida para todas las requests a la API de Skipo con el propósito de identificar al usuario que realiza la petición. Esta debe especificarse en un header HTTP llamado X-API-KEY
.
Para crear la API Key debes iniciar sesión en nuestra aplicación web 👉 app.skipo.com
Una vez dentro debes ir a Configuración → Desarrollador → Generar API key. Lo que despliegará la siguiente ventana:
Acá deberás seleccionar un nombre, fecha de expiración y permisos para la API Key. En caso de que elijas sólo el permiso de Ver
se te pedirá el código de doble factor (2FA) y obtendrás tu API Key.
Por otro lado, si decides asignar otros permisos, entonces se te pedirá un paso previo para la generación y entrega de un PUBLIC-CERTIFICATE
(ver punto 2).
2) Generación de llave privada y certificado público
2.1) Llave privada
Primero debemos generar una llave privada con el algoritmo RSA, para lo cual basta con ejecutar el siguiente comando en la terminal:
openssl genrsa -out skipo_private.key 2048
Esto genera un archivo skipo_private.key
el cual contiene la llave privada generada. Posteriormente deberás utilizarla para firmar las request que realizarás a la API (más detalles en el punto 3).
Seguridad
Te pedimos que tanto la API Key como la llave privada (sobre todo está última) las resguardes en un lugar seguro y no las compartas con nadie, ni siquiera con nosotros. Asimismo, en caso de pérdida de la llave privada el equipo de Skipo no podrá recuperarla y se deberá repetir el proceso de generación de llave.
2.2) Certificado público
Luego, debemos generar un PUBLIC-CERTIFICATE
(certificado público) a partir de la llave privada creada anteriormente. Para esto se debe ejecutar el siguiente comando en la terminal:
openssl req -x509 -new -key skipo_private.key -out skipo_public.crt -sha256
Sobre certificados RSA
Al correr el comando la terminal te pedirá ciertos datos como el código del país, el nombre de la organización, entre otros. Esto es propio de los certificados RSA y es utilizado para identificar el origen del mismo. Simplemente completa con la información requerida para la creación del certificado.
Esta instrucción genera un archivo skipo_public.crt
el cual contiene el PUBLIC-CERTIFICATE
.
2.3) Envío de certificado y entrega del API Key
Una vez creado el archivo skipo_public.crt
te pedimos que nos lo proporciones para hacerte entrega de la API Key. Descuida, este certificado es público por lo que no es información sensible a diferencia de la llave privada skipo_private.key
la cual debes resguardar y bajo ninguna circunstancia compartir.
3) Firma de mensajes
La firma de mensajes se realiza combinando el algoritmo de hashing SHA256 con la encriptación RSA utilizando la llave privada (SHA256withRSA). Para esto, los lenguajes de programación facilitan bastante la implementación al proveer librerías para obtener la firma deseada.
Librerías criptográficas
Algunos ejemplos de librerías criptográficas son las siguientes:
crypto
(NodeJS) -cryptography
orsa
(Python) -Crypto++
(C++) -openssl
(Ruby)
Preparación de string a firmar:
El contenido del string a firmar dependerá del endpoint que se invoque y se define de la siguiente manera:
{GET|POST|PUT|DELETE} {path} {sorted_alphabetically_body}
Donde:
-
{GET|POST|PUT|DELETE}
: Corresponde el método HTTP a ejecutar. -
path
: Corresponde a la ruta de la request incluyendo los query params pero excluyendo el host.Ej: `/v1/ledger_movements/BTC?startDate=2024-01-01&endDate=2024-04-01`
-
sorted_alphabetically_body
: Corresponde al body de la request con sus atributos ordenados alfabéticamente. En el caso de los métodos GET este valor es{}
.
Headers
Así como la API Key se incluye en el header X-API-KEY
, la firma generada con SHA256withRSA sobre el string especificado arriba se incluye en un header llamado X-SIGNATURE
. La firma en este caso debe ir codificada en Base64.
Recuerda que para métodos GET (sólo lectura) no es necesario especificar la firma del header X-SIGNATURE
, por lo que sólo bastara con el envío del header X-API-KEY
para la ejecución del método.
Código
A continuación se muestra un ejemplo completo de la obtención del string para el mensaje, la firma con la llave privada y la request a la API.
import axios from "axios";
import { sign } from "crypto";
import { readFileSync } from "fs";
import { resolve } from "path";
const PATH_TO_PRIVATE_KEY = "PATH_TO_PRIVATE_KEY";
const YOUR_API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.skipo.com";
// Interface for request body
interface RequestBody {
[key: string]: any;
}
class SkipoApiClient {
private readonly baseUrl: string;
private readonly secret: string;
constructor(private readonly apiKey: string) {
this.baseUrl = BASE_URL;
// Replace "PATH_TO_PRIVATE_KEY" with the path to your private key file
// or you can store the private key in an environment variable
this.secret = readFileSync(resolve(PATH_TO_PRIVATE_KEY), "utf8");
}
// Function to sort object keys alphabetically
static sortObjectKeys(obj: RequestBody): RequestBody {
return Object.keys(obj)
.sort()
.reduce((result: RequestBody, key: string) => {
result[key] = obj[key];
return result;
}, {});
}
async request(
method: "GET" | "POST" | "PUT" | "DELETE",
path: string,
body: RequestBody = {}
) {
try {
const url = `${this.baseUrl}${path}`;
const headers: Record<string, string> = { "X-API-KEY": this.apiKey };
if (method !== "GET") {
const message = `${method} ${path} ${JSON.stringify(SkipoApiClient.sortObjectKeys(body))}`;
const signature = sign("sha256", Buffer.from(message), this.secret).toString("base64");
headers["X-SIGNATURE"] = signature;
}
const response = await axios.request({
method,
url,
data: body,
headers,
});
return response.data;
} catch (error: any) {
console.error("Error:", error.message);
// Handle error with custom logic...
}
}
}
// Create an instance of the SkipoApiClient class and make a request
const skipoApiClient = new SkipoApiClient(YOUR_API_KEY);
skipoApiClient.request(
"GET",
"/v1/ledger_movements/CLP?startDate=2024-01-01&endDate=2024-04-01"
).then((response) => console.log("Response:", response));
const axios = require("axios");
const { sign } = require("crypto");
const { readFileSync } = require("fs");
const { resolve } = require("path");
const PATH_TO_PRIVATE_KEY = "PATH_TO_PRIVATE_KEY";
const YOUR_API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.skipo.com";
class SkipoApiClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = BASE_URL;
this.secret = readFileSync(resolve(PATH_TO_PRIVATE_KEY), "utf8");
}
static sortObjectKeys(obj) {
return Object.keys(obj)
.sort()
.reduce((result, key) => {
result[key] = obj[key];
return result;
}, {});
}
async function request(method, path, body = {}) {
try {
const url = this.baseUrl + path;
const headers = { "X-API-KEY": this.apiKey };
if (method !== "GET") {
const message = `${method} ${path} ${JSON.stringify(sortObjectKeys(body))}`;
const signature = sign("sha256", Buffer.from(message), this.secret).toString("base64");
headers["X-SIGNATURE"] = signature;
}
const response = await axios({
method: method,
url: url,
data: body,
headers: headers
});
return response.data;
} catch (error) {
console.error("Error:", error.message);
// Handle error with custom logic...
}
}
}
const skipoApiClient = new SkipoApiClient(YOUR_API_KEY);
skipoApiClient.request(
"GET",
"/v1/ledger_movements/CLP?startDate=2024-01-01&endDate=2024-04-01"
).then((response) => console.log("Response:", response));
import json
from base64 import b64encode
from pathlib import Path
# This needs a requirements.txt for requests and cryptography libraries
import requests
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key
PRIVATEKEY_PATH = "PATH_TO_PRIVATE_KEY"
YOUR_API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.skipo.com"
class SkipoApiClient:
def __init__(self, api_key):
self.base_url = BASE_URL
self.api_key = api_key
self.secret = self.load_private_key(PRIVATEKEY_PATH)
def load_private_key(self, path):
with open(Path(path), 'rb') as key_file:
private_key = load_pem_private_key(
key_file.read(),
password=None,
)
return private_key
@staticmethod
def sort_object_keys(obj):
return {key: obj[key] for key in sorted(obj)}
def sign_message(self, message):
signature = self.secret.sign(
message.encode(),
padding.PKCS1v15(), # Use PKCS#1 v1.5 padding for RSA
hashes.SHA256()
)
# Base64 encode and convert to string
return b64encode(signature).decode('utf-8')
def request(self, method, path, body=None):
if body is None:
body = {}
sorted_body = json.dumps(
self.sort_object_keys(body), separators=(',', ':'))
message = f"{method} {path} {sorted_body}"
signature = self.sign_message(message)
headers = {
"X-API-KEY": self.api_key,
"X-SIGNATURE": signature
}
response = requests.request(
method=method,
url=f"{self.base_url}{path}",
headers=headers,
json=body
)
try:
response.raise_for_status()
return response.json()
except requests.RequestException as error:
print(f"Error: {error}")
return None
# Create an instance of the SkipoApiClient class and make a request
if __name__ == "__main__":
skipo_api_client = SkipoApiClient(YOUR_API_KEY)
response = skipo_api_client.request(
"GET",
"/v1/ledger_movements/CLP?startDate=2024-01-01&endDate=2024-04-01"
)
print(json.dumps(response, indent=2))
Recuerda que esto es sólo un ejemplo de implementación, siéntete libre de modificarlo de la forma que te parezca y se adecue más a tus necesidades.