Nota de Crédito (05)
La Nota de Crédito (tipo 05) se emite para ajustar un CCF (tipo 03) ya emitido. Se usa típicamente para:
- Devoluciones de mercadería.
- Correcciones de precio o cantidad posteriores a la emisión.
- Anulación de un CCF que ya salió de la ventana de invalidación directa.
- Descuentos aplicados después de la facturación.
La NC no aplica a Facturas (01), Exportaciones (11), ni SUEX (14). Para esos tipos se usa Anulación directa.
/credit-memo vs /invalidate — cuándo usar cada uno
Cuando necesitas revertir un CCF tienes dos caminos:
| Situación | Endpoint | Qué hace |
|---|---|---|
| Ajuste parcial (p.ej. devolución de 2 items de 10) | POST /credit-memo con lines | Genera NC parcial, deja el CCF vigente. |
| Ajuste total intencional | POST /credit-memo con full_adjustment: true | Genera NC completa, marca el CCF como invalidado. |
| Anular CCF completo dentro de ventana de invalidación | POST /invalidate/{id} | Anulación directa en MH. Más simple. |
| Anular CCF completo fuera de ventana de invalidación | POST /invalidate/{id} | El API crea automáticamente una NC porque el MH ya no permite anulación directa. |
Si solo quieres anular un CCF completo, usa /invalidate — el API decide solo si hacer anulación directa o generar una NC automática. Usa /credit-memo cuando necesitas control explícito sobre las líneas ajustadas.
Detalles técnicos
| Método | POST |
| URL | https://ocote.io/api/connect/credit-memo |
| Content-Type | application/json |
| Autenticación | Header Authorization: Bearer odt_... |
Request
Campos
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
document_id | string | Sí | UUID o external_ref del CCF a ajustar. |
full_adjustment | bool | No (default false) | Si true, genera NC completa (clona todas las líneas del CCF) e invalida el CCF. |
lines | array | No | Líneas específicas a ajustar. Solo válido con full_adjustment: false. Si no envías líneas y full_adjustment: false, el API clona todas las del CCF pero no invalida. |
external_ref | string | No | Tu identificador idempotente para la NC. |
Líneas (lines)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
description | string | Sí | Descripción del ítem ajustado (típicamente igual al del CCF original). |
quantity | float | Sí | Cantidad a ajustar. |
unit_price | float | Sí | Precio unitario con IVA incluido. |
discount_percentage | float | No (default 0) | Descuento. |
Los tres modos de emisión
Modo 1: Ajuste completo explícito (full_adjustment: true)
El API copia todas las líneas del CCF original a la NC y marca el CCF como invalidado. Equivale a una anulación pero genera un documento fiscal explícito de ajuste.
{
"document_id": "CCF-ENLACES-001",
"full_adjustment": true,
"external_ref": "NC-DEV-TOTAL-001"
}
Úsalo cuando quieres un documento de anulación formal con todas las líneas visibles.
Modo 2: Ajuste parcial (full_adjustment: false + lines)
Solo ajustas lo que especificas en lines. El CCF queda vigente pero con monto ajustado.
{
"document_id": "CCF-ENLACES-001",
"full_adjustment": false,
"lines": [
{
"description": "Laptop Dell XPS devuelta",
"quantity": 1,
"unit_price": 1200.00
}
],
"external_ref": "NC-DEV-PARCIAL-001"
}
Úsalo para devoluciones reales (regresó 1 item de 10) o descuentos post-facturación.
Modo 3: Clonación sin invalidar (sin lines, full_adjustment: false)
El API clona todas las líneas del CCF pero no lo invalida. Es útil para "anotar" un ajuste completo sin anular fiscalmente el CCF.
{
"document_id": "CCF-ENLACES-001",
"external_ref": "NC-AJUSTE-001"
}
Es el modo menos común. Si necesitas anular completamente, prefiere Modo 1 o el endpoint /invalidate.
Validaciones
El API valida sobre el CCF antes de procesar:
| Validación | HTTP | Mensaje |
|---|---|---|
| CCF no existe o no es tuyo | 404 | Documento no encontrado |
| Documento referenciado no es CCF | 400 | Solo se pueden emitir Notas de Credito sobre CCF (tipo 03) |
| CCF ya invalidado | 400 | Este CCF ya fue invalidado |
| CCF ya completamente ajustado | 400 | Este CCF ya fue completamente ajustado |
| CCF con más de 90 días hábiles | 400 | Este CCF tiene mas de 90 dias y no puede ser ajustado |
La NC directa vía /credit-memo solo aplica mientras el CCF tiene menos de 90 días hábiles desde su emisión. Después de eso, el API rechaza el ajuste y debes usar otros mecanismos contables. Si tu intención era simplemente anular un CCF antiguo completo, usa POST /invalidate/{ccf_id} — ese endpoint sí maneja automáticamente el caso de fuera de ventana generando la NC equivalente.
Response
Además de los flags estándar (Respuestas y estados), la NC devuelve campos específicos:
| Campo | Tipo | Descripción |
|---|---|---|
document_id | string (UUID) | ID de la NC creada. |
external_ref | string | El que enviaste (o autogenerado). |
control_number | string | Correlativo de la NC (DTE-05-...). |
generation_code | string | Código de generación MH. |
reception_stamp | string | Sello MH de la NC. |
ccf_control_number | string | Correlativo del CCF ajustado. |
ccf_adjusted_amount | float | Monto total ajustado (suma de líneas de la NC). |
ccf_fully_adjusted | bool | true si el CCF quedó completamente ajustado tras esta NC. |
dte | object | Schema firmado completo de la NC (solo si dte_success: true). |
Ejemplo: devolución parcial
Escenario: emitiste un CCF por tres productos ($500, $300, $200 = $1 000 gravable). El cliente devuelve solo el producto de $300. Emites NC parcial:
curl -X POST https://ocote.io/api/connect/credit-memo \
-H "Authorization: Bearer odt_xxx" \
-H "Content-Type: application/json" \
-d '{
"document_id": "CCF-ENLACES-TEST-001",
"external_ref": "NC-ENLACES-TEST-001",
"full_adjustment": false,
"lines": [
{
"description": "Silla ergonómica de oficina",
"quantity": 1,
"unit_price": 339.00
}
]
}'
const { data } = await axios.post(
'https://ocote.io/api/connect/credit-memo',
{
document_id: 'CCF-ENLACES-TEST-001',
external_ref: 'NC-ENLACES-TEST-001',
full_adjustment: false,
lines: [
{ description: 'Silla ergonómica de oficina',
quantity: 1, unit_price: 339.00 },
],
},
{ headers: { Authorization: `Bearer ${process.env.OCOTE_API_KEY}` } }
);
r = requests.post(
"https://ocote.io/api/connect/credit-memo",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"document_id": "CCF-ENLACES-TEST-001",
"external_ref": "NC-ENLACES-TEST-001",
"full_adjustment": False,
"lines": [
{
"description": "Silla ergonómica de oficina",
"quantity": 1,
"unit_price": 339.00,
},
],
},
)
Respuesta exitosa
{
"success": true,
"dte_success": true,
"contingency": false,
"rejected": false,
"posted": true,
"is_duplicate": false,
"document_id": "63e82935-9d98-43db-b2b8-bc669364885c",
"external_ref": "NC-ENLACES-TEST-001",
"control_number": "DTE-05-M001P002-000000000000001",
"generation_code": "63E82935-9D98-43DB-B2B8-BC669364885C",
"reception_stamp": "20260421145823...",
"ccf_control_number": "DTE-03-M001P002-000000000000008",
"ccf_adjusted_amount": 339.00,
"ccf_fully_adjusted": false,
"observaciones": [],
"dte_state": "",
"dte_message": ""
}
Observa:
ccf_control_numberapunta al CCF ajustado — útil para tu conciliación.ccf_adjusted_amount: 339.00es el monto que esta NC descuenta del CCF.ccf_fully_adjusted: falseindica que el CCF aún tiene monto vigente — podrías emitir más NC si siguen llegando devoluciones, siempre dentro de los 90 días hábiles desde la emisión del CCF.
Ajuste total
Para anular un CCF completo vía NC (dentro de ventana):
curl -X POST https://ocote.io/api/connect/credit-memo \
-H "Authorization: Bearer odt_xxx" \
-H "Content-Type: application/json" \
-d '{
"document_id": "CCF-ENLACES-TEST-002",
"full_adjustment": true,
"external_ref": "NC-ANULA-CCF-002"
}'
Este modo es equivalente a lo que hace POST /invalidate/{id} internamente cuando el CCF está fuera de ventana. La diferencia es que aquí tú tomas la decisión explícita.
Idempotencia y retry
Aplican las mismas reglas de Retry e idempotencia:
- Mismo
external_refdevuelto con éxito →is_duplicate: true, la NC original. - Mismo
external_refque falló previamente → reutiliza registro con el correlativo original, actualizando datos del payload nuevo. - El
document_iddel CCF no se puede cambiar en retry. El API ignoradocument_iddel payload nuevo y conserva el CCF vinculado originalmente. Si quieres emitir NC sobre otro CCF, usa otroexternal_ref.
Descargar archivos
El endpoint POST /credit-memo no retorna invoice_url ni json_url en el response — solo los datos fiscales. Para obtener los URLs de descarga de una NC emitida, consulta el endpoint de estado:
curl -H "Authorization: Bearer odt_xxx" \
https://ocote.io/api/connect/credit-memo/status/NC-ENLACES-TEST-001
La respuesta incluye invoice_url (PDF carta) y json_url (DTE firmado). Las NC no generan ticket térmico; solo los dos archivos mencionados. Ver Descargar archivos para detalle de auth dual y tabla completa por tipo de documento.
Ver también
- Crédito Fiscal (03) — emisión del CCF original.
- Anular DTE — incluye la creación automática de NC para CCF fuera de ventana.
- Retry e idempotencia — comportamiento en reintentos de NC.
- Respuestas y estados — semántica de flags.