Subir CSD
Sube los archivos del Certificado de Sello Digital (CSD) para una sucursal. Esto es necesario para poder sellar (firmar) las facturas.
Endpoint
POST https://api.lummy.io/v1/branches/{id}/csd
| Entorno | URL |
|---|---|
| Producción | https://api.lummy.io/v1/branches/{id}/csd |
| Sandbox | https://sandbox.lummy.io/v1/branches/{id}/csd |
Path Parameters
Path Parameters
{
"id": stringrequerido
↳Identificador único de la sucursal en formato UUID a la cual se le asignará el Certificado de Sello Digital. Este identificador se obtiene al crear la sucursal o mediante el endpoint de listado de sucursales.
↳Formato: uuid
↳Ejemplo:
"789e8400-e29b-41d4-a716-446655449999"}
Headers
Headers
{
"Authorization": string,requerido
↳Token de autenticación en formato Bearer (JWT) que valida la identidad del usuario. Este token debe ser enviado en cada petición para acceder a los recursos protegidos de la API.
↳Ejemplo:
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." "x-organization-id": string,requerido
↳Identificador único en formato UUID de la organización a la cual pertenece la sucursal. Este identificador debe corresponder a una organización existente y el usuario debe tener permisos sobre ella.
↳Formato: uuid
↳Ejemplo:
"550e8400-e29b-41d4-a716-446655440000" "x-idempotency": string,requerido
↳Identificador único en formato UUID utilizado para garantizar la idempotencia de la operación. Si se envía el mismo UUID en una petición posterior, el sistema devolverá el resultado de la primera operación sin procesar los archivos nuevamente.
↳Formato: uuid
↳Ejemplo:
"01936ff8-9c6a-7b2e-8000-123456789abc" "Content-Type": stringrequerido
↳Tipo de contenido de la petición. Este endpoint requiere multipart/form-data para soportar la carga de los archivos del certificado y la llave privada.
↳Valores permitidos:
"multipart/form-data"↳Ejemplo:
"multipart/form-data"}
Body Parameters (Multipart)
Request Body
{
"cerFile": file,requerido
↳Archivo del certificado público del CSD en formato .cer (DER). Este archivo contiene el certificado digital emitido por el SAT y es requerido para el proceso de sellado de CFDIs. El certificado debe estar vigente y su RFC debe coincidir con el RFC de la organización. Tamaño máximo: 5MB.
↳Formato: binary
↳Ejemplo:
"certificado.cer" "keyFile": file,requerido
↳Archivo de la llave privada del CSD en formato .key (DER encriptado). Este archivo contiene la clave privada protegida con contraseña que se utilizará para firmar digitalmente los CFDIs. La llave debe corresponder al certificado proporcionado y será validada usando la contraseña especificada. Tamaño máximo: 5MB.
↳Formato: binary
↳Ejemplo:
"llave_privada.key" "password": stringrequerido
↳Contraseña de la llave privada del CSD. Esta contraseña es la misma que se configuró al momento de generar el CSD ante el SAT. Se utiliza para descifrar y validar la llave privada. La contraseña no se almacena en texto plano, solo se usa para validar y descifrar la llave durante el proceso de carga.
↳Longitud mínima: 1 caracteres
↳Ejemplo:
"contraseña_secreta_123"}
Ejemplos de Código
- cURL
- Node.js (TypeScript)
- Python
- PHP (Guzzle)
curl -X POST https://sandbox.lummy.io/branches/${BRANCH_ID}/csd \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "x-organization-id: ${LUMMY_ORG_ID}" \
-H "x-idempotency: $(uuidgen)" \
-F "cerFile=@/path/to/certificado.cer" \
-F "keyFile=@/path/to/llave.key" \
-F "password=contraseña_csd"
Variables de entorno
Define ACCESS_TOKEN, LUMMY_ORG_ID y BRANCH_ID en tu shell:
export ACCESS_TOKEN="your-access-token"
export LUMMY_ORG_ID="your-organization-id"
export BRANCH_ID="your-branch-id"
import axios, { AxiosError } from 'axios';
import FormData from 'form-data';
import fs from 'fs';
import { v4: uuidv4 } from 'uuid';
interface CsdResponse {
id: string;
status: string;
}
async function subirCsd(): Promise<CsdResponse> {
const BRANCH_ID = 'your-branch-uuid'; // Reemplazar con ID real
const ORG_ID = process.env.LUMMY_ORG_ID!;
const API_URL = `https://sandbox.lummy.io/branches/${BRANCH_ID}/csd`;
const ACCESS_TOKEN = process.env.ACCESS_TOKEN!;
const form = new FormData();
form.append('cerFile', fs.createReadStream('./certificado.cer'));
form.append('keyFile', fs.createReadStream('./llave.key'));
form.append('password', 'contraseña_csd');
try {
const response = await axios.post<CsdResponse>(API_URL, form, {
headers: {
...form.getHeaders(),
'Authorization': `Bearer ${ACCESS_TOKEN}`,
'x-organization-id': ORG_ID,
'x-idempotency': uuidv4(),
},
});
console.log('CSD subido exitosamente');
console.log('ID:', response.data.id);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
console.error('Error al subir CSD:', axiosError.response?.data);
throw axiosError;
}
throw error;
}
}
// Ejecutar
subirCsd();
Dependencias
npm install axios form-data uuid
npm install -D @types/uuid @types/node
Variables de entorno
Define ACCESS_TOKEN y LUMMY_USER_ID en tu shell. El ACCESS_TOKEN se obtiene después de que el usuario se autentica en la plataforma Lummy.
import os
import requests
from uuid import uuid4
def subir_csd():
branch_id = "your-branch-uuid"
org_id = os.getenv("LUMMY_ORG_ID")
api_url = f"https://sandbox.lummy.io/branches/{branch_id}/csd"
access_token = os.getenv("ACCESS_TOKEN")
if not access_token or not org_id:
raise ValueError("Debes definir ACCESS_TOKEN y LUMMY_ORG_ID")
headers = {
"Authorization": f"Bearer {access_token}",
"x-organization-id": org_id,
"x-idempotency": str(uuid4()),
}
# Nota: requests calcula el Content-Type multipart automáticamente
files = {
'cerFile': ('certificado.cer', open('./certificado.cer', 'rb'), 'application/x-x509-ca-cert'),
'keyFile': ('llave.key', open('./llave.key', 'rb'), 'application/octet-stream')
}
data = {
'password': 'contraseña_csd'
}
try:
response = requests.post(api_url, files=files, data=data, headers=headers, timeout=60)
response.raise_for_status()
result = response.json()
print("CSD subido exitosamente")
print(f"ID: {result['id']}")
return result
except requests.exceptions.RequestException as e:
print(f"Error al subir CSD: {e}")
if e.response:
print(f"Detalle: {e.response.text}")
raise
finally:
files['cerFile'][1].close()
files['keyFile'][1].close()
if __name__ == "__main__":
subir_csd()
Dependencias
pip install requests
Variables de entorno
Define ACCESS_TOKEN y LUMMY_USER_ID en tu shell. El ACCESS_TOKEN se obtiene después de que el usuario se autentica en la plataforma Lummy.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Ramsey\Uuid\Uuid;
function subirCsd()
{
$branchId = 'your-branch-uuid';
$orgId = getenv('LUMMY_ORG_ID');
$apiUrl = "https://sandbox.lummy.io/branches/{$branchId}/csd";
$accessToken = getenv('ACCESS_TOKEN');
if (!$accessToken || !$orgId) {
throw new Exception('Debes definir ACCESS_TOKEN y LUMMY_ORG_ID');
}
$client = new Client(['timeout' => 60]);
try {
$response = $client->post($apiUrl, [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'x-organization-id' => $orgId,
'x-idempotency' => Uuid::uuid4()->toString(),
],
'multipart' => [
[
'name' => 'password',
'contents' => 'contraseña_csd'
],
[
'name' => 'cerFile',
'contents' => fopen('./certificado.cer', 'r'),
'filename' => 'certificado.cer'
],
[
'name' => 'keyFile',
'contents' => fopen('./llave.key', 'r'),
'filename' => 'llave.key'
],
]
]);
$data = json_decode($response->getBody(), true);
echo "CSD subido exitosamente\n";
echo "ID: " . $data['id'] . "\n";
return $data;
} catch (RequestException $e) {
echo "Error al subir CSD: " . $e->getMessage() . "\n";
if ($e->getResponse()) {
echo "Detalle: " . $e->getResponse()->getBody() . "\n";
}
throw $e;
}
}
try {
subirCsd();
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}
Dependencias
composer require guzzlehttp/guzzle ramsey/uuid
Variables de entorno
Define ACCESS_TOKEN y LUMMY_USER_ID en tu shell. El ACCESS_TOKEN se obtiene después de que el usuario se autentica en la plataforma Lummy.
Respuestas
Todas las respuestas siguen el formato estándar StandardResponse.
201 Created
CSD subido y validado exitosamente.
{
"requestId": "abc123-def456",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"serialNumber": "00001000000123456789",
"validFrom": "2024-01-01T00:00:00.000Z",
"validTo": "2028-01-01T00:00:00.000Z",
"status": "active"
},
"timestamp": "2025-01-15T10:30:00.000Z",
"path": "/branches/789e8400-e29b-41d4-a716-446655449999/csd",
"method": "POST"
}
400 Bad Request
- Archivos faltantes.
- Contraseña incorrecta.
- El RFC del certificado no coincide con el RFC de la sucursal.
- Certificado caducado o revocado.
{
"requestId": "abc123-def456",
"error": {
"message": "El RFC del certificado no coincide con el RFC de la organización",
"code": "ValidationError",
"status": 400
},
"timestamp": "2025-01-15T10:30:00.000Z",
"path": "/branches/789e8400-e29b-41d4-a716-446655449999/csd",
"method": "POST"
}
404 Not Found
La sucursal especificada no existe.
{
"requestId": "abc123-def456",
"error": {
"message": "Branch not found",
"code": "NotFoundException",
"status": 404
},
"timestamp": "2025-01-15T10:30:00.000Z",
"path": "/branches/789e8400-e29b-41d4-a716-446655449999/csd",
"method": "POST"
}