Cambios del SDK y notas de comportamiento del API de FiscalAPI que afectan a quien usa el SDK.
webhooks.verify(headers, body, secret, tolerance=webhooks.DEFAULT_TOLERANCE)(módulofiscalapi.webhooks, también accesible comofrom fiscalapi import webhooks) verifica una entrega de webhook de FiscalAPI y devuelve suWebhookEvent: valida la firma Standard Webhooks (HMAC-SHA256 sobrewebhook-id.webhook-timestamp.cuerpocon el secretowhsec_…, comparada en tiempo constante conhmac.compare_digest) y quewebhook-timestampno difiera del reloj del receptor más que la tolerancia (timedelta), hacia atrás o hacia adelante (por omisiónwebhooks.DEFAULT_TOLERANCE, 5 minutos). Acepta cualquier firmav1válida del headerwebhook-signature(varias, separadas por espacio) y el secreto con o sin el prefijowhsec_.secretacepta un secreto o una lista de secretos para la rotación (FiscalAPI firma con el nuevo y el anterior durante 24 h).headerses elrequest.headersde Flask, Django o FastAPI (o undict); los nombres se comparan sin distinguir mayúsculas.- Opera sobre el cuerpo crudo tal como llegó, de preferencia en
bytes(request.get_data(),request.body,await request.body()); unstrse firma con su UTF-8. Un cuerpo reserializado no verifica. Ejemplo para Flask en el README (sección 12). - Una entrega que no se puede tratar como auténtica (falta un header, ninguna firma coincide, timestamp fuera de la tolerancia, un cuerpo o un header que no es texto UTF-8 válido, revisado antes de la firma, o un cuerpo firmado que no es un evento JSON) lanza
WebhookVerificationException(exportada desdefiscalapi), con la causa original en__cause__; un secreto que no es base64 o está vacío, una lista de secretos vacía o una tolerancia negativa lanzanValueError, y un argumentoNoneo de otro tipo lanzaTypeError(errores de configuración del receptor), que en una lista de secretos indican la posición del secreto y nunca su valor.
- Opera sobre el cuerpo crudo tal como llegó, de preferencia en
- Modelos nuevos exportados desde
fiscalapi(pydantic, como los demás):WebhookEvent, el mismo sobre para todo evento, conid(igual al headerwebhook-id, se repite en cada reintento),type,created_at(datetimeen UTC,tzinfo=timezone.utc; llega como ISO 8601 con fecha, hora y zona, y un epoch, una fecha sin hora o una hora sin zona rechazan la entrega conWebhookVerificationException),person_id(Nonesi el recurso no tiene persona) ydata(WebhookEventData) conrelated_object(WebhookRelatedObject:id,type,url),statusyprevious_status.related_object.urles la ruta relativa del GET v4 del recurso (por ejemplo/api/v4/invoices/{id}); el recurso se lee con el servicio de su tipo yrelated_object.id. typees texto y no un enum: las constantes deWebhookEventTypes(INVOICE_CANCELLATION_REQUESTED,INVOICE_CANCELED,INVOICE_CANCELLATION_REJECTED,DOWNLOAD_REQUEST_COMPLETED,DOWNLOAD_REQUEST_ABANDONED,DOWNLOAD_RULE_SUSPENDEDyWEBHOOK_TEST) son una ayuda para comparar, y un tipo o un campo que el SDK no conoce llega sin error.WEBHOOK_TEST(webhook.test) no se suscribe: llega solo al pulsar "Enviar prueba" (POST /api/v4/webhooks/{id}/test), conrelated_object.typewebhookyrelated_object.url/api/v4/webhooks/{id}, sin persona ni estados.- Cambio aditivo: nada existente cambia. Sin dependencias nuevas.
- Tipo de crédito
TICKET(3) para transferir y leer créditos de ticket:stamps.transfer_stamps()concredit_type=CreditType.TICKETlos transfiere con las mismas reglas que los timbres, y las transacciones (stamps.get_list(),stamps.get_by_id()) los devuelven comoCreditType.TICKET. El saldo de créditos de ticket se lee enbalancesde la persona (CreditBalanceconcredit_typeigual aCreditType.TICKET); un tipo ausente tiene saldo 0. Los valores existentes no cambian (STAMP1,VALIDATION2).
- Las transacciones con un tipo de crédito desconocido ya no fallan: el valor se conserva. Antes,
stamps.get_list()ystamps.get_by_id()lanzabanValidationErrorsi una transacción traía uncreditTypeque el SDK no conocía (por ejemplo el 3 antes de este cambio) y se perdía la respuesta completa. AhoraStampTransaction.credit_typeesOptional[Union[CreditType, int]]: un tipo conocido sigue llegando como miembro deCreditTypey uno desconocido llega como su número (int). El código que compara contraCreditType(t.credit_type == CreditType.STAMP) no cambia; si su código usa atributos del enum (t.credit_type.name), compruebe antesisinstance(t.credit_type, CreditType).
- Servicio de tickets (Auto CFDI):
tickets.create(conIdempotency-Keyopcional),get_listcon filtros,get_by_id,get_codeycancel, enclient.tickets(TicketService):create(ticket, idempotency_key=None):POST /api/v4/tickets; cobra 1 crédito de ticket y devuelveTicketIngestionResult(id,ticket_code,qr_url,status,self_invoice_eligible,eligible_untily los importes calculados). Conidempotency_keyenvía el headerIdempotency-Key(sin ella no lo envía): repetir la key con el mismo cuerpo responde 200 con el ticket original y sin cobrar. Un folio y serie ya activos del emisor responden 409 y un total que no cuadra 422, comoApiResponsefallida con el texto del API endetailsy sudataenerror_data.get_list(filters=None):GET /api/v4/ticketsconTicketListFilters:status(uno o más; se repite la llavestatus),person_id,details(conTrueenvíadetails=true: cada ticket trae sus conceptos con impuestos, sin la historia;Falseno se envía),global_invoice_id(factura global que ampara los tickets),from_/to(date, fecha civil de México;from_lleva guion bajo porquefromes palabra reservada),page_numberypage_size; los filtros enNoneno se envían.get_by_id(ticket_id, details=False): condetails=Truetraeitems(conitem_taxes) yevents.get_code(ticket_id)devuelveTicketCodeResult(ticket_code,qr_url) para reimprimir.cancel(ticket_id)usaDELETE(solo desdepending; el crédito no se devuelve).
- Modelos nuevos exportados desde
fiscalapi:Ticket,TicketIssuer,TicketItem,TicketItemTax,TicketEvent,TicketIngestionResult,TicketCodeResultyTicketListFilters.payment_datees undatetimeen hora civil de México (sin zona viaja comoyyyy-MM-ddTHH:mm:ss); los decimales (Decimal) se envían tal cual. - Enums
TicketStatus,TicketEventTypeyTicketActorType(str, Enum):status,events[].typeyevents[].actor_typesonUnion[<enum>, str]; un valor que el SDK no conoce llega como texto, sinValidationError. - El SDK de Python no define interfaces que el integrador implemente:
FiscalApiClientsolo gana el atributotickets; quien usa el cliente del SDK no cambia nada. - Ejemplo:
examples/ejemplos-tickets.py.
ticketIden el concepto de la factura para ligar la factura global a los tickets (Auto CFDI); se omite si es nulo. En Python esInvoiceItem.ticket_id(Optional[str], aliasticketId): enNoneno viaja en el JSON. Un 409 por tickets no disponibles traeunavailableTickets(ticketId,status) enerror_data. Ejemplo:examples/ejemplos-factura-global-tickets.py.
error_data(Optional[Any]): nuevo miembro (aditivo) con eldatacrudo de una respuesta de error (el dict o la lista tal cual): por ejemploexistingTicketIden el 409 detickets.create(response.error_data["existingTicketId"]) oexpectedTotalycomputedTotalen su 422. EsNoneen las respuestas exitosas y en los errores sindata.data,message,details,http_status_codeytrace_identifierno cambian (en un error,datasigue enNone).
- Se elimina
Person.capital_regime(estaba deprecado): el API no tiene régimen de capital, ignoraba el valor y nunca lo devolvía. Quitecapital_regime=...de losPerson(...)que construya y toda lectura deperson.capital_regime(ahora lanzaAttributeError); envíe la razón social sin régimen de capital enlegal_name. Pasarlo al constructor no lanza error, pero se descarta sin enviarse. TaxCredentialdeja de heredar deBaseDto: solo tienebase64_file,file_typeypassword(opcional en el .cer). Quiteid=...,created_at=...yupdated_at=...de losTaxCredential(...)que construya (se descartan sin enviarse) y toda lectura de esos atributos (ahora lanzaAttributeError). Un código que trate unTaxCredentialcomoBaseDto(por ejemploisinstance(cred, BaseDto)) ya no lo reconoce.- El API deja de devolver
stripeCustomerIdysubscriptionStatusen las personas (/api/v4/peopley la persona de las reglas de descarga): eran datos internos de Stripe. Este SDK nunca los modeló enPerson(el modelo ya los descartaba), así que el código que usa el modelo no cambia; si su integración los lee de la respuesta JSON cruda, quite esa lectura: el campo ya no llega. - Certificados: el API deja de devolver la contraseña de la llave privada (.key) en las consultas de certificados:
tax_files.get_list(),tax_files.get_by_id()y la respuesta detax_files.create()traenpasswordvacía (""); antes, la contraseña en claro (tambiénGET /api/v4/tax-files/allyGET /api/v4/tax-files/load, que este SDK no usa). Solotax_files.get_default_values()la devuelve (a la persona misma o al owner del tenant);tax_files.get_default_references()ya la devolvía vacía. El modeloTaxFileno cambia (la descripción del campo lo documenta): la contraseña solo se envía al subir contax_files.create(). Si su integración leetax_file.passwordde una de esas consultas, usetax_files.get_default_values()o la contraseña que ya conoce. Ningún método ni ejemplo del SDK lee la contraseña de una consulta.
- Nuevos campos opcionales:
phone_number(phoneNumber): lo aceptan la creación y la actualización de personas.balances: saldos por tipo de crédito (CreditBalance:credit_typeyavailable, solo lectura). Solo aparecen los tipos que la persona ha tenido; un tipo ausente tiene saldo 0, y un tipo que el SDK no conoce conserva su número.is_owner(isOwner):Truesi la persona es el owner de su tenant (solo lectura).country: país de residencia fiscal expandido (solo lectura).valid_to(validTo,datetime): fin de vigencia de la persona (solo lectura). La asigna el API; casi siempre esNoney es informativa (no limita el timbrado ni el acceso al API).
password: requerida al crear; al actualizar,Noneo vacía conserva la contraseña actual. El API nunca la devuelve.user_type_id:"C"(cliente, el valor por omisión al crear) o"U"(usuario)."T"(tenant) solo llega en respuestas: el API lo rechaza al crear y al actualizar solo lo acepta si la persona ya es"T".
trace_identifier(traceIdentifier): nuevo campo opcional (aditivo) con el identificador de la petición en los registros de Fiscalapi. El API lo envía en sus errores (por ejemplo, un 500 o un 502, cuyos textos piden enviarlo a soporte@fiscalapi.com) y el SDK lo copia ahora también en las respuestas fallidas, incluido el 400 de validación (antes lo descartaba); quedaNonesi el cuerpo no lo trae.message,detailsyhttp_status_codeno cambian. Igual quegetTraceIdentifier()del SDK de Java.
passworddeja de ser obligatorio en el modelo: el API solo lo exige en la llave privada (.key,file_type=1); en el certificado (.cer) no lo usa.
currency_codeesstr(antesLiteral["MXN", "USD", "EUR"]): acepta cualquier clave del catálogo c_Moneda del SAT (por ejemploCADoJPY), que el API valida (400 enComplement.Payment.CurrencyCodesi no está en el catálogo, viene en minúsculas o esXXX). Antes el modelo rechazaba localmente las monedas distintas de esas tres. El valor por omisión sigue siendo"MXN"; el código existente no cambia.
ProductTax.tax_id,ProductTax.tax_flag_id,ProductTax.tax_type_id,Person.sat_tax_regime_id,Person.sat_cfdi_use_id,Person.user_type_idytax_flag_codedeItemTax,PaidInvoiceTaxyLocalTaxsonstr(antesLiteralcon las claves del catálogo): el SDK envía el valor tal cual, como los SDK de .NET, Node.js, PHP y Java, y el API lo valida (400 de validación con la falla en el campo; ver las notas de productos, personas y claves de catálogo del timbrado). Antes pydantic rechazaba al construir el modelo (ValidationError, sin llamar al API) cualquier otro valor, incluida la cadena vacía"", que el API acepta en las personas (la guarda sin valor). El código que ya usaba claves del catálogo no cambia.TaxFile.file_typeyTaxCredential.file_typesonint(antesLiteral[0, 1]): el SDK envía el valor tal cual, como los SDK de .NET, Node.js, PHP y Java, y el API lo valida (entax_files.create(), 400 de validación enFileTypesi no es un tipo de archivo admitido; enTaxCredential, el timbrado y la cancelación por valores exigen el par CSD,0y1, y si no responden 400 enTaxCredentials). Antes pydantic rechazaba localmente2y3(certificado y llave privada FIEL), quetax_files.create()acepta, y fallaba al leer la respuesta detax_files.get_list()otax_files.get_by_id()con un archivo FIEL (ValidationError). El código que ya usaba0o1no cambia.4(Pfx) lo rechazatax_files.create()con 400 enFileType(ver la nota de certificados).CancelInvoiceRequest.cancellation_reason_codeesstr(antesLiteral["01", "02", "03", "04"]): el SDK envía el valor tal cual, como los SDK de .NET, Node.js, PHP y Java, y el API valida la clave contra el catálogo c_MotivoCancelacion antes del PAC (400 de validación enCancellationReasonCode; ver la nota de cancelación). Antes pydantic rechazaba localmente cualquier otro valor (ValidationError, sin llamar al API). El código que ya usaba01a04no cambia.
DownloadRequest.next_attempt_date(nextAttemptDate) queda obsoleto (lo indica su descripción) y se eliminará en una versión mayor. El API ya no devuelvenextAttemptDateen las solicitudes de descarga (download_requests.get_list(),download_requests.get_by_id(),download_requests.search()ydownload_requests.create()): siempre eranull, porque Fiscalapi no programa un siguiente intento por solicitud (revisa las solicitudes pendientes periódicamente). El campo sigue existiendo y valeNone, como antes: quite esa lectura (la fecha del último intento está enlast_attempt_date).
Person.tax_passwordes la contraseña de la llave privada (.key) que la persona guarda en su perfil; el API no la usa para sellar (al timbrar usa la contraseña de los certificados registrados o la detax_credentials). Solo la reciben con valor la propia persona y el owner del tenant; los demás recibenNone. Al actualizar,Nonela conserva (el SDK no envía losNone) y""la borra.ValidationFailure.attempted_value: en un 400 de validación, el valor de un secreto (contraseñas, códigos, tokens, archivos y contraseñas de CSD/FIEL) llega enmascarado como"[masked: n]"(nes su longitud), o"[masked]"si la falla es de un objeto o una lista que lo contiene.- El API ya no devuelve
twoFactorEnableden las personas (este SDK nunca lo modeló). TaxFile.tines opcional entax_files.create(): si se omite (Noneo vacío), el API usa el RFC de la persona. Si se envía, debe ser el RFC de la persona (sin distinguir mayúsculas); si no, el API responde 400 con la falla enTin(después de comprobar que puede gestionar los certificados de la persona; si no, 403). El API no guarda el valor enviado: eltindel archivo siempre es el RFC de la persona.- Las personas ya no traen
stripeCustomerIdnisubscriptionStatus(datos internos de Stripe).valid_toycommitted_balanceson de solo lectura: el API los ignora al crear o actualizar.committed_balancees un campo heredado que el API ya no calcula y siempre vale 0; para el saldo useavailable_balance,available_validation_balanceobalances. - Personas:
people.create()ypeople.update()con un correo que ya pertenece a otra persona, de esta o de otra cuenta (se compara sin distinguir mayúsculas ni los espacios de los extremos), responden 400 de validación con la falla enEmaily el mensajeThe email is already registered.(en el SDK,succeededesFalseydetailsvale"Email: The email is already registered."). Antes respondía otro 400 (una falla de Identity o el genéricoA record with the same unique values already exists.). Al actualizar, el correo actual de la persona no cuenta como repetido. El API guarda el correo sin los espacios de los extremos. - Personas:
people.update()ypeople.delete()pueden responder 409 condetailsThe person was modified by another request (for example, a failed sign-in). Reload it and try again.(en el SDK,http_status_code409 y ese texto endetails) si otra petición modificó a la persona mientras se procesaba: cuenta cualquier escritura a la persona, por ejemplo un inicio de sesión con su correo (exitoso o fallido) o un cambio en su suscripción. No se guarda nada: vuelva a consultar la persona y repita la llamada. Antes la escritura se guardaba igual y podía deshacer el cambio de la otra petición (el bloqueo de la cuenta por intentos fallidos o la sesión recién iniciada).PATCH /api/v4/people/{id}, que este SDK no usa, responde el mismo 409. - Personas:
people.delete()revoca también las API keys de la persona, en la misma operación: ya no autenticaban (el API responde 401 a una API key de una persona eliminada) y ahora tampoco aparecen enapi_keys.get_list()ni enapi_keys.get_by_id()(responde 404 conNo such resource for the given Id: <id>); antes seguían en el listado como habilitadas (apiKeyStatus1) y sin persona. - Personas:
people.create()ypeople.update()validan el correo y la contraseña con las reglas de Identity antes de guardar. Un correo con caracteres fuera dea-z,A-Z,0-9y-._@+(por ejemploñ, una vocal acentuada, un apóstrofo o un espacio intermedio) responde 400 de validación con la falla enEmail,errorCodeInvalidUserNamey el mensajeThe email contains characters that are not allowed: 'ñ'. Allowed characters: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+(con los caracteres rechazados entre comillas). Una contraseña que no cumple las reglas (al menos 6 caracteres, con una mayúsculaA-Z, una minúsculaa-z, un dígito0-9y un carácter que no sea letra ni dígito; las letras con acento y lañno cuentan como mayúsculas ni minúsculas) responde una sola falla enPasswordcon el mensajePassword must be at least 6 characters long and contain at least one uppercase letter (A-Z), one lowercase letter (a-z), one digit (0-9) and one character that is not a letter or a digit. Letters with accents and ñ are not uppercase or lowercase letters for this rule., el código del primer requisito que no se cumple enerrorCode(p. ej.PasswordTooShortoPasswordRequiresUpper) yattemptedValuenull(en el SDK,succeededesFalseydetailstrae la falla con el campo). Antes, al crear, esos valores llegaban a Identity y la falla traía el código enpropertyName(InvalidUserName,PasswordRequiresUpper…) con el texto de Identity (p. ej.Username '…' is invalid, can only contain letters or digits.) (en el SDK,detailsempezaba conInvalidUserName:y ahora conEmail:); una contraseña corta comoabcdaba dos fallas (MinimumLengthValidatoryPredicateValidator), y al actualizar el API guardaba un correo conño una contraseña comoÑandú12!(200). Si aun asípeople.create()falla en otra validación de Identity, la falla trae enpropertyNameel campo del request (EmailoPassword) y el código de Identity enerrorCode. - Facturas (
invoices.create()): con una moneda extranjera (currency_codedistinto deMXNy deXXX),exchange_rate0 o negativo responde 400 de validación con la falla enExchangeRatey el mensajeCon una moneda distinta de MXN, 'exchangeRate' (tipo de cambio) debe ser el del día de la operación (mayor que 0).(en el SDK,succeededesFalseydetailstrae la falla con el campo); antes pasaba la validación del API. ConMXNsigue siendo exactamente 1. El complemento de pagos sigue la misma regla en el tipo de cambio del pago (complement.payment.exchange_ratefrente acomplement.payment.currency_code): conMXN, exactamente 1 (si esNone, el API usa 1); con otra moneda, mayor que 0. Si no, responde 400 de validación con la falla enComplement.Payment.ExchangeRate(mensajeCon la moneda MXN, 'exchangeRate' (tipo de cambio) debe ser 1.o el de moneda extranjera); antes llegaba al PAC, que lo rechazaba, o el pago salía con totales en 0. - Cambios del API en los endpoints de cuenta, que este SDK no usa (se autentica con API key), así que no le afectan:
POST /api/v4/account/registeryPOST /api/v4/account/infocon un correo que ya pertenece a otra persona responden 400 de validación con la falla enEmail(NewEmaileninfo) y el mensajeThe email is already registered.;POST /api/v4/account/change-passwordcon la contraseña actual incorrecta responde 400 de validación con la falla enCurrentPassword(errorCodePasswordMismatch, mensajeIncorrect password.); en general, las fallas que vienen de Identity enaccount/change-password,account/info,account/reset-passwordyaccount/confirm-emailtraen enpropertyNameel campo del request que las causó (CurrentPassword,NewPassword,NewEmail,ResetCodeoCode) y el código de Identity enerrorCode(PasswordMismatch,InvalidToken, …), en lugar del código comopropertyName;POST /api/v4/account/refreshcon un refresh token vencido responde 401Invalid refresh tokenen lugar de emitir otro (con uno vigente devuelve el mismo token), así que una sesión iniciada conaccount/loginsolo se puede renovar hasta 7 días después del login. - Facturas (
invoices.create()): concurrency_codeXXX(facturas de pago y de traslado), unexchange_ratedistinto de 1 y de 0 responde 400 de validación con la falla enExchangeRatey el mensajeCon la moneda XXX, 'exchangeRate' (tipo de cambio) no aplica: se omite (o se envía 1 o 0).(en el SDK,succeededesFalseydetailstrae la falla con el campo); antes, con un valor mayor que 1 lo rechazaba el PAC (CFDI40116 en un ingreso, CRP20109 en un pago) y con uno entre 0 y 1 o negativo se timbraba ignorándolo. Con 1 (si esNone, el API usa 1) o 0 se sigue timbrando, sinTipoCambioen el XML. - Facturas de pago (
invoices.create()concomplement.payment): la moneda del pago (complement.payment.currency_code) y la de cada factura pagada (currency_codede cada elemento decomplement.payment.paid_invoices) se validan contra el catálogo c_Moneda del SAT, escritas tal cual: si se omiten, no están en el catálogo (también en minúsculas, comomxn) o sonXXX, responde 400 de validación con la falla enComplement.Payment.CurrencyCodeo enComplement.Payment.PaidInvoices[n].CurrencyCode(n= posición, desde 0) con el mensajeLa moneda '<valor>' no existe en el catálogo c_Moneda del SAT.,La moneda de un pago o de un documento pagado no puede ser XXX.o el de campo vacío (en el SDK,succeededesFalseydetailstrae la falla con el campo); antes llegaban al PAC, que las rechazaba (CFDI40113, CRP20213 o CRP20236). La moneda de la factura de pago (currency_code) debe serXXX: con otra, 400 de validación enCurrencyCodeconLa moneda de un comprobante de pago debe ser XXX.(antes, el PAC respondía CRP20104). Una factura sincurrency_coderesponde una sola falla enCurrencyCode(antes, dos). - Factura global (
invoices.create()conglobal_information.year): el año acepta el de la fecha de la factura (date) y los 5 anteriores, como el SAT (con fecha en 2026, de 2021 a 2026); antes solo el año en curso o el anterior, y por ejemplo 2021 a 2024 respondían 400 aunque el SAT los timbra. Fuera de ese rango responde 400 de validación con la falla enGlobalInformation.Yeary el mensaje'year' debe ser el año de la fecha del comprobante (2026) o uno de los 5 anteriores.(con el año de la fecha) (en el SDK,succeededesFalseydetailstrae la falla con el campo). - Más cambios del API en los endpoints de cuenta, que este SDK no usa:
POST /api/v4/account/register(Email,Password),POST /api/v4/account/info(NewEmail,NewPassword),POST /api/v4/account/change-passwordyPOST /api/v4/account/reset-password(NewPassword) aplican las mismas reglas de correo y contraseña que las personas (400 conInvalidUserNameo con la falla única de la contraseña); antesregistercreaba la cuenta con un correo conño una contraseña comoÑandú12!.reset-passwordvalida la contraseña nueva antes del código (con las dos inválidas, la falla es la deNewPassword). Enaccount/info,CurrentPasswordya no se valida con las reglas de formato (una incorrecta respondePasswordMismatchenCurrentPassword) y es obligatoria cuando vieneNewPassword: sin ella, 400 de validación enCurrentPasswordconCurrent password is required to set a new password.(antes, el mismo texto en un 400 sin campo). Restablecimiento de contraseña: el código quePOST /api/v4/account/forgot-passwordenvía por correo (elresetCodedePOST /api/v4/account/reset-password) pasa a ser de 6 dígitos (antes, un texto de unos 350 caracteres), vence a los 15 minutos (antes, a 1 día aunque el correo decía 15 minutos), sirve una sola vez y admite 5 intentos fallidos por persona cada 15 minutos (pedir otro código no da intentos nuevos; agotados, ningún código sirve hasta que terminan esos 15 minutos). UnresetCodeque no es de 6 dígitos responde 400 de validación enResetCodeconEl código debe tener 6 dígitos.sin gastar intento (también un código largo pedido antes de este cambio: hay que pedir otro); uno equivocado, vencido, ya usado o con los intentos agotados, igual que un correo desconocido, responde 400 enResetCode(errorCodeInvalidToken) conEl código no es válido o ya venció.(antesInvalid token.; el mismo texto enCodedeaccount/confirm-email, cuyo enlace no cambia).forgot-passwordrespondeSi el correo está registrado y confirmado, te enviamos un código de 6 dígitos para restablecer tu contraseña; vence en 15 minutos.(antesPassword reset instructions have been sent if the email is registered and confirmed.) yreset-password,La contraseña se restableció correctamente.(antesPassword has been reset successfully.). - Certificados:
tax_files.delete()hace una baja lógica: el certificado deja de aparecer en todas las consultas y el API deja de usarlo para timbrar; borra el archivo y su contraseña y conserva solo el registro (tipo, RFC, vigencia, quién lo eliminó y cuándo). La respuesta no cambia: 200 contrue, o 400 confalsesi no existe, ya se había eliminado o es de otra persona. Para volver a usar un certificado eliminado, súbalo de nuevo contax_files.create(). - Certificados: el servicio de certificados no tiene
update(tax_filesnunca lo tuvo en este SDK, así que su forma no cambia): el API retiróPUT /api/v4/tax-files/{id}, que ahora responde 405 (Method Not Allowed); antes nunca actualizaba un certificado (respondía un error). Para cambiar un certificado, suba el nuevo contax_files.create()y elimine el anterior contax_files.delete(). - Facturas (
invoices.create()): con el RFC genérico (XAXX010101000oXEXX010101000) el régimen fiscal del receptor debe ser616y su código postal el deexpeditionZipCode; con el régimen616el uso CFDI debe serS01oCP01; y una factura de ingreso (I) aXAXX010101000PUBLICO EN GENERALdebe traerglobalInformation. Si no, responde 400 de validación (en el SDK,succeededesFalseydetailstrae la falla con el campo), con todas las fallas juntas, antes de timbrar y sin consumir timbre, enRecipient.TaxRegimeCode(Con el RFC genérico (XAXX010101000 o XEXX010101000) el régimen fiscal del receptor (taxRegimeCode) debe ser 616 (CFDI40159).),Recipient.ZipCode(Con el RFC genérico (XAXX010101000 o XEXX010101000) el código postal del receptor (zipCode) debe ser el lugar de expedición (expeditionZipCode) (CFDI40149).),Recipient.CfdiUseCode(Con el régimen fiscal 616 del receptor, el uso de CFDI (cfdiUseCode) debe ser S01 o CP01 (c_UsoCFDI, CFDI40161).) oGlobalInformation(Un ingreso a XAXX010101000 'PUBLICO EN GENERAL' es una factura global: globalInformation es obligatorio (CFDI40130).). Vale igual con el receptor por valores y por referencia (recipient.id: se revisan los datos de la persona guardada). Antes las rechazaba el PAC con un 400 sin campo (CFDI40159, CFDI40149, CFDI40161 y CFDI40130). - Facturas: la factura que devuelven
invoices.create()yinvoices.get_by_id()traeInvoice.global_information: completa (periodicity_code,month_codeyyear) en una factura global, oNonesi no es global o si es una global timbrada antes de este cambio (el XML timbrado conserva la información global). Antes la respuesta no traía el campo (el modelo quedaba enNone). - Facturas (
invoices.create()): el tipo de factura y sus complementos deben corresponder, como exige el SAT:Iadmitecomplement.localTaxes,complement.cartaPorteycomplement.comercioExterior(o ninguno);E,complement.localTaxesycomplement.comercioExterior(o ninguno);Trequierecomplement.cartaPorteocomplement.comercioExterior;Prequierecomplement.paymentyN,complement.payroll;T,PyNno admiten otro complemento. Si falta el complemento que el tipo requiere o viene uno que no le corresponde (por ejemplopayrollen un ingreso,cartaPorteen un pago olocalTaxesen un traslado), responde 400 de validación con la falla enComplement(en el SDK,succeededesFalseydetailstrae la falla con el campo), antes de timbrar y con el mensaje del tipo:Un comprobante de tipo I (ingreso) no lleva complement o solo lleva complement.localTaxes, complement.cartaPorte o complement.comercioExterior.,Un comprobante de tipo E (egreso) no lleva complement o solo lleva complement.localTaxes o complement.comercioExterior.,Un comprobante de tipo T (traslado) requiere complement.cartaPorte o complement.comercioExterior y no admite otro complemento.,Un comprobante de tipo P (pago) requiere complement.payment y no admite otro complemento.oUn comprobante de tipo N (nómina) requiere complement.payroll y no admite otro complemento.EnIyE, uncomplementvacío (sin complementos, o con todos enNone) equivale a no enviarlo; enT,PyN, responde el 400 de este tipo. Antes, sin el complemento que el tipo requiere o con uncomplementvacío, respondía un error 500 (No mapper found for invoice request); con un complemento que no le correspondía, la factura llegaba al PAC, que la rechazaba (por ejemplo CRP20101 conpaymentfuera dePo CP114 concartaPorteenPoE), o el complemento sobrante se omitía sin aviso (por ejemplolocalTaxesen un pago), y unIconpayrollse enviaba como nómina (N).POST /api/v4/invoices/calculate, que este SDK no usa, responde igual, y con untypeCodefuera del catálogo responde 400 enTypeCode(antes, 500). - Facturas (
invoices.create()): el 400 de untypeCodefuera del catálogo diceEl tipo de comprobante '<valor>' no existe en el catálogo c_TipoDeComprobante del SAT.(antesPlease use the SAT catalog values: I,E,T,P,N,P, con laPrepetida; ver la nota de claves de catálogo del timbrado). - Facturas (
invoices.create()): una fecha ausente,null, vacía o que no se reconoce como fecha responde 400 de validación en el campo (en el SDK,succeededesFalseydetailstrae la falla con el campo) en lugar de llegar al PAC como0001-01-01(que la rechazaba con CFDI40101 o CFDI40999):date→Datecon'date' es obligatorio y debe ser una fecha con el formato yyyy-MM-ddTHH:mm:ss.;complement.payment.paymentDate→Complement.Payment.PaymentDatecon'paymentDate' es obligatorio y debe ser una fecha con el formato yyyy-MM-ddTHH:mm:ss.; ycomplement.payroll.paymentDate,initialPaymentDateyfinalPaymentDate→Complement.Payroll.PaymentDate,Complement.Payroll.InitialPaymentDateyComplement.Payroll.FinalPaymentDatecon'<campo>' es obligatorio y debe ser una fecha con el formato yyyy-MM-dd.('paymentDate','initialPaymentDate'o'finalPaymentDate'). Una fecha del año 1 (0001-01-01…) cuenta como ausente y responde el mismo 400 en el campo; antes, con la zona horaria de México (America/Mexico_City), el API la convertía a otra hora del año 1, no la reconocía como ausente y la factura llegaba al PAC (CFDI40101 o CFDI40999). - Facturas (
invoices.create()): una facturaI,EoTsinitems(o con la lista vacía) responde 400 de validación con la falla enItemsy el mensajeitems debe tener al menos un concepto en los comprobantes de tipo I, E y T.(en el SDK,succeededesFalseydetailstrae la falla con el campo), sin ir al PAC; antes la rechazaba el PAC (CFDI40999 o CP999: el nodoConceptoses obligatorio en el CFDI). EnPyNse siguen omitiendo: el API genera el concepto.POST /api/v4/invoices/calculate, que este SDK no usa, aplica la misma regla (antes respondía 400 sin mensaje) y, con un total 0 (todoTyP, o un ingreso con descuento del 100 %), responde 200 con el cálculo (antes, 400 consucceededfalse). - Facturas con Carta Porte o Comercio Exterior (
invoices.create()): el API valida los datos de esos complementos antes de timbrar (obligatorios, longitudes, patrones y catálogos del SAT): una falla responde 400 de validación con la falla en el campo, enComplement.CartaPorte.…oComplement.ComercioExterior.…(por ejemploComplement.CartaPorte.Ubicaciones[0].NumEstacionIdconNumEstacionId must be a valid c_Estaciones value.) (en el SDK,succeededesFalseydetailstrae la falla con el campo), sin consumir timbre; antes esas reglas no se aplicaban y el PAC rechazaba la factura (CPxxx, CCExxx o CFDI40101). La fecha de salida o llegada de cada ubicación de Carta Porte sigue la regla de las demás fechas: ausente, vacía, del año 1 o ilegible responde 400 enComplement.CartaPorte.Ubicaciones[n].FechaHoraSalidaLlegadacon'fechaHoraSalidaLlegada' es obligatorio y debe ser una fecha con el formato yyyy-MM-ddTHH:mm:ss.(antes llegaba al PAC como0001-01-01). Las claves de estación (c_Estaciones) se escriben como en el catálogo del SAT, con acento cuando lo llevan:Juárez0se acepta yJuarez0responde 400. ElNumRegIdTribdel receptor del complemento Comercio Exterior (complement.comercio_exterior.receptor.num_reg_id_trib) se ignora, como antes: el API usa el del receptor del CFDI (recipient.foreign_tin, o el de la persona receptora por referencias); puede omitirlo. - Facturas: los errores de lectura del request (un valor de otro tipo, JSON mal formado, cuerpo vacío o un campo obligatorio ausente o
nulldentro de un complemento, por ejemploComplement.CartaPorte.TranspInternacIdcon'transpInternacId' no debe estar vacío.) responden el mismo 400 de validación que las demás reglas, con la falla en el campo, en lugar de un ProblemDetails (application/problem+json): en el SDK, antesmessageeraError processing server error responseydetailsel cuerpo crudo; ahorasucceededesFalse,messageesSorry, it's not me, it's you.ydetailstrae las fallas comoCampo: mensajeseparadas por;. En esas fallaserrorCodeesnull. Un cuerpo que no se puede leer responde una sola falla: un valor de otro tipo, en su ruta (por ejemplo$.items[0].quantity), y un JSON mal formado, en$(la raíz del JSON), ambos con el texto en inglés del lector de JSON; un cuerpo vacío onull, en$conEl cuerpo de la petición es requerido y no debe estar vacío.El ProblemDetails de antes traía además enerrorsla clave del parámetro del endpoint (command,The command field is required.) y, con un cuerpo vacío, la clave vacía (""); ya no llegan. Vale para todos los endpoints. - Mensajes de validación en español (todos los endpoints): los
errorMessagegenerales de las validaciones del API pasan de inglés a español, por ejemplo'<Campo>' no debe estar vacío.(antes'<Campo>' must not be empty.) o'tipoCambioUSD' debe ser mayor que '0'., y también los de lectura del request:'transpInternacId' no debe estar vacío.(antesThe TranspInternacId field is required.),El cuerpo de la petición es requerido y no debe estar vacío.(antesA non-empty request body is required.) yEl valor 'abc' no es válido para PageNumber.(antesThe value 'abc' is not valid for PageNumber.). En el SDK cambia el texto dedetails;propertyName,errorCode,message(Sorry, it's not me, it's you.),details(One or more validation failures have occurred.) y la forma del 400 no cambian. Conservan su texto los mensajes propios de cada regla (varios en inglés, como los de personas y cuentas citados en estas notas; los del timbrado pasan al español, ver la nota de mensajes del timbrado) y los del lector de JSON (JSON mal formado o un valor de otro tipo, en inglés). Si su integración compara el texto del mensaje, use el campo de la falla (property_name) en su lugar. - Certificados, timbrado y cancelación por referencias:
tax_files.get_default_references(),tax_files.get_default_values(),invoices.create()con el emisor por referencia (issuer.id) yinvoices.cancel()de una factura emitida por referencias, cuando la persona (o el emisor) solo tiene vigente uno de los dos archivos del CSD, responden 404 conNo se encontró la llave privada de la persona: <id>oNo se encontró el certificado de la persona: <id>(en el SDK,http_status_code404 y ese texto endetails); suba el archivo que falta. Antes, las referencias por defecto y la cancelación respondían un error 500,get_default_values()respondía 200 con un solo archivo y el timbrado respondía 400 conAmbos certificado y clave privada son requeridos para crear un credencial FIEL o CSD.Sin ningún archivo vigente, las referencias por defecto, el timbrado y la cancelación responden el mismo 404 conNo se encontró tal recurso. Verifica los archivos fiscales de la persona: <id>; antes, ese 404 solo lo daban las referencias por defecto, el timbrado respondía 400 conNo se proporcionaron archivos fiscales para crear un credencial FIEL o CSD.y la cancelación 400 en inglés conWhen issuer data is missing, issuer's certificates must be configured.get_default_values()sin ningún archivo vigente sigue respondiendo 200 con la lista vacía. En el timbrado,<id>es elissuer.idenviado; en la cancelación, el id del emisor de la factura. - Certificados:
tax_files.get_default_references()aplica la misma regla quetax_files.get_default_values(): solo la persona misma o el owner del tenant obtienen las referencias de sus certificados. Si la API key es de una persona que no es el owner y pide las referencias de otra persona, responde 403 conSolo el propietario de la cuenta puede gestionar las credenciales de otra persona: correo, contraseña, API keys y certificados.(en el SDK,http_status_code403 y ese texto endetails); antes respondía 200 con los ids y la vigencia de los certificados de esa persona. - Reglas de reporte (
/api/v4/reportrules, que este SDK no usa):PUTyDELETEsobre la regla predeterminada de FiscalAPI (compartida con todas las cuentas) responden 403 conOperation denied.(en el SDK,http_status_code403 y ese texto endetails); antes el API permitía modificarla o borrarla.DELETEde un id que no existe responde 404 (antes, 400 confalse). - Facturas por referencias (
invoices.create()conissuer.id,recipient.idoitems[n].id): cuando a la persona o al producto guardado le falta un dato fiscal, el API responde 400 en español con un solo formato,La persona emisora '<id>' no tiene <dato> (<campo>).,La persona receptora '<id>' no tiene <dato> (<campo>).oEl producto '<id>' no tiene <dato> (<campo>)., por ejemploLa persona emisora '<id>' no tiene RFC (tin).oLa persona receptora '<id>' no tiene uso de CFDI (satCfdiUseId).(en el SDK,http_status_code400 y ese texto endetails). En las personas el dato esRFC (tin),razón social (legalName),régimen fiscal (satTaxRegimeId)ocódigo postal (zipCode), y en la receptora tambiénuso de CFDI (satCfdiUseId); en el producto,clave de producto o servicio (satProductCodeId),clave de unidad (satUnitMeasurementId),objeto de impuesto (satTaxObjectId)odescripción (description). Antes, 400 en inglés sin el id:Issuer Tin is required,Issuer LegalName is required,Issuer SatTaxRegimeId is required,Issuer ZipCode is required,Recipient <Campo> is requiredyProduct <Campo> is required. El código 400 y la forma del error no cambian; un id que no existe sigue respondiendo 404 conNo such resource for the given Id: <id>. - Cancelación por referencias (
invoices.cancel()conid): con el id de una factura que no existe o que es de otra cuenta, el API responde 404 conNo such resource for the given Id: <id>(en el SDK,http_status_code404 y ese texto endetails); antes respondía un error 500 (Something went wrong on our end.). Si la persona emisora de la factura ya no existe (se eliminó), responde 400 conNo se encontró la persona emisora '<id>' de esta factura. Para cancelarla, envía invoiceUuid, tin y taxCredentials (cancelación por valores).(<id>es el id del emisor; antes, 400 en inglés conWhen issuer data is missing, issuer's Id must be provided.): cancélela por valores coninvoice_uuid,tinytax_credentials. Si la persona emisora no tiene RFC, responde 400 conLa persona emisora '<id>' no tiene RFC (tin).(antesLa persona '<id>' no tiene datos fiscales). - Reglas de descarga (
download_rules.create(),download_rules.update()ydownload_rules.delete()): solo el owner del tenant crea, actualiza o elimina una regla de la persona owner (person_iddel owner). Con la API key de otra persona del tenant, el API responde 403 conSolo el propietario de la cuenta puede modificar al propietario de la cuenta.(en el SDK,http_status_code403 y ese texto endetails) y la regla no se crea, no cambia ni se elimina; antes el API lo permitía (al crear, respondía 200 o el error de asientos o de FIEL; ahora el 403 llega antes de esas revisiones). Las reglas de las demás personas no cambian. - Cambio de valor (corrige un error), Descarga masiva:
download_requests.get_xmls()(GET /api/v4/download-requests/{id}/xmls,Xml.date) ydownload_requests.get_metadata_items()(GET /api/v4/download-requests/{id}/meta-items,MetadataItem.invoice_date,MetadataItem.sat_certification_dateyMetadataItem.cancellation_date) devuelven ahora la fecha y hora del CFDI/SAT tal cual (la hora local del lugar de expedición, igual a laFechadel XML, y la de la metadata del SAT), sin depender deX-TIME-ZONE(FiscalApiSettings.time_zone); antes se les restaba el desfase de esa zona (6 h conAmerica/Mexico_City, la predeterminada del SDK). Igual enGET /api/v4/download-xmls/{id}yGET /api/v4/download-meta-items/{id}, que este SDK no usa. Ejemplo: XML conFecha="2025-06-18T18:58:30": antes"date": "2025-06-18T12:58:30.000"(Xml.date=datetime(2025, 6, 18, 12, 58, 30)), ahora"date": "2025-06-18T18:58:30.000"(datetime(2025, 6, 18, 18, 58, 30)). Ningún tipo cambia; si su código compensaba el desfase (por ejemplo, sumando 6 h), quite esa compensación. Los demás campos de fecha (created_at,updated_at,start_date,end_dateylast_attempt_date) no cambian. - Consultas por id: con un id que no existe o que es de otra cuenta,
invoices.get_by_id(),invoices.get_xml(),invoices.get_pdf()(invoice_id),invoices.get_status()(id),people.get_by_id(),people.update(),people.delete(),stamps.get_by_id(),products.get_by_id(),download_rules.get_by_id(),download_rules.delete(),download_requests.get_by_id(),download_requests.delete(),download_requests.download_package(),download_requests.download_sat_request()ydownload_requests.download_sat_response()responden 404 conNo such resource for the given Id: <id>(en el SDK,http_status_code404 y ese texto endetails), la misma respuesta que ya daban otras rutas por id (por ejemploinvoices.send()); elmessageesSorry, it's not me, it's you.Antes cada una tenía su propio texto:invoices.get_by_id(),Not found such resource: <id>;invoices.get_xml(),No such resource for the given id: <id>;invoices.get_status(),stamps.get_by_id(),download_rules.delete(),download_requests.delete(),download_requests.download_sat_request()ydownload_requests.download_sat_response(),No such resource for given id: <id>;people.get_by_id()ypeople.delete(),No such resource for the given id:<id>;products.get_by_id(),Product with id: <id> not found;download_rules.get_by_id(),Rule with id: <id> not found;download_requests.get_by_id(),Sat request with id: <id> not found; ydownload_requests.download_package(),No existe una solicitud de descarga con id <id>.con elmessageNot Found.invoices.get_pdf()ypeople.update()respondían 400 (conNo such resource for given id: <id>yNo such resource for the given id: <id>); ahora, 404.PATCH /api/v4/people/{id}, que este SDK no usa, responde igual quepeople.update(). Si su integración reconoce el "no encontrado" por el texto, use el código 404. - Catálogos (
catalogs.get_record_by_id()): una clave que no existe en el catálogo responde 404 conNo such resource for the given Id: <id>(en el SDK,http_status_code404 y ese texto endetails); antes respondía 400 conRecord not found for <catálogo> with ID <id>enmessageydetailsvacío. Un nombre de catálogo que no existe responde 404 (ver la nota de nombres de catálogo); las claves que existen responden igual que antes. - Productos y datos de empleado y empleador (BE-238/BE-239;
products.create()yproducts.update(),people.employee.create()ypeople.employee.update(),people.employer.create()ypeople.employer.update()):unit_pricede un producto debe ser mayor que 0 y como máximo999999999999.999999(12 enteros y 6 decimales); por encima, 400 de validación enUnitPriceconEl precio unitario no puede ser mayor a 999999999999.999999.(antes, 500). Las tasas o cuotas deproduct_taxes(rate) deben ser mayores o iguales a 0 y como máximo999999999999.999999(una cuota fija puede ser mayor que 1): 400 enProductTaxes[n].RateconLa tasa o cuota debe ser mayor o igual a 0.oLa tasa o cuota no puede ser mayor a 999999999999.999999.(antes, una tasa negativa se guardaba y una mayor respondía 500).base_salary_for_contributionseintegrated_daily_salarydel empleado, yown_resource_amountdel empleador (si se envía, conNoneno se valida), deben ser mayores o iguales a 0 y como máximo999999999999.999999: 400 en el campo conSalarioBaseCotApor debe ser mayor o igual a 0.,SalarioDiarioIntegrado no puede ser mayor a 999999999999.999999.oMontoRecursoPropio no puede ser mayor a 999999999999.999999.(antes, un salario negativo se guardaba y uno mayor, igual que unown_resource_amountmayor, respondía 500). - Productos (
products.create()yproducts.update()): las claves de catálogo del producto y de sus impuestos se validan antes de guardar y responden 400 de validación con la falla en el campo y el mensaje en español (en el SDK,succeededesFalseydetailstrae la falla con el campo):SatUnitMeasurementId,La clave de unidad '<valor>' no existe en el catálogo c_ClaveUnidad del SAT.;SatTaxObjectId,El objeto de impuesto '<valor>' no existe en el catálogo c_ObjetoImp del SAT.;SatProductCodeId(si se envía, también vacío),La clave de producto o servicio '<valor>' no existe en el catálogo c_ClaveProdServ del SAT.; y en cada impuesto (n= posición, desde 0),ProductTaxes[n].TaxId,El impuesto '<valor>' no existe en el catálogo c_Impuesto del SAT.;ProductTaxes[n].TaxTypeId,El tipo factor '<valor>' no existe en el catálogo c_TipoFactor del SAT.; yProductTaxes[n].TaxFlagId,La naturaleza del impuesto '<valor>' no es válida: usa T (traslado) o R (retención).Una clave vacía (salvoSatProductCodeId) responde el mensaje de campo vacío. Antes, unSatProductCodeIdo un impuesto inválido respondía 400 sin campo, conSorry, it's not you, it's me.yThe operation cannot be completed because the record is related to another non-existent record., y la clave de unidad y el objeto de impuesto,Please use the SAT catalog values: <todas las claves del catálogo>.SatProductCodeIdennullsigue permitido. Un impuesto repetido en la lista de impuestos del producto (mismotaxId,taxTypeId,taxFlagIdyrate; la tasa se compara con 6 decimales, así que0.16y0.1600004son el mismo) responde 400 de validación antes de guardar, con la falla enProductTaxes[n](n= la segunda aparición) y el mensajeEl impuesto '<taxId>' <taxTypeId> <rate> (<taxFlagId>) se repite en el producto: cada impuesto va una sola vez.(por ejemplo,El impuesto '002' Tasa 0.16 (T) se repite en el producto: cada impuesto va una sola vez.; la tasa sale como se envió, redondeada a 6 decimales); antes llegaba a la base de datos y respondía el 400 sin falla de validaciónYa existe un registro con los mismos datos únicos.(ver la nota «Errores del servidor»). Una actualización rechazada ya no borra los impuestos guardados del producto (antes,products.update()con un impuesto inválido respondía el error y dejaba el producto sin impuestos). En este SDK,tax_id,tax_flag_idytax_type_iddeProductTaxsonstr(ver «Claves de catálogo en los modelos»): el SDK envía el valor tal cual y estas fallas las responde el API. - Personas (
people.create()ypeople.update(); tambiénPATCH /api/v4/people/{id}, que este SDK no usa): el régimen fiscal, el uso de CFDI, el país y el tipo de persona que no son del catálogo responden 400 de validación con la falla en el campo y el mensaje en español (en el SDK,succeededesFalseydetailstrae la falla con el campo):SatTaxRegimeId,El régimen fiscal '<valor>' no existe en el catálogo c_RegimenFiscal del SAT.;SatCfdiUseId,El uso de CFDI '<valor>' no existe en el catálogo c_UsoCFDI del SAT.;CountryId,El país '<valor>' no existe en el catálogo c_Pais del SAT.; yUserTypeId,UserTypeId debe ser uno de: C, U. 'T' está reservado y solo se permite cuando la persona ya es Tenant.(al actualizar,Tse sigue aceptando si la persona ya esT). Antes, al actualizar, el régimen fiscal y el uso de CFDI respondíanPlease use the SAT catalog values: <lista del catálogo>; al crear, y el país en las dos operaciones, 400 sin campo conSorry, it's not you, it's me.yThe operation cannot be completed because the record is related to another non-existent record.; y el tipo de persona al crear,UserTypeId must be one of: C, U. Los valores vacíos se siguen aceptando (se guardan sin valor). En este SDK,sat_tax_regime_id,sat_cfdi_use_id,user_type_idycountry_iddePersonsonstr(ver «Claves de catálogo en los modelos»): el SDK envía el valor tal cual (también""; para no enviar valor useNone) y estas fallas las responde el API. - Facturas (
invoices.create()): una moneda que no está en el catálogo c_Moneda (también en minúsculas, comomxn) responde 400 de validación enCurrencyCodeconLa moneda '<valor>' no existe en el catálogo c_Moneda del SAT.(en el SDK,succeededesFalseydetailstrae la falla con el campo); antes,Please use the SAT catalog values: <todas las claves de c_Moneda>. La moneda del pago y la de cada factura pagada usan el mismo texto (ver la nota de facturas de pago). - Catálogos (
catalogs.get_record_by_id(),catalogs.search_catalog()ydownload_catalogs.list_catalog()): un nombre de catálogo que no existe responde 404 conNo existe el catálogo '<nombre>'.(en el SDK,http_status_code404 y ese texto endetails); elmessageesSorry, it's not me, it's you.Antes respondía 400 conInvalid catalog name: <nombre>enmessage, o un error 500 con algunos nombres (por ejemploDatabaseoUsers). El nombre distingue mayúsculas y minúsculas (satpaymentformsno esSatPaymentForms), como antes; los nombres válidos son los que devuelvenGET /api/v4/catalogsyGET /api/v4/download-catalogs, y responden igual que antes.GET /api/v4/catalogs/<nombre>yGET /api/v4/catalogs/<nombre>/load, que este SDK no usa, responden el mismo 404 (antes, 400 y 500). - Productos (
products.create()yproducts.update()): una descripción (description) de más de 255 caracteres responde 400 de validación con la falla enDescriptiony el mensaje'Description' debe ser menor o igual que 255 caracteres. Ingresó <n> caracteres.(en el SDK,succeededesFalseydetailstrae la falla con el campo). Antes, al crear, respondía 400 sin campo conSorry, it's not you, it's me.yThe operation cannot be completed because the record is related to another non-existent record., y al actualizar, 400 conOne or more fields exceed the maximum allowed length.Con 255 caracteres o menos, sin cambio. - Claves de catálogo del timbrado (
invoices.create()) y de los datos de empleado y empleador (people.employee.create()ypeople.employee.update();people.employer.create()ypeople.employer.update()): lo que se acepta o rechaza no cambia (mismo 400 y misma falla en el campo), pero el mensaje pasa al español, con el valor y el catálogo y sin la lista de claves (en el SDK,succeededesFalseydetailstrae la falla con el campo):<El dato> '<valor>' no existe en el catálogo <c_Catálogo> del SAT.enTypeCode(c_TipoDeComprobante),ExportCode(c_Exportacion),PaymentFormCode(c_FormaPago),PaymentMethodCode(c_MetodoPago), el régimen fiscal del emisor, del receptor y a cuenta de terceros (c_RegimenFiscal), el uso de CFDI del receptor (c_UsoCFDI), la unidad (c_ClaveUnidad) y el objeto de impuesto (c_ObjetoImp) de cada concepto, el impuesto (c_Impuesto) y el tipo factor (c_TipoFactor) de los impuestos del concepto y del documento pagado, la periodicidad (c_Periodicidad) y el mes o bimestre (c_Meses) de la información global y el tipo de relación (c_TipoRelacion); enVersionCode,La versión de CFDI '<valor>' no es válida: usa 4.0.; en la naturaleza (TaxFlagCode) de los impuestos del concepto, del documento pagado y de los impuestos locales,La naturaleza del impuesto '<valor>' no es válida: usa T (traslado) o R (retención).; en el código postal de expedición, del receptor y a cuenta de terceros,El código postal '<valor>' debe tener 5 caracteres (catálogo c_CodigoPostal del SAT).(en el del receptor también… debe tener 5 dígitos (catálogo c_CodigoPostal del SAT).y, si falta,El código postal es obligatorio.); en la clave de producto o servicio del concepto y de sus partes,La clave de producto o servicio '<valor>' debe tener 8 caracteres (catálogo c_ClaveProdServ del SAT).; en la moneda de una factura de pago (CurrencyCode),La moneda de un comprobante de pago debe ser XXX.; en los catálogos de Nómina 1.2 (tipo de nómina, percepción, horas extra, otro pago, deducción e incapacidad, y en los datos de empleado y empleador tipo de contrato, régimen, periodicidad de pago, entidad federativa, riesgo de puesto, banco, tipo de jornada y origen del recurso),La clave '<valor>' no existe en el catálogo <c_Catálogo> del SAT o ya no está vigente.; y en Sindicalizado,El valor '<valor>' de Sindicalizado no es válido: usa Sí o No.Antes,Please use the SAT catalog values: <lista del catálogo>(en nómina,(<catálogo>): <lista>; a cuenta de terceros,Utilice los valores del catálogo SAT: <lista>),The value does not exist in the SAT catalog <c_X> or is no longer valid.,The TaxFlagCode must be either…,Please use the SAT 'c_CodigoPostal' catalog values.oThe currency of an invoice with the payment complement must be XXX.Cada campo responde una sola falla: en los campos obligatorios con esta regla (por ejemploVersionCode,TypeCode, los impuestos o las claves de nómina) un valor vacío o ausente responde solo el mensaje de campo vacío (antes, también el del catálogo), y unExpeditionZipCodeinválido, una falla (antes, dos iguales).POST /api/v4/invoices/calculate, que este SDK no usa, responde igual. Si su integración compara el texto del mensaje, use el campo de la falla. - Facturas (
invoices.create()): antes de cobrar el timbre y de llamar al PAC, el API arma la factura que guardará después de timbrar y revisa que cada valor quepa en ella. Un texto más largo que su máximo, o un importe o una cantidad con más dígitos enteros que los admitidos (redondeado a sus decimales; por ejemplo, la cantidad del concepto y el monto del pago admiten 12: valores menores que1000000000000), responde 400 de validación conSorry, it's not me, it's you.yOne or more validation failures have occurred., una falla por valor, sin timbrar ni consumir timbre (en el SDK,succeededesFalseydetailstrae la falla con el campo). El mensaje esAdmite como máximo <n> caracteres y se recibieron <m>.en un texto (attemptedValueennull: puede ser un dato personal) oAdmite como máximo <d> dígitos enteros (valores menores que <10^d>); se recibió <valor>.en un importe o una cantidad (attemptedValuees el número). El campo de la falla (propertyName) es la ruta del dato en la factura guardada, no la del request:InvoiceItems[0].Quantity(poritems[0].quantity),InvoicePayment.Amount(porcomplement.payment.amount) oPayroll.Deductions[0].Code(porcomplement.payroll.deductions[0].code); otras rutas son, por ejemplo,InvoicePayment.PaidInvoices[0].Number,Payroll.Earnings.Earnings[0].Concept,InvoiceRecipient.EmployeeData.Curp,InvoiceIssuer.EmployerData.EmployerRegistration,CartaPorte.Mercancias[0].ClaveUnidadyComercioExterior.Emisor.Domicilio.Colonia. Antes, un texto más largo lo rechazaba el PAC con su código (por ejemploCFDI40999,CRP20999,CP999,NOM111oCCE999), y un importe o una cantidad de 13 o más dígitos enteros se timbraba y la factura se cancelaba en segundo plano (400 conSorry, it's not you, it's me.). Las reglas propias de los campos del request (por ejemplo, el correo del receptor de hasta 256 caracteres,itemSkude hasta 100 o Sindicalizado) no cambian y responden antes, en el campo del request. - Facturas (
invoices.create()):items[n].unitOfMeasurement(este SDK no lo modela enInvoiceItem) se acepta y se ignora, como antes: el API no lo lee, el CFDI se timbra sin el atributoUnidaddel concepto y la factura guardada lo devuelve ennull. Puede omitirlo; la unidad del concepto esunit_of_measurement_code(catálogo c_ClaveUnidad). - Mensajes del timbrado en español (
invoices.create(); tambiénPOST /api/v4/invoices/calculate, que este SDK no usa): lo que se acepta o rechaza no cambia (mismo 400 de validación, mismas fallas y mismo campo;propertyNamesigue siendo la ruta en PascalCase, por ejemploItems[0].ItemTaxes[0].TaxCode), pero cambia el texto (en el SDK,succeededesFalseydetailstrae la falla con el campo). Los mensajes propios de las reglas del timbrado que seguían en inglés pasan al español (los que citan las notas anteriores ya están al día): enIssuer.TinyRecipient.Tin,'tin' no tiene el formato de RFC del SAT: 3 letras (persona moral) o 4 (persona física), la fecha AAMMDD y 3 caracteres de homoclave.(antesDoes not match the pattern defined in the type defined for expressing Mexican Federal Taxpayer Registry Keys (RFC)); enNumber,'number' (folio) debe tener de 1 a 40 caracteres sin el carácter '|'; si se omite (o va null o vacío), se genera automáticamente.; en la tasa de los impuestos del concepto y del documento pagado (TaxRate),'taxRate' (tasa o cuota) debe ser mayor o igual que 0.; en una retención exenta (TaxTypeCode),Una retención (taxFlagCode R) no puede tener 'taxTypeCode' Exento.; enRelatedInvoices,Todos los 'relatedInvoices' deben tener el mismo relationshipTypeCode.; en los impuestos locales (Complement.LocalTaxes),'taxes' requiere al menos un impuesto local.,'taxName' (nombre del impuesto local) es obligatorio; p. ej., ISN o CEDULAR.y'taxRate' debe ser mayor que 0; p. ej., 0.030000 para 3 %., y en su naturaleza (TaxFlagCode) el mismo texto que en los demás impuestos,La naturaleza del impuesto '<valor>' no es válida: usa T (traslado) o R (retención).(antesThe TaxFlagCode must be either 'T' (Traslado) or 'R' (Retención).); en los impuestos del documento pagado,'base' debe ser mayor o igual que 0 (0 toma el subTotal del documento pagado).y, contaxTypeCodeCuota,Un impuesto con taxTypeCode Cuota requiere 'base' (unidades): el subTotal del documento pagado es un importe.; en el documento pagado,'equivalence' debe ser 1 cuando la moneda del documento pagado es la del pago.y'subTotal' debe ser mayor que 0 cuando un impuesto del documento pagado tiene base 0 (base 0 toma el subTotal).; enRecipient.ForeignTin,'foreignTin' (NumRegIdTrib) debe tener de 1 a 40 caracteres.; enRecipient.Email,'email' no es un correo electrónico válido.; en la descripción del concepto,'description' debe tener de 1 a 1000 caracteres y no puede contener el carácter '|'.; en las credenciales del emisor por valores (Issuer.TaxCredentials, y en la cancelación por valores),Debes proporcionar exactamente 2 credenciales fiscales (sellos SAT): una de fileType=0 (CertificateCsd) y otra de fileType=1 (PrivateKeyCsd).(el texto que ya daba la cancelación),'fileType' '<valor>' no es un tipo de archivo válido.y'password' es obligatorio para la llave privada.; con más de 1500 conceptos, el 400 sin campo traeUna factura admite como máximo 1500 conceptos (items).; en Carta Porte,'ubicaciones' requiere al menos 2 ubicaciones.ySe permiten como máximo 2 remolques.; y en Comercio Exterior,'certificadoOrigen' debe ser 0 (no funge) o 1 (funge como certificado de origen).,'mercancias' requiere al menos una mercancía.y'domicilios' del destinatario requiere al menos un domicilio.Además, en los mensajes generales de validación del timbrado el campo se nombra como en la API (camelCase, como en el JSON) y no con el nombre de la propiedad separado en palabras:'taxCode' no debería estar vacío.(antes'Tax Code' no debería estar vacío.),'quantity' debe ser mayor que '0'.y, en la lectura del request,'code' no debe estar vacío.(antes'Code' no debe estar vacío.); vale también donde otro endpoint reutiliza esos tipos, como las credenciales de la cancelación por valores. Los demás endpoints conservan el nombre separado en palabras. Siguen en inglés los mensajes de catálogo de Carta Porte y Comercio Exterior (<Campo> must be a valid c_<Catálogo> value.), los de los archivos de certificados (por ejemploThe certificate (.cer) belongs to RFC …) y los del lector de JSON. Si su integración compara el texto del mensaje, use el campo de la falla. - Facturas (
invoices.create(); tambiénPOST /api/v4/invoices/calculate, que este SDK no usa): un total de 2,147,483,648 o más ya no responde 500 conValue was either too large or too small for an Int32.(antes del PAC, sin consumir timbre): si cabe en la factura se timbra (o se calcula) como cualquier otro y, si no, responde el 400 de los valores que no caben en la factura (ver esa nota). Además, el importe con letra que se guarda con la factura y sale en su PDF diceMILLONESde 2 a 9 millones (antesDOS MILLON …) y es correcto con mil millones o más (antes leía solo parte de los millones); el XML timbrado no lleva el importe con letra. Las facturas ya timbradas conservan el texto con el que se guardaron. - Cancelación (
invoices.cancel(), por valores y por referencias):cancellationReasonCodedebe ser una clave del catálogo c_MotivoCancelacion del SAT (01,02,03o04); otro valor responde 400 de validación con la falla enCancellationReasonCodey el mensajeEl motivo de cancelación '<valor>' no existe en el catálogo c_MotivoCancelacion del SAT.(en el SDK,succeededesFalseydetailstrae la falla con el campo), sin llamar al PAC. Antes solo se exigía que no estuviera vacío y cualquier otro valor llegaba al PAC (el de pruebas llegó a cancelar con un motivo inexistente; el SAT lo rechaza con el código 311). Un motivo vacío sigue respondiendo el mensaje de campo vacío, y01sinreplacementUuid, la falla enReplacementUuid, como antes. - Certificados (
tax_files.create()):fileType4(Pfx), que el API aceptaba sin documentarlo y que ningún flujo usa, responde 400 de validación con la falla enFileTypey el mensaje'File Type' 4 (Pfx) no se admite: el certificado (.cer) y la llave privada (.key) se cargan por separado (fileType 0 y 1 para el CSD, 2 y 3 para la FIEL).(en el SDK,succeededesFalseydetailstrae la falla con el campo); antes respondía 200 y guardaba el archivo. Los tipos admitidos son0y1(certificado y llave privada del CSD) y2y3(certificado y llave privada de la FIEL); otro valor sigue respondiendo'File Type' tiene un rango de valores que no incluye '<valor>'. - Elementos
nullen listas (facturasinvoices.create(), tambiénPOST /api/v4/invoices/calculate, que este SDK no usa; productosproducts.create()yproducts.update(); cancelacióninvoices.cancel(), por valores y por referencias): cada elemento de una lista de objetos del cuerpo debe ser un objeto. Un elementonull(por ejemplo, enitems,relatedInvoices, las percepciones de nómina,productTaxesotaxCredentials) responde 400 de validación conSorry, it's not me, it's you.yOne or more validation failures have occurred., una falla por cada elementonull(en el SDK,succeededesFalseydetailstrae las fallas con su campo), antes de las demás validaciones y sin llamar al PAC ni consumir timbre.propertyNamees la ruta del elemento con su posición (por ejemplo,RelatedInvoices[0],Items[1],Complement.Payroll.Earnings.Earnings[0],ProductTaxes[1]oTaxCredentials[0]) y el mensaje es'<campo>' no admite elementos null: cada elemento debe ser un objeto., con el campo como en la API en las facturas ('relatedInvoices' no admite elementos null: cada elemento debe ser un objeto.) y separado en palabras en productos y cancelación ('Product Taxes','Tax Credentials'). Antes, la mayoría de estos casos respondía 500 (Something went wrong on our end.) y algunos se aceptaban ignorando el elemento (en las facturas,propertyInfo,partsycustomsInfode los conceptos se timbraban sin él; en la cancelación por referencias,taxCredentialsse ignoraba);taxCredentials,paidInvoices,paidInvoiceTaxesyubicaciones, que ya respondían 400 con otro texto (la falla del par de certificados yno debe estar vacío.,no debe estar vacío.o el mínimo de 2 ubicaciones), responden ahora solo esta falla. Las listas de textos conservan su regla ymetadata(un diccionario) sigue aceptando valoresnull. Al construir un modelo, pydantic ya rechaza unNonedentro de una lista de modelos (ValidationError, sin llamar al API); si se agrega después (por ejemplo, conappend), el SDK lo envía comonully el API responde este 400. - Certificados y llaves privadas: una contraseña que no abre la llave privada (.key) responde 400 de validación con la falla en el campo de la contraseña y el mensaje
La contraseña de la llave privada (.key) es incorrecta o el archivo no es una llave privada.(en el SDK,succeededesFalseydetailstrae la falla con el campo), antes de llamar al PAC y sin consumir timbre: eninvoices.create()con el emisor por valores (taxCredentialsdel emisor), enIssuer.TaxCredentials[n].Password(n= posición de la llave, desde 0); entax_files.create()con una llave privada (fileType1o3) y enmanifests.sign(), enPassword. El mismo mensaje sale si el archivo enviado como llave no es una llave privada. Antes respondía 400 sin falla de validación, conThe .KEY's password is incorrect.La cancelación por valores (invoices.cancel()) no abre la llave: la revisa el PAC, como antes. Un certificado o una llave recibidos que no se pueden leer, en un flujo sin esa revisión (por ejemplo, un .cer ilegible enmanifests.sign()), responden 400 conNo se pudo leer el certificado (.cer) o la llave privada (.key).(en el SDK,http_status_code400 y ese texto endetails) (antes,The .KEY's password is incorrect.). Si el API no puede descifrar un dato protegido que guarda (por ejemplo, la contraseña de los certificados de una persona al timbrar o cancelar por referencias, o entax_files.get_default_values()), responde 500 conSorry, it's not you, it's me.yNo pudimos descifrar un dato protegido del servidor. Escríbenos a soporte@fiscalapi.com.(en el SDK,http_status_code500 y ese texto endetails): es un error del servidor, no de la petición; escriba a soporte. Antes respondía 400 conThe .KEY's password is incorrect. - Datos de empleador y de empleado:
people.employer.create()para una persona que ya tiene datos de empleador responde 400 de validación con la falla enPersonIdy el mensajeLa persona ya tiene datos de empleador: modifícalos con PUT /api/v4/people/{personId}/employer., ypeople.employee.create()para una persona que ya tiene datos de empleado, con la falla enEmployeePersonIdy el mensajeLa persona ya tiene datos de empleado: modifícalos con PUT /api/v4/people/{personId}/employee.(en el SDK,succeededesFalseydetailstrae la falla con el campo); modifíquelos conpeople.employer.update()opeople.employee.update(). Antes respondía 400 sin falla de validación, conSorry, it's not you, it's me.yA record with the same unique values already exists. - Decimales de importes, cantidades y tasas (
invoices.create(); tambiénPOST /api/v4/invoices/calculate, que este SDK no usa): un número del request de facturación con más decimales significativos de los que admite su atributo en el XML del SAT (los ceros finales no cuentan) responde 400 de validación conSorry, it's not me, it's you.yOne or more validation failures have occurred., una falla por valor en su campo (propertyNamecon la ruta en PascalCase, por ejemploItems[0].Discount;attemptedValuees el número recibido) y el mensaje'<campo>' admite como máximo <n> decimales (se recibieron <m>): redondéalo antes de enviarlo; FiscalAPI no lo redondea.(en el SDK,succeededesFalseydetailstrae las fallas con su campo), antes de llamar al PAC y sin consumir timbre. Por ejemplo,discount0.30000000000000004responde enItems[0].Discount'discount' admite como máximo 6 decimales (se recibieron 17): redondéalo antes de enviarlo; FiscalAPI no lo redondea.Límites: 6 enquantity,unitPriceydiscountde los conceptos (yquantityyunitPricede sus partes),taxRatede sus impuestos,exchangeRate, los importes y tasas del complemento de pagos (exchangeRate,amount,previousBalance,paymentAmount,remainingBalance,subTotal,baseytaxRate) ymarketPriceygrantPricedestockOptions; 10 enequivalence; 2 en los importes de nómina (percepciones, horas extra, otros pagos, jubilación, separación, deducciones, incapacidades, salarios del empleado yownResourceAmountdel empleador); 3 endaysPaidytimePercentage. Antes el valor llegaba al PAC, que respondía un error de esquema sin el campo (CFDI40999,CRP20999oCFDI40117), y en nóminatotalInstallmentsydailyAmountjunto contotalOneTimese ignoraban y la factura se timbraba (ahora también responden el 400). El API no redondea: el caso típico es unDecimalconstruido desde unfloat(por ejemplo0.1 + 0.2, que llega como0.30000000000000004) o el resultado de una división; redondéelo antes de asignarlo, por ejemplovalor.quantize(Decimal('0.000001'))(Decimal('0.01')en los importes de nómina). Además,daysPaidcon uno o dos decimales o con.0(por ejemplo15.5o15.0), que el PAC rechazaba conCFDI40999, se timbra: el XML lo escribe entero o con exactamente 3 decimales (15.500,15). Un importe que va tal cual con ceros de más (por ejemplo200.0000000) lo sigue rechazando el PAC conCFDI40999. Los campos de Carta Porte y Comercio Exterior conservan su propia validación de decimales. - Facturas que antes fallaban y ahora se timbran (
invoices.create(); sin cambio de contrato ni del SDK): (1) nómina: los totales del complemento (TotalPercepciones,TotalDeduccionesyTotalOtrosPagos) y el concepto que genera el API se escriben siempre con 2 decimales; antes repetían los decimales con que llegaba cada importe y, si un importe llegaba sin ceros finales, el PAC rechazaba la nómina conCFDI40168(Descuentodel concepto con más decimales que suImporte). Con este SDK pasaba con unDecimalconstruido desde unfloat, que no lleva ceros finales (3322.20se envía como3322.2). El XML y la respuesta llevan ahora, por ejemplo,5000.00en lugar de5000.0o5000(mismo valor). (2) Nómina con datos de empleador o de empleado en blanco (""o solo espacios), enviados por valores (issuer.employerData,recipient.employeeData) o guardados conpeople.employer.create()ypeople.employer.update()opeople.employee.create()ypeople.employee.update():curp,employerRegistrationyoriginEmployerTindel empleador, ycurp,socialSecurityNumber,seniority,employeeNumber,department,positionybankAccountdel empleado se omiten del XML; antes el PAC respondíaCFDI40999. Si el tipo de contrato (01a08) exige el registro patronal, ahora responde el error fiscal del PAC (NOM42). (3) Factura con descuento: un concepto cuyodiscounttiene más decimales que su importe (por ejemplounitPrice200condiscount10.55) se timbra: el XML escribe elImportecon los decimales delDescuento(200.00), hasta 6; antes el PAC respondíaCFDI40168. (4) Nómina con una percepción045(acciones o títulos) constockOptions(marketPriceygrantPrice): antes respondía 500Something went wrong on our end.(en el SDK,http_status_code500) sin llamar al PAC ni consumir timbre; ahora se timbra con el nodoAccionesOTitulos(ValorMercadoyPrecioAlOtorgarse) y la factura se guarda con él. - Errores del servidor (todos los métodos): un error inesperado del API (500) deja de traer en
detailsel texto interno del error (por ejemplo, de la base de datos o de la conversión de datos del servidor), que queda solo en los registros de Fiscalapi con el mismotraceIdentifier: respondeSomething went wrong on our end.conOcurrió un error inesperado en el servidor. Escríbenos a soporte@fiscalapi.com con el traceIdentifier de esta respuesta.(en el SDK,http_status_code500, ese texto endetailsy el identificador entrace_identifier). El status ymessageno cambian, ni los 500 con texto propio (por ejemplo, el del dato protegido que el API no puede descifrar; ver la nota de certificados y llaves privadas).messageindica además de qué lado está la causa: todo 5xx traeSorry, it's not you, it's me., salvo el 500 inesperado (Something went wrong on our end.), y todo 4xxSorry, it's not me, it's you.. Antes, los 5xx propios del API con un texto endetailstraíanSorry, it's not me, it's you.; por ejemplo, el 503 deinvoices.send()cuando el proveedor de correo no está disponible y el 502 dedownload_requests.create()cuando el SAT no pudo crear la solicitud (en el SDK,messagepasa aSorry, it's not you, it's me.;detailsy el status no cambian). Conservan su propiomessageel rechazo del PAC al timbrar o cancelar, el 425 (Too Early) de la descarga de paquetes y el 429 (Too Many Requests) del límite de solicitudes. Base de datos: si la base de datos de Fiscalapi no respondió a tiempo o no está disponible, el API responde 503 conSorry, it's not you, it's me.yLa base de datos no respondió a tiempo o no está disponible. Intenta de nuevo en unos minutos; si continúa, escríbenos a soporte@fiscalapi.com con el traceIdentifier de esta respuesta.(reintente más tarde; antes, 400 conThe server did not respond in time. Please try again later.al guardar o el 500 inesperado al consultar); si otra petición modificó o eliminó el registro entre su lectura y su escritura, 409 conSorry, it's not me, it's you.yOtra petición modificó o eliminó el registro después de leerlo. Vuelve a consultarlo e inténtalo de nuevo.(antes 400 conAn error occurred while processing your request.; el 409 de personas no cambia); y una solicitud que no cumple una restricción de los datos de la base, 400 conSorry, it's not me, it's you.(antesSorry, it's not you, it's me.), sin falla de validación y con uno de estos textos endetails:Ya existe un registro con los mismos datos únicos.,La operación no cumple una restricción de los datos: un registro relacionado no existe o depende de este, o un valor no es válido.,Falta un dato obligatorio.,Uno o más campos exceden la longitud máxima permitida.,El formato de uno o más campos es incorrecto.oUno o más valores numéricos exceden el máximo permitido.(antes en inglés, comoA record with the same unique values already exists., o en algunas operaciones el 500 inesperado) (en el SDK,http_status_code503, 409 o 400 y el texto endetails). Cualquier otra falla de la base de datos es el 500 inesperado. - Carta manifiesto (
manifests.sign()): todas las personas del tenant con el RFC del certificado quedan conmanifest_status_idenSigned; antes, si varias personas del tenant compartían ese RFC, se marcaba solo una de ellas. La petición y la respuesta no cambian; si ninguna persona del tenant tiene ese RFC, sigue respondiendo 404 conNo existe un usuario con RFC <rfc> en el tenant actual.