Catalog DTOs
Full schemas of the DTOs used by the catalog endpoints: products, customers, code catalogs, and brands.
ProductoApiRequestDto
Body of POST /api/v1/productos.
interface ProductoApiRequestDto {
/** REQUIRED. Integrator's own SKU/code. Upsert key. */
codigo: string;
/** REQUIRED. */
nombre: string;
descripcion?: string | null;
/** REQUIRED. Hacienda CABYS code, minimum 13 digits. Tax data — goes into the XML. */
codigoCabys: string;
/** REQUIRED. Unit-of-measure code from Hacienda's catalog
(GET /catalogos/unidades-medida). Tax data — goes into the XML. */
unidadMedida: string;
/** REQUIRED. > 0. */
precioVenta: number;
/** REQUIRED. > 0. */
precioCosto: number;
/** Default 0. Informational only — not recalculated against precioVenta/precioCosto. */
porcentajeGanancia?: number;
idCategoria?: number | null;
idSubcategoria?: number | null;
/** Optional. If omitted on a CREATE, the product is left WITHOUT a brand (shown as 0 in
the response). On an UPDATE, if omitted, the product keeps the brand it already had —
this is one of the two exceptions to the update not being partial. */
idMarca?: number | null;
/** Optional. Same rule as idMarca, applied to the supplier. */
idProveedor?: number | null;
/** Hacienda code-type code ("01" from the seller, "02" from the buyer, …).
Defaults to "01" if omitted. */
tipoCodigo?: string | null;
/** Only applies when creating (default true). On an update the product keeps its
current status, and sending a value different from the current one returns a 400. */
estado?: boolean;
/** REQUIRED. At least 1 line. On an update it replaces the product's entire tax
list — it is not merged with the one it already had. */
impuestos: ProductoApiImpuestoDto[];
}Field validations
| Field | Rule | Error if it fails |
|---|---|---|
codigo | Not empty after trimming whitespace. | 400 — Debe indicar 'codigo'. |
nombre | Not empty after trimming whitespace. | 400 — Debe indicar 'nombre'. |
codigoCabys | Minimum 13 digits. | 400 — El código CABYS debe tener 13 dígitos. |
unidadMedida | Not empty. | 400 — Debe indicar 'unidadMedida'. |
precioVenta | > 0. | 400 — Debe indicar 'precioVenta'. |
precioCosto | > 0. | 400 — Debe indicar 'precioCosto'. |
impuestos | Non-empty array, each line with codigo. | 400 — Debe indicar al menos un impuesto en 'impuestos'. / 400 — Cada línea de 'impuestos' debe indicar 'codigo'. |
ProductoApiImpuestoDto
A product’s tax line.
interface ProductoApiImpuestoDto {
/** REQUIRED. Tax code from Hacienda's catalog (e.g. "01" = VAT). */
codigo: string;
porcentaje: number;
/** Rate code within the tax (e.g. "08" = general 13% rate). */
codigoTarifa?: string | null;
}ProductoApiResponseDto
Returned by POST /api/v1/productos and GET /api/v1/productos/{codigo}.
interface ProductoApiResponseDto {
idProducto: number;
codigo: string;
nombre: string;
descripcion?: string | null;
codigoCabys: string;
unidadMedida: string;
precioVenta: number;
precioCosto: number;
idCategoria?: number | null;
idSubcategoria?: number | null;
/** 0 = no brand assigned. Not "brand number 0". */
idMarca: number;
/** 0 = no supplier assigned. Not "supplier number 0". */
idProveedor: number;
estado: boolean;
impuestos: ProductoApiImpuestoDto[];
/** Non-blocking. Only populated when saving auto-corrected the unit of measure
(a service CABYS with an incompatible unit). It travels only in the response of
the POST that generated it — GET /productos/{codigo} always returns it as null. */
advertencia?: string | null;
}ClienteApiRequestDto
Body of POST /api/v1/clientes. Upsert by identificacion: created if it doesn’t exist, partially updated if it does.
interface ClienteApiRequestDto {
/** REQUIRED. Upsert key. Cannot be changed once the customer is created. */
identificacion?: string | null;
/** Hacienda identification-type code (01 individual, 02 corporate, …).
REQUIRED when creating. On an update, "" is ignored. */
tipoIdentificacion?: string | null;
/** REQUIRED when creating. On an update, "" is ignored. */
nombre?: string | null;
/** On an update, "" DOES apply (ends up empty). */
apellidos?: string | null;
/** Internal/trade name. Only applies to corporate ID ("02"). */
nombreInterno?: string | null;
/** REQUIRED when creating — an empty string does not pass validation. On an
update, if sent, it must be a valid email: it cannot be cleared. */
correo?: string | null;
/** REQUIRED when creating. Normalized. On an update, if sent, it must be a
valid phone number: it cannot be cleared. */
telefono?: string | null;
/** On an update, "" DOES apply (ends up null). */
direccion?: string | null;
/** On an update, "" DOES apply (ends up null). */
comentario?: string | null;
/** Defaults to false when creating. On an update it is only overwritten if the
field is present in the JSON — including an explicit false. */
pagaTimbre?: boolean | null;
/** Marker; tax exemptions themselves are not managed through this endpoint. */
exonerado?: boolean | null;
limiteCredito?: number | null;
}The field-by-field semantics of the partial update are in Create or update customer.
ClienteApiResponseDto
Returned by POST /api/v1/clientes and GET /api/v1/clientes/{identificacion}.
interface ClienteApiResponseDto {
idCliente: number;
identificacion: string;
tipoIdentificacion: string;
nombre: string;
apellidos: string;
nombreInterno?: string | null;
correo: string;
telefono: string;
direccion?: string | null;
comentario?: string | null;
estado: boolean;
exonerado: boolean;
pagaTimbre: boolean;
limiteCredito?: number | null;
/** Added in the 2026-08-31 release. The customer's exemption documents: this is where
the idExoneracion sent per line at issuance comes from (see Tax exemptions). All
are returned, current or not, with `vigente` already resolved by the server. */
exoneraciones: ExoneracionClienteApiDto[];
}
interface ExoneracionClienteApiDto {
/** The id sent as idExoneracion on the document's line. */
idExoneracion: number;
/** Exemption document type code (Hacienda catalog). */
tipoDocumento: string;
/** Document type description, for display. */
tipoDocumentoNombre: string;
numeroDocumento: string;
nombreInstitucion: string;
fechaEmision?: string | null;
fechaExpiracion?: string | null;
/** Authorized percentage. At issuance it's capped at the product's real rate: a line
can't be exempted from more tax than it actually carries. */
porcentajeExoneracion?: number | null;
/** Whether the document is active in the tenant's catalog. */
estado: boolean;
/** Whether it can be used TODAY: active and not expired. Same condition the server
validates at issuance — an exemption with vigente:false returns 400 if its
idExoneracion is used. */
vigente: boolean;
}Read-only DTOs
DTOs returned by the code catalogs.
interface MedioPagoApiDto {
/** Use as medioPago when issuing. */
codigo: string;
nombre: string;
permiteDescuento: boolean;
}
interface CondicionVentaApiDto {
/** Use as condicionVenta when issuing. */
codigo: string;
nombre: string;
}
interface UnidadMedidaApiDto {
/** Unit-of-measure code from Hacienda's catalog. */
codigo: string;
nombre: string;
reduceInventario: boolean;
}
interface ActividadEconomicaApiDto {
codigo: string;
descripcion: string;
/** true = the tenant's default activity when codigoActividad is not given when issuing. */
principal: boolean;
}
interface PuntoVentaApiDto {
/** Use as idPuntoVenta when issuing. */
id: number;
nombre: string;
activo: boolean;
}
interface TipoCodigoApiDto {
/** Use as tipoCodigo when creating a product. */
codigo: string;
descripcion: string;
}
interface MarcaApiDto {
/** Use as idMarca when creating a product. */
id: number;
nombre: string;
/* No 'activo' field: the listing already returns only active brands. */
}