Perfil
El perfil del propio dueño.
Ningún permiso aquí alcanza el de otra persona. Enviar solo los campos que cambian deja el resto como está.
/api/ v1/ draftsLista los borradores del dueño.
/api/ v1/ draftsGuarda un borrador.
/api/ v1/ drafts/ {id}Lee un borrador del dueño.
/api/ v1/ drafts/ {id}Actualiza un borrador del dueño.
/api/ v1/ drafts/ {id}Elimina un borrador del dueño.
/api/ v1/ profileEl perfil del dueño.
/api/ v1/ profile/ imagesLas fotos que la cuenta ya usó, para poder volver a una sin buscar el archivo otra vez. Subir una nueva es trabajo del pipeline de imágenes, no de aquí.
Parámetros
| Campo | Tipo | Notas |
|---|---|---|
kindobligatorio | string | AVATAR o BANNER. |
/api/ v1/ profile/ images/ {id}Olvida una foto antigua. La que está en el perfil ahora se queda donde está.
/api/ v1/ profileCambia los campos visibles. Envía solo lo que cambia; el resto queda como está.
Cuerpo
| Campo | Tipo | Notas |
|---|---|---|
display_name | string | Hasta 60 caracteres. |
bio | string | Hasta 500 caracteres. |
pronouns | string | Hasta 30 caracteres. |
thought | string | Hasta 100 caracteres. |
youtube_username | string | Hasta 60 caracteres. |
instagram_username | string | Hasta 30 caracteres. |
twitter_username | string | Hasta 15 caracteres. |
library_visibility | string | Quién ve la biblioteca: PUBLIC, FOLLOWERS o PRIVATE. Es del perfil, no de cada juego. |
/api/ v1/ profile/ layoutEl layout de Profile Studio del dueño: draft (el borrador en edición), published (lo que ven los visitantes, o null), version (la versión que el próximo guardado debe citar), published_version, published_at, updated_at, customized y draft_incomplete (el borrador guardado tenía algo que esta versión del sitio no sabe leer y llega sin eso). Sin layout guardado, draft es el layout por defecto, version es 0 y customized es false. Un documento guardado que esta versión del sitio no puede leer aparece como el de por defecto, pero con su version real, para que el próximo guardado funcione. media resuelve cada imagen subida para el layout, por id, en url, width, height y animated: todas las del dueño, porque el borrador puede citar cualquiera.
Ejemplo
{
"data": {
"draft": { "schema": 2, "page": { ... }, "sections": [ ... ] },
"published": null,
"version": 3,
"published_version": null,
"published_at": null,
"updated_at": "2026-10-09T18:20:00.000Z",
"customized": true,
"draft_incomplete": false,
"media": {
"k3Jd8Qp2Lm": {
"url": "https://media.uloggd.com/layout/.../....avif",
"width": 1600, "height": 900, "animated": false
}
}
}
}/api/ v1/ profile/ layoutGuarda el borrador. Los visitantes siguen viendo el publicado hasta POST /api/v1/profile/layout/publish. version es la versión que cargó el editor; si otra pestaña guardó antes, la respuesta es 409 conflict con la version actual en error.version, para recargar en lugar de sobrescribir. Un documento inválido es 400 invalid_request con error.issues (hasta 20, cada uno con code, message y, cuando corresponde, path y type). Responde version y updated_at; guardar un borrador idéntico al actual no cambia la version.
Cuerpo
| Campo | Tipo | Notas |
|---|---|---|
layoutobligatorio | object | El documento: schema 2, page (width, density, background) y de 1 a 12 sections, cada una de un kind. Una sección flow tiene de 1 a 3 columns cuyos span suman 12, y cada columna hasta 12 blocks. Cada bloque tiene id, type, visibility (mobile, tablet, desktop), style y config. type es uno de estos y ningún otro: identity, shortcuts, social, stats, showcase, games, reviews, activity, lists, screenshots, image, comments, text, heading, divider, spacer. Una sección canvas es un área de proporción fija (aspect, la altura sobre el ancho, de 0.2 a 3; phone, scale o stack) con hasta 24 elements en posición libre, en el orden en que se dibujan y se leen: cada uno tiene id, type (image, text, shape o block, este solo con stats, social, heading, shortcuts o image), box (x, y, w, h en fracciones del ancho del área, sin salirse del borde; w y h desde 0.001), rotation, opacity, locked, visibility y config. background, en la página, una sección o el style de un bloque, es una imagen: media (o null, que no dibuja nada), fit, position, dim (0 a 80, cuánto se oscurece) y tone (auto, light o dark: el tema que siguen los colores del propio sitio encima de la imagen; light y dark lo fijan, y auto cuenta como dark una imagen con dim de 50 o más y deja el tema del visitante por debajo de eso; sin imagen dibujada, tone no cambia nada). No existe color de fondo ni color de texto: el texto, incluido el de un elemento text, sigue el tema; solo una shape tiene colores, fill y stroke (#rrggbb o null). Una shape line puede tener thickness (de 0.001 a 0.05 del ancho del área); sin él, strokeWidth cuenta en píxeles en un ancho de 1000px. Los textos y los bloques incrustados se componen en un ancho de referencia de 1000px (un text de size 0.05 mide 50px) y se escalan con el área; con phone scale un texto no tiene tamaño mínimo, y con phone stack los elementos siguen su posición (de arriba abajo y, en una fila, de izquierda a derecha) y un texto queda entre 12px y 40px. Una imagen es siempre una referencia, nunca una URL: { kind: media, id } (subida en /api/v1/profile/layout/media), { kind: screenshot, id } (una captura del dueño) o { kind: cover, slug } (la portada de un juego). identity es obligatorio y siempre visible. Como máximo 40 bloques, 80 elementos y 48000 bytes en UTF-8. Un documento schema 1 (solo secciones de columnas, sin background) se sigue aceptando: se convierte al 2 y se guarda así. En cualquier schema, los colores que ya no existen se descartan, no se rechazan: color en un background (un background que solo tenía color, sin media, pasa a null), textColor en el style de un bloque y color, background y radius en el config de un elemento text. Ninguna respuesta trae esas claves. |
versionobligatorio | integer | La version que cargó el editor: 0 cuando aún no hay nada guardado (también después de un DELETE). |
Ejemplo
{ "data": { "version": 4, "updated_at": "2026-10-09T18:21:02.000Z" } }/api/ v1/ profile/ layoutVuelve al layout por defecto: borra el borrador y el publicado, para el dueño y para todos. Repetirlo no es un error. Después, el próximo guardado cita version 0.
Ejemplo
{ "data": { "reset": true } }/api/ v1/ profile/ layout/ publishPublica el borrador guardado en la version indicada, que pasa a ser lo que ven los visitantes. Si el borrador cambió desde entonces, 409 conflict con la version actual en error.version; sin layout guardado, 404 not_found. Publicar no cambia la version.
Cuerpo
| Campo | Tipo | Notas |
|---|---|---|
versionobligatorio | integer | La version del borrador que muestra el editor, 1 o más. |
Ejemplo
{ "data": { "version": 4, "published_at": "2026-10-09T18:22:10.000Z" } }/api/ v1/ profile/ layout/ mediaLas imágenes que el dueño subió para el layout, de la más nueva a la más antigua: id, url, width, height, bytes, animated, created_at e in_use (el borrador o el publicado cita el id). quota trae limit (15), count, bytes_limit (10485760, 10 MB) y bytes. Solo con la sesión del sitio; una clave recibe 403.
Ejemplo
{
"data": [
{
"id": "k3Jd8Qp2Lm", "url": "https://media.uloggd.com/layout/.../....avif",
"width": 1600, "height": 900, "bytes": 184320, "animated": false,
"created_at": "2026-10-11T19:02:00.000Z", "in_use": true
}
],
"quota": { "limit": 15, "count": 1, "bytes_limit": 10485760, "bytes": 184320 }
}/api/ v1/ profile/ layout/ mediaSube una imagen para usar en el layout, como multipart/form-data con el archivo en image (JPEG, PNG, WebP, GIF o AVIF, hasta 15 MB; un GIF o WebP animado sigue animado). Se reduce para caber en 1600x1600, nunca se amplía, y se optimiza: una imagen fija pasa a un AVIF de unos 300 KB y nunca más de 1 MB, y una animación a un WebP de como máximo 3 MB y 10 segundos. Pasa por la misma verificación de contenido que un avatar, va al alojamiento de imágenes y recibe un id, que es lo que cita el documento del layout. Responde 201 con id, url, width, height, bytes, animated y created_at. Una imagen rechazada es 400 invalid_request con reason: invalid_format (no es JPEG, PNG, WebP, GIF ni AVIF, o el contenido no es del tipo enviado), too_large (más de 15 MB), invalid_image (no se pudo leer), too_many_frames (más de 300 fotogramas), too_many_pixels (más de 40 millones de píxeles, sumando los fotogramas), animation_too_long (más de 10 segundos), unsupported_animation (un PNG o AVIF animado, o una animación sin la duración de cada fotograma), too_heavy (sigue por encima de 1 MB, o de 3 MB animada, incluso después de optimizarla) o sensitive_image (la verificación de contenido la rechazó). 409 con reason quota_exceeded (y limit, count, bytes_limit, bytes) cuando la cuenta ya tiene 15 imágenes o 10 MB, o cuando esta, ya optimizada, la llevaría por encima de 10 MB; 429 por el cupo de 30 subidas cada 10 minutos o con reason busy cuando el procesamiento está lleno; 403 con reason mfa_required en una sesión que aún debe el segundo factor; 500 internal con reason upload_unavailable, screening_unavailable o upload_failed cuando el alojamiento o la verificación fallan. Solo con la sesión del sitio.
Cuerpo
| Campo | Tipo | Notas |
|---|---|---|
imageobligatorio | file | El archivo, en un campo de formulario multipart. |
Ejemplo
{
"data": {
"id": "k3Jd8Qp2Lm", "url": "https://media.uloggd.com/layout/.../....avif",
"width": 1600, "height": 900, "bytes": 184320, "animated": false,
"created_at": "2026-10-11T19:02:00.000Z"
}
}/api/ v1/ profile/ layout/ media/ {id}Borra una imagen del layout. Mientras el borrador o el publicado aún la citan, la respuesta es 409 conflict con reason in_use, salvo con force=true, que la borra igual (el layout queda con un hueco donde estaba). 404 cuando no hay una imagen tuya con ese id; 403 con reason mfa_required en una sesión que aún debe el segundo factor. Solo con la sesión del sitio.
Parámetros
| Campo | Tipo | Notas |
|---|---|---|
force | boolean | true para borrar aunque un layout todavía la cite. |
Ejemplo
{ "data": { "id": "k3Jd8Qp2Lm", "deleted": true } }El layout de Profile Studio
Un perfil se puede componer con bloques organizados en secciones y columnas,
y con áreas libres donde imágenes, textos y formas quedan donde el dueño los
pone. La composición es un documento JSON por perfil, guardado en dos copias: el
borrador (draft), que solo lee el dueño y que cada PUT reemplaza, y la
copia publicada (published), que es lo que ven los visitantes. Guardar
nunca cambia lo que ven los visitantes; POST /api/v1/profile/layout/publish
copia el borrador guardado sobre el publicado. Sin nada guardado, el borrador
es el layout por defecto, el perfil que todos ya conocen, y published es
null (los visitantes ven el de por defecto). DELETE vuelve a él para
todos. Cuando el borrador guardado tiene algo que esta versión del sitio no
sabe leer (un tipo de bloque de una versión más nueva), draft_incomplete
es true y draft llega sin esa parte: guardar encima la borra.
El type de cada bloque es uno de un conjunto cerrado, y cualquier otro se
rechaza: identity, shortcuts, social, stats, showcase, games,
reviews, activity, lists, screenshots, image, comments, text,
heading, divider, spacer. El config de un bloque guarda opciones y
referencias (el slug de un juego, el id público de una captura), nunca
marcado, script ni URL. identity, el encabezado del perfil, es obligatorio y
no se puede ocultar.
El documento
El documento actual es el schema 2. page guarda el ancho (width), el
espacio entre las secciones (density) y un fondo opcional (background), y
sections tiene de 1 a 12 secciones, cada una de un kind:
flow: de 1 a 3columnsen una cuadrícula de 12 (losspansuman 12), cada columna con hasta 12 bloques, uno debajo del otro.canvas: un área de proporción fija, conaspect(la altura sobre el ancho, de 0.2 a 3) yphone(scaleencoge el área entera en el móvil,stackapila los elementos en una columna en el orden en que se ven: de arriba abajo y, en una misma fila, de izquierda a derecha). Guarda hasta 24elementsen posición libre:boxdax,y,wyhen fracciones del ancho del área, conwyhdesde 0.001, y ningún elemento se sale del borde (el editor mantiene lo que no es una línea en 0.02 o más). Cada elemento tienerotation,opacity,locked,visibilityy untype:image,text,shapeoblock, que incrusta un bloquestats,social,heading,shortcutsoimage. El orden de la lista es el orden del dibujo (el último queda encima) y también el orden en que lo lee un lector de pantalla.
Los textos y los bloques incrustados se componen en un ancho de referencia de
1000px y después se escalan junto con el área, como una imagen: un text de
size 0.05 mide 50px en ese ancho, y una caja de w 0.4 mide 400px. Así
cortan las líneas en los mismos lugares en cualquier ancho. Con phone
scale, un texto se encoge con el área en el móvil, sin tamaño mínimo; con
stack, vuelve a ser texto al ancho de la columna, con el 60% del tamaño que
tiene en el ancho de referencia, entre 12px y 40px. El editor mantiene la
altura de la caja de un texto, de un bloque incrustado y de una línea
ajustada a lo que contiene.
Una shape line es un trazo en el medio de su caja, con un thickness
opcional (de 0.001 a 0.05 del ancho del área), que se escala con el área y
nunca queda por debajo de 1px. Una línea guardada antes de que existiera
thickness sigue usando strokeWidth, contado en píxeles en un ancho de
1000px. En un rectángulo o un círculo, strokeWidth es el borde, en píxeles.
La página, una sección y el style de un bloque pueden tener background,
que es siempre una imagen: media (la imagen, o null, que no dibuja nada),
fit, position, dim (de 0 a 80, cuánto se oscurece la imagen para que
el texto siga legible) y tone (auto, light o dark: el tema que siguen
los colores del propio sitio encima de la imagen, para que el texto siga
legible en cualquier tema). light y dark fijan ese tema; auto usa
dark cuando el dim es 50 o más y, por debajo de eso, deja el tema del
visitante. Sin imagen dibujada, tone no cambia nada. No existe color de
fondo ni color de texto: el texto, incluido el de un elemento text, sigue
siempre el tema, y solo una shape tiene colores propios (fill y
stroke, #rrggbb o null). Una imagen nunca es una URL, sino una
referencia:
{ "kind": "media", "id": "..." } para una imagen subida en Imágenes del
layout, { "kind": "screenshot", "id": "..." } para una captura del dueño y
{ "kind": "cover", "slug": "..." } para la portada de un juego. Una
referencia a la imagen de otra persona, o a una que ya no existe, no dibuja
nada.
El perfil entero tiene como máximo 40 bloques (los de las columnas y los
incrustados en las áreas libres, sumados) y 80 elementos. Un documento
schema 1, de antes de las áreas libres y los fondos, se sigue aceptando en
un PUT: se convierte al 2 (cada sección pasa a flow, sin fondo, y el
screenshot del bloque image pasa a source) y se guarda ya así, por lo
que el siguiente GET responde schema 2.
Un PUT que todavía envía los colores de antes no se rechaza: se descartan,
en cualquier schema. Son color en un background (un background que
tenía color y ninguna media pasa a null), textColor en el style de
un bloque y color, background y radius en el config de un elemento
text. Un documento guardado con ellos se lee sin ellos, así que ningún
GET los trae.
Versiones
version cuenta los cambios del borrador. GET responde la actual (0 cuando
no hay nada guardado), y cada guardado y publicación cita la versión en la que
se basó. Si el borrador cambió entretanto, en otra pestaña u otro
dispositivo, la respuesta es un conflicto que trae la versión actual, para que
el editor ofrezca recargar en lugar de sobrescribir el trabajo de alguien:
{
"error": {
"code": "conflict",
"message": "The layout changed elsewhere. Reload it before saving.",
"version": 5
}
}Guardar un borrador idéntico al guardado mantiene la misma versión. Después de
un DELETE, el próximo guardado cita la versión 0 otra vez.
Validación
El documento se revisa entero antes de guardarse: la forma, ids únicos,
columnas cuyos spans suman 12, los límites de bloques por tipo y en total, los
de elementos, elementos dentro de su área, el encabezado obligatorio y como
máximo 48000 bytes. Un rechazo lista lo que está mal, hasta 20 problemas, cada
uno con un code estable (shape, duplicate_id, spans,
too_many_blocks, too_many_of_type, too_many_elements, out_of_bounds,
required_missing, required_hidden, too_large), un message para
personas y, cuando corresponde, el path dentro del documento y el type del
bloque:
{
"error": {
"code": "invalid_request",
"message": "The layout is not valid.",
"issues": [
{
"code": "spans",
"message": "The columns of a section must add up to 12.",
"path": ["sections", 1, "columns"]
}
]
}
}Imágenes del layout
Un layout puede mostrar imágenes que el dueño subió para él, y el documento
las cita por id, nunca por URL. POST /api/v1/profile/layout/media recibe
el archivo como multipart/form-data en el campo image (JPEG, PNG, WebP,
GIF o AVIF, hasta 15 MB), lo reduce para caber en 1600x1600 y lo optimiza:
una imagen fija pasa a un AVIF de unos 300 KB, nunca más de 1 MB, y una
animación (GIF o WebP) a un WebP de hasta 3 MB y 10 segundos. Después lo
pasa por la misma verificación de contenido que un avatar (una imagen
sensible se rechaza, porque una página de perfil no tiene cómo cubrirla), lo
guarda en el alojamiento de imágenes y responde con el id, la url,
width, height, bytes y animated.
Una imagen rechazada es 400 invalid_request con un reason:
invalid_format, too_large, invalid_image, too_many_frames,
too_many_pixels, animation_too_long, unsupported_animation,
too_heavy o sensitive_image (la tabla de arriba dice qué significa cada
uno). too_heavy es una imagen que sigue siendo demasiado pesada incluso
después de optimizarla, casi siempre una animación larga o grande:
{
"error": {
"code": "invalid_request",
"message": "That image is still too heavy after optimization. Use a shorter or smaller animation.",
"reason": "too_heavy"
}
}Una cuenta guarda hasta 15 imágenes y 10 MB (10485760 bytes); pasado eso la
respuesta es 409 con reason: "quota_exceeded", limit, count,
bytes_limit y bytes. Una cuenta que ya tenía más cuando el límite bajó
conserva todo lo que tiene, pero solo vuelve a subir después de borrar lo
suficiente para quedar por debajo.
GET /api/v1/profile/layout/media lista las imágenes con in_use, que dice
si el borrador o el publicado cita el id, y la quota junto a data.
DELETE /api/v1/profile/layout/media/{id} borra una; mientras un layout
todavía la cita, la respuesta es 409 con reason: "in_use", y
?force=true la borra igual. Una imagen que ningún layout cita desde hace
siete días se elimina sola.
Estos tres endpoints son solo de la sesión del sitio: una clave recibe 403,
como en Cuenta, y la subida y el borrado responden 403 con
reason: "mfa_required" mientras la sesión aún debe el segundo factor. En las
lecturas, media resuelve cada id en url, width,
height y animated: en GET /api/v1/profile/layout, todas las imágenes
del dueño; en GET /api/v1/profiles/{username}/layout, solo las que cita el
documento publicado, para que una imagen subida para el borrador no aparezca
antes de publicarse.
Los visitantes leen el layout publicado con
GET /api/v1/profiles/{username}/layout, en Personas.