AlchemyCode API docs

Empresas y credenciales

Una empresa es el RFC sobre el que se ejecutan los servicios. Cada empresa pertenece a una sola cuenta y guarda sus credenciales cifradas: e.firma del SAT y, si aplica, usuario y contraseña de IMSS e Infonavit.

Alta de empresa

POST/v1/companies201 Created
CampoTipoDescripción
rfcstringrequeridoRFC con homoclave. 12 caracteres para persona moral, 13 para persona física. Se normaliza a mayúsculas.
legal_namestringrequeridoRazón social o nombre, de 2 a 240 caracteres. Se usa en reportes y PDFs.
person_typeenumopcionalmoral o fisica. Si se omite se infiere de la longitud del RFC.
tax_regimestringopcionalClave del régimen fiscal del SAT, por ejemplo 601 o 626.
addressobjectopcionalDomicilio declarado. Se guarda tal cual para tu operación; no sustituye al del SAT.
external_refstringopcionalIdentificador en tu sistema (máx. 80). Con el proveedor alchemy guarda el cliente_id interno.
notesstringopcionalNotas internas de la empresa.
Shell
curl -sS -X POST "https://api.434alchemycode.com/v1/companies" \
  -H "Authorization: Bearer $ALCHEMY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rfc": "OMX2503255F9",
    "legal_name": "OMNICONMX, S.A. de C.V.",
    "tax_regime": "601",
    "address": {
      "calle": "Av. Insurgentes Sur",
      "numero_exterior": "1602",
      "colonia": "Crédito Constructor",
      "codigo_postal": "03940",
      "municipio": "Benito Juárez",
      "estado": "Ciudad de México"
    },
    "external_ref": "OMNICOM-001"
  }'
JSON
{
  "id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "object": "company",
  "rfc": "OMX2503255F9",
  "legal_name": "OMNICONMX, S.A. de C.V.",
  "person_type": "moral",
  "tax_regime": "601",
  "status": "active",
  "external_ref": "OMNICOM-001",
  "address": {
    "calle": "Av. Insurgentes Sur",
    "numero_exterior": "1602",
    "numero_interior": "Piso 12",
    "colonia": "Crédito Constructor",
    "codigo_postal": "03940",
    "municipio": "Benito Juárez",
    "estado": "Ciudad de México",
    "pais": "México"
  },
  "created_at": "2026-10-31T16:20:11.402331+00:00",
  "updated_at": "2026-10-31T16:20:11.402331+00:00",
  "credentials": {
    "fiel": {
      "configured": false,
      "status": null,
      "rfc": null,
      "serial": null,
      "valid_to": null,
      "verified_at": null
    },
    "imss": {
      "configured": false,
      "username": null,
      "status": null
    },
    "infonavit": {
      "configured": false,
      "username": null,
      "status": null
    }
  },
  "usage": {}
}
El RFC se valida con la estructura del SAT (^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$). Si no cumple, la respuesta es 422 RFC_INVALID con el campo marcado en errors.

El alta es idempotente por RFC dentro de tu cuenta: si ya existe, responde 409 COMPANY_DUPLICATE e incluye el company_id existente para que puedas reutilizarlo.

El plan Invitación · Beta permite hasta 5 empresas activas. Al llegar al límite, archiva una empresa o solicita ampliación; el error es 409 COMPANY_LIMIT_REACHED.

Carga de e.firma

El SAT exige la e.firma de la empresa para la constancia, la opinión 32-D y el domicilio fiscal. La carga es multipart/form-data con el certificado, la llave y la contraseña.

POST/v1/companies/{company_id}/credentials/sat200 OK · multipart
Shell
curl -sS -X POST "https://api.434alchemycode.com/v1/companies/9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17/credentials/sat" \
  -H "Authorization: Bearer $ALCHEMY_API_KEY" \
  -H "Accept: application/json" \
  -F "cer=@./omnicom.cer;type=application/x-x509-ca-cert" \
  -F "key=@./omnicom.key;type=application/octet-stream" \
  -F "password=$FIEL_PASSWORD"
JSON
{
  "object": "sat_credential",
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "status": "valid",
  "rfc": "OMX2503255F9",
  "serial": "3A7F19C2B84E",
  "subject": "CN=OMNICONMX SA DE CV, x500UniqueIdentifier=OMX2503255F9, O=OMNICONMX, C=MX",
  "valid_from": "2024-03-18T00:00:00+00:00",
  "valid_to": "2028-03-18T00:00:00+00:00",
  "cer_sha256": "b7c1e0a4f8d29c63507a4e1b8d3f2c6a9e0b4d7c1a8f5e2d3b6c9a0f4e7d1c28",
  "message": "e.firma validada y cifrada. Lista para consultar el SAT."
}

El mismo alta desde PHP, que es el caso más común en integraciones de escritorio y ERP:

PHP
<?php
declare(strict_types=1);

$companyId = '9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17';
$ch = curl_init("https://api.434alchemycode.com/v1/companies/{$companyId}/credentials/sat");

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('ALCHEMY_API_KEY'),
        'Accept: application/json',
    ],
    // No fijes Content-Type: cURL genera el boundary de multipart por ti.
    CURLOPT_POSTFIELDS => [
        'cer'      => new CURLFile(__DIR__ . '/omnicom.cer', 'application/x-x509-ca-cert', 'omnicom.cer'),
        'key'      => new CURLFile(__DIR__ . '/omnicom.key', 'application/octet-stream', 'omnicom.key'),
        'password' => getenv('FIEL_PASSWORD'),
    ],
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 60,
]);

$raw    = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$errno  = curl_errno($ch);
$error  = curl_error($ch);
curl_close($ch);

if ($errno !== 0) {
    exit("Error de red cURL {$errno}: {$error}");
}

$body = json_decode((string) $raw, true);
if ($status >= 400) {
    exit(sprintf('Error %s: %s', $body['code'] ?? 'UNKNOWN', $body['detail'] ?? 'sin detalle'));
}

printf("e.firma %s, vigente hasta %s\n", $body['status'], $body['valid_to']);

Validaciones que hace la API antes de guardar:

  • El .cer debe ser un certificado X.509 del SAT legible.
  • La .key debe abrir con la contraseña enviada (si no, FILE_INVALID).
  • El RFC del certificado debe coincidir con el RFC de la empresa.
  • Cada archivo debe pesar menos de 10 MB.
Una e.firma vencida se acepta con status: "expired" para que puedas ver la vigencia y planear la renovación, pero los servicios del SAT responderán FIEL_EXPIRED hasta que cargues una vigente.

Credenciales de IMSS e Infonavit

Se envían como JSON y se usan en el buzón IMSS, la opinión IMSS y la opinión Infonavit. El campo extra permite guardar datos adicionales que el portal solicite (por ejemplo, el registro patronal).

POST/v1/companies/{company_id}/credentials/{portal}portal: imss | infonavit
Shell
curl -sS -X POST "https://api.434alchemycode.com/v1/companies/9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17/credentials/imss" \
  -H "Authorization: Bearer $ALCHEMY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "B4712XH9071",
    "password": "ClavePortalImss#2026",
    "extra": {"registro_patronal": "B4712XH9071"}
  }'
JSON
{
  "object": "portal_credential",
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "portal": "imss",
  "username": "B4712XH9071",
  "status": "valid",
  "last_verified_at": "2026-10-31T16:26:38+00:00",
  "message": "Credenciales de IMSS cifradas y listas para usar."
}

Para consultar el estado sin exponer secretos, usa el endpoint de credenciales:

Shell
curl -sS "https://api.434alchemycode.com/v1/companies/9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17/credentials" \
  -H "Authorization: Bearer $ALCHEMY_API_KEY"
JSON
{
  "object": "credentials",
  "company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
  "rfc": "OMX2503255F9",
  "fiel": {
    "configured": true,
    "status": "valid",
    "rfc": "OMX2503255F9",
    "serial": "3A7F19C2B84E",
    "valid_to": "2028-03-18T00:00:00+00:00",
    "verified_at": "2026-10-31T16:22:07+00:00"
  },
  "imss": {"configured": true, "username": "B4712XH9071", "status": "valid"},
  "infonavit": {"configured": false, "username": null, "status": null},
  "required": [
    {"service": "sat.constancia", "needs": ["fiel"]},
    {"service": "imss.buzon", "needs": ["imss"]},
    {"service": "infonavit.opinion", "needs": ["infonavit"]}
  ]
}

Para eliminar credenciales (por ejemplo al dar de baja a un cliente) usa DELETE /v1/companies/{company_id}/credentials/{kind} con kind igual a fiel, imss o infonavit. Responde 204 y no se puede deshacer.

Cómo se guardan los secretos

El material sensible se cifra con envelope encryption antes de tocar la base de datos:

CapaQué esDónde vive
KEKLlave maestra de 32 bytes (AES vía Fernet) que envuelve las DEK.Variable ALCHEMY_ENCRYPTION_KEY, fuera de la base de datos.
DEKLlave de datos por cuenta, generada al crear la cuenta y envuelta con la KEK.Columna accounts.dek_wrapped.
Secreto.cer, .key, contraseña de e.firma y contraseñas de portales, cifrados con la DEK de la cuenta.Columnas *_ciphertext, con prefijo de versión v1:.
  • El texto plano sólo existe en memoria mientras se ejecuta una consulta; nunca se escribe en disco ni en logs.
  • La API nunca devuelve el material: sólo RFC, serial, vigencia, SHA-256 del certificado y estado.
  • Rotar la KEK no obliga a re-cifrar cada secreto: se re-envuelven las DEK de las cuentas.
  • Si se compromete la base de datos sin la KEK, los secretos no son legibles.
El cifrado protege el reposo, no reemplaza tu control de acceso. Una llave con credentials:write puede reemplazar credenciales: trátala como privilegiada y no la compartas con integraciones de sólo lectura.

Errores frecuentes de empresas y credenciales

CódigoHTTPCuándo ocurre
RFC_INVALID422El RFC no cumple la estructura del SAT.
COMPANY_DUPLICATE409Ya existe una empresa con ese RFC en tu cuenta.
COMPANY_LIMIT_REACHED409El plan alcanzó su máximo de empresas.
COMPANY_NOT_FOUND404El UUID no existe o pertenece a otra cuenta.
FILE_REQUIRED422Falta el .cer o el .key.
FILE_INVALID422El certificado o la llave no abren, o el RFC no coincide.
CREDENTIAL_MISSING422El servicio requiere credenciales que la empresa no tiene.
CREDENTIAL_INVALID422El portal rechazó el usuario o la contraseña.
FIEL_EXPIRED422La e.firma está vencida.
PAYLOAD_TOO_LARGE413El archivo supera 10 MB o el cuerpo 2 MB.

Buenas prácticas

  • Una empresa por RFC; no reutilices una empresa para otro contribuyente.
  • Carga la e.firma desde un backend con TLS; nunca desde el navegador del usuario final.
  • No envíes la contraseña de la e.firma por correo, chat ni tickets. Usa un formulario y un gestor de secretos.
  • Verifica valid_to y renueva antes del vencimiento; agenda un recordatorio a 60 días.
  • Usa external_ref para mapear tu cliente contra AlchemyCode y evitar duplicados.
  • Archiva (DELETE) las empresas que ya no operan: el borrado es lógico y conserva la auditoría.
  • Elimina credenciales al terminar la relación con un cliente; la baja de la empresa no las borra.