Si subes una app Flutter a App Store Connect y recibes un correo con un aviso ITMS-91053, no es un bug de Flutter ni un capricho del revisor. Apple exige desde el 1 de mayo de 2024 que cada app declare, en un fichero llamado PrivacyInfo.xcprivacy, por qué usa ciertas APIs sensibles. Y una app Flutter usa varias de esas APIs sin que tú las escribas: las mete el motor, las meten los plugins. La respuesta corta: necesitas un privacy manifest a nivel de app, comprobar que tus plugins traen el suyo, y declarar cada Required Reason API con un código de motivo válido. Esta es la checklist concreta para hacerlo sin dar vueltas.

Qué es el privacy manifest

El privacy manifest es un fichero .plist (PrivacyInfo.xcprivacy) que acompaña a tu app y a cada framework de terceros. Declara tres cosas:

  • Qué datos recoge el código (NSPrivacyCollectedDataTypes): correo, ubicación, identificadores, etc. Es lo que alimenta las etiquetas de privacidad de la ficha.
  • Si hace tracking (NSPrivacyTracking y los dominios asociados).
  • Qué Required Reason APIs usa y con qué motivo (NSPrivacyAccessedAPITypes). Esta es la parte que dispara los rechazos automáticos.

Las "Required Reason API" son un grupo de APIs del sistema que se pueden usar para hacer fingerprinting de un dispositivo. Apple no las prohíbe: te obliga a decir para qué las usas, con un código de una lista cerrada. Si accedes a una de ellas y no la declaras, el build no pasa.

Los tres errores que vas a ver

No es un único error, son tres, y cada uno significa algo distinto:

CódigoQué significaCausa habitual
ITMS-91053Falta la declaración de una APIUsas una Required Reason API y no hay NSPrivacyAccessedAPITypes que la cubra
ITMS-91054Categoría de API inválidaEscribiste mal el valor de NSPrivacyAccessedAPICategory...
ITMS-91055Motivo inválidoEl código de motivo no existe o no es válido para esa categoría

El primero es de lejos el más común en Flutter. Los otros dos aparecen cuando alguien copia un manifest de internet sin mirar los códigos. Ojo con eso: un código copiado a ciegas te cambia el 91053 por un 91055, que es el mismo trabajo otra vez.

Estos correos son avisos, no rechazos en la primera versión que los introdujo. Pero Apple ya rechaza builds por ellos, así que trátalos como bloqueantes.

Las cinco categorías de Required Reason API

Hay cinco categorías. Estas son, con el código de motivo más habitual para una app normal:

  • File timestamp (NSPrivacyAccessedAPICategoryFileTimestamp), fechas de creación o modificación de ficheros. Motivo frecuente C617.1 cuando muestras esos timestamps al usuario, o DDA9.1 para ficheros dentro del contenedor de la app.
  • System boot time (NSPrivacyAccessedAPICategorySystemBootTime), tiempo desde el arranque. Motivo 35F9.1 para medir tiempo transcurrido dentro de la app.
  • Disk space (NSPrivacyAccessedAPICategoryDiskSpace), espacio libre. Motivo E174.1 para comprobar que hay hueco antes de escribir.
  • Active keyboards (NSPrivacyAccessedAPICategoryActiveKeyboards), teclados activos. Solo aplica a casos concretos, como una extensión de teclado.
  • User defaults (NSPrivacyAccessedAPICategoryUserDefaults), lectura y escritura en NSUserDefaults. Motivo CA92.1 cuando accedes a datos escritos por tu propia app.

Este último, User defaults, es el que pilla a casi cualquier app Flutter: shared_preferences guarda en NSUserDefaults, y shared_preferences está en medio proyecto. No copies estos códigos sin más. Ve a la tabla oficial de Apple, mira qué motivo describe de verdad tu uso, y pon ese. Poner el motivo equivocado es tan bloqueante como no ponerlo.

Qué te resuelve Flutter y qué te toca a ti

Aquí está el malentendido que hace perder una tarde. Flutter no lo arregla todo solo, pero tampoco te deja tirado.

Lo que ya viene hecho:

  • El motor de Flutter trae su propio PrivacyInfo.xcprivacy desde Flutter 3.19. Cubre las APIs que usa el propio engine.
  • Los plugins oficiales (los del equipo de Flutter y de Google: shared_preferences, path_provider, image_picker, sqflite…) incorporaron su manifest a lo largo de 2024. Si estás en versiones recientes, los traen.

Lo que sigue siendo tuyo:

  • El manifest a nivel de app. Si tu propio código Dart o Swift toca una Required Reason API, hace falta un PrivacyInfo.xcprivacy en el target de la app. Y la parte de recogida de datos y tracking siempre es responsabilidad tuya: eso no lo declara ningún plugin por ti.
  • Los plugins de terceros que no lo traen. Un SDK de analítica, de push, de pagos o de mapas que lleve tiempo sin actualizarse puede no incluir manifest. Ese hueco lo pagas tú en el rechazo, no su autor.

La regla mental: Flutter cubre lo que Flutter escribe; tú cubres lo que escribes tú y lo que arrastras de dependencias viejas.

La checklist para dejarlo cerrado

  1. Actualiza Flutter y los plugins primero. Muchos ITMS-91053 se van solos con un flutter pub upgrade. Es el paso más barato y el que más rechazos quita. Hazlo antes de tocar nada a mano.
  2. Crea el manifest de la app en Xcode. New File → App Privacy File, y lo añades al target Runner. Ahí declaras la recogida de datos (NSPrivacyCollectedDataTypes), el tracking y las Required Reason APIs que use tu código propio.
  3. Localiza qué API te está avisando. El correo de App Store Connect nombra la categoría. Para encontrar de dónde sale, busca en el proyecto los símbolos típicos: NSUserDefaults / UserDefaults, systemUptime, .creationDate / .modificationDate, volumeAvailableCapacity. Un grep en ios/ y en los pods suele señalar al culpable.
  4. Declara cada API con su motivo real. Copia el código de motivo desde la tabla de Apple, no de un Stack Overflow de 2024. Verifica que el motivo describe tu uso; si no encaja ninguno, probablemente estás usando la API para algo que Apple no permite y hay que replantear.
  5. Audita los plugins de terceros. Abre los pods y comprueba si cada framework trae su PrivacyInfo.xcprivacy. El que no lo tenga: actualízalo, sustitúyelo, o si es imprescindible y está abandonado, asume que tendrás que envolverlo o forkarlo.
  6. Súbelo a TestFlight antes que a revisión. Los avisos de manifest llegan en el procesado del build, sin esperar al revisor humano. Un build a TestFlight te dice en minutos si te falta algo, y te ahorra el ciclo de un día de la cola de review.

No lo dejes fuera del pipeline

Lo que pasa en la práctica: alguien arregla el manifest a mano una vez, sube la versión, y seis meses después un plugin nuevo vuelve a meter una API sin declarar. Vuelta a empezar. Si tienes CI/CD, el sitio de esta comprobación es ahí, en el mismo pipeline que ya construye y firma el .ipa, no en la memoria de quien hizo la release la última vez.

No es una tarea de "una vez y ya". Cada dependencia nueva es una fuente potencial de un ITMS-91053, y el coste de detectarlo en CI es minutos frente al coste de detectarlo cuando el revisor te devuelve el build a tres días de una fecha de lanzamiento.

Dónde no te compliques

Si tu app no recoge datos, no hace tracking y usas solo plugins oficiales al día, es posible que no necesites un manifest a nivel de app: el del motor y los de los plugins pueden bastar. No añadas declaraciones "por si acaso"; declarar una recogida de datos que no haces también es un problema, porque las etiquetas de privacidad de la ficha salen de ahí y estarías mintiendo a la baja o al alza. Declara lo que haces, ni más ni menos.

Y si el aviso viene de un SDK propietario que no controlas, la conversación no es técnica sino de proveedor: o publican su manifest o cambias de proveedor. No hay truco de configuración que declare por ellos lo que hacen dentro de su binario.


El privacy manifest es más tedioso que difícil, y fácil de olvidar. La diferencia entre una release limpia y un rechazo a última hora es tenerlo en la checklist de publicación y en el pipeline, no en la cabeza de quien hizo la última entrega. Si estáis montando o auditando el proceso de release de una app Flutter y queréis que esto deje de morder en cada versión, es exactamente el tipo de cosa que resolvemos con equipo senior metido en vuestro proyecto. Hablamos.

Relacionado: requisitos para subir una app a la App Store, rechazos de App Store y Google Play con el texto exacto y su solución y CI/CD para apps Flutter con GitHub Actions.