API de grupos de usuarios
Los grupos de usuarios son colecciones de miembros, delimitadas por organización, que puedes gestionar en conjunto. Cada grupo tiene un ULID asignado por el servidor, un nombre único dentro de la organización (sensible a mayúsculas y minúsculas), una descripción opcional, un conteo de miembros calculado por el servidor y un conteo de proyectos calculado por el servidor. Un grupo puede asignarse a la lista de acceso de un proyecto, otorgando a todos los miembros actuales y futuros del grupo acceso a ese proyecto en una sola operación.
/api/v1/groupsRequiere el permiso de gestión de grupos de miembros (que tienen owner/admin); member y viewer reciben
403 forbidden.
Cuerpo de la solicitud
Sección titulada «Cuerpo de la solicitud»| Campo | Requerido | Notas |
|---|---|---|
name | sí | 1–120 caracteres, sin espacios al inicio/fin. Debe ser único entre los grupos de la organización |
description | no | Hasta 500 caracteres, u omitirlo/null para no tener ninguna |
memberUserUlids | no | ULIDs de miembros activos de la organización para agregar como miembros iniciales, hasta 200 |
projectUlids | no | ULIDs de proyectos de la organización para agregar como asignaciones iniciales, hasta 200 |
Un nombre duplicado dentro de la misma organización devuelve 409 conflict. El mismo nombre se acepta
en una organización distinta.
memberUserUlids incorpora la membresía del grupo en la misma solicitud atómica que lo crea: el grupo
y sus miembros incorporados se confirman todos juntos o ninguno, nunca queda un grupo creado con
algunos miembros faltantes. Los ULIDs repetidos se deduplican silenciosamente. Cualquier entrada que
nombre a alguien que ya dejó la organización (o que nunca fue miembro) rechaza la creación completa
con 404 not_found y no crea nada, ni siquiera la fila del grupo. Cada miembro incorporado emite su
propio evento de auditoría user_group.member_added (ver Eventos de auditoría),
con la misma forma que un miembro agregado después mediante Agregar un miembro:
el feed de actividad no puede distinguir uno del otro.
projectUlids incorpora las asignaciones de proyectos iniciales del
grupo en la misma solicitud atómica, siguiendo la misma convención: los ULIDs repetidos se deduplican
silenciosamente, y una entrada que nombre un proyecto fuera de la organización activa rechaza la
creación completa con 404 not_found, sin dejar fila de grupo, fila de membresía ni fila de
asignación. Una asignación incorporada nunca se rechaza porque el modo de acceso almacenado del
proyecto destino sea "public": un grupo suele crearse antes de decidir cuáles de sus proyectos serán
privados, así que una asignación latente sobre un proyecto público es el caso común, no un error. Cada
asignación incorporada emite su propio evento de auditoría project.access_changed en el feed de
actividad del proyecto, con reason: "group_granted", con la misma forma que una asignación hecha
después mediante Asignar proyectos o los
endpoints de grupo del plano de proyecto:
quien lea el historial de acceso de un proyecto no puede distinguir qué vía la produjo. Las
asignaciones incorporadas no llevan una verificación de autorización adicional: crear un grupo ya
requiere owner/admin, y ambos roles ya pueden asignar cualquier proyecto de la organización.
Ni memberUserUlids ni projectUlids se aceptan en ningún otro endpoint. Actualizar no
acepta ninguno de los dos — una clave desconocida rechaza toda la solicitud con 422. Incorporar
miembros o proyectos después de la creación se hace a través de
Agregar un miembro y de Asignar proyectos.
Admite el almacén de idempotencia del plano de tenant: repetir
una creación con el mismo encabezado Idempotency-Key resuelve al grupo original (con sus miembros
incorporados) en lugar de crear uno duplicado.
Respuesta
Sección titulada «Respuesta»201 con el grupo creado. Ver Campos de la respuesta.
Ejemplo
Sección titulada «Ejemplo»curl -sS -X POST \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Equipo de QA","description":"A cargo del ciclo de regresión"}' \ "https://app.probara.net/api/v1/groups"Incorporando miembros y proyectos en la misma solicitud:
curl -sS -X POST \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Equipo de QA","memberUserUlids":["01JMEMBERXXXXXXXXXXXXXXXXXX"],"projectUlids":["01JPROJECTXXXXXXXXXXXXXXXXX"]}' \ "https://app.probara.net/api/v1/groups"/api/v1/groupsDevuelve los grupos de la organización, ordenados por nombre ascendente. Paginación basada en páginas. Lectura abierta para cualquier miembro de la organización, sin importar su rol.
Parámetros de consulta
Sección titulada «Parámetros de consulta»| Parámetro | Notas |
|---|---|
page | Número de página (basado en 1, por defecto 1) |
pageSize | Elementos por página (por defecto 50, máximo 200) |
q | Búsqueda de subcadena sin distinción de mayúsculas sobre el nombre y la descripción del grupo |
Respuesta
Sección titulada «Respuesta»200 con { items, page, pageSize, total }, cada elemento con la forma de la respuesta.
Ejemplo
Sección titulada «Ejemplo»curl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/groups?q=qa"Obtener
Sección titulada «Obtener»/api/v1/groups/{userGroupUlid}Devuelve un solo grupo delimitado a la organización activa. Lectura abierta para cualquier miembro de la organización.
Devuelve 404 not_found cuando el grupo está fuera de la organización activa.
Ejemplo
Sección titulada «Ejemplo»curl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX"Actualizar
Sección titulada «Actualizar»/api/v1/groups/{userGroupUlid}Actualización parcial: ambos campos son opcionales, con la misma validación que Crear.
Requiere owner o admin.
Renombrar a un nombre ya usado por otro grupo de la organización devuelve 409 conflict; el grupo
original conserva su nombre. Devuelve 404 not_found cuando el grupo está fuera de la organización
activa.
Ni memberUserUlids ni projectUlids son campos aceptados aquí, deliberadamente: una clave
desconocida rechaza toda la solicitud con 422. Los cambios de membresía posteriores a la creación se
hacen a través de Agregar un miembro y
Quitar un miembro; los cambios de asignación de proyectos se hacen a través de
Asignar proyectos y de los
endpoints de grupo del plano de proyecto.
Ejemplo
Sección titulada «Ejemplo»curl -sS -X PATCH \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"description":"A cargo del ciclo de regresión de lanzamiento"}' \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX"Eliminar
Sección titulada «Eliminar»/api/v1/groups/{userGroupUlid}Requiere owner o admin. Devuelve 204 cuando se completa; las filas de membresía del grupo
se eliminan con él (una cascada por clave foránea; no hace falta ni es posible vaciar un grupo antes por
una llamada separada). Eliminar un grupo con miembros nunca se rechaza: no existe una precondición de
vacío.
Devuelve 404 not_found cuando el grupo está fuera de la organización activa.
Ejemplo
Sección titulada «Ejemplo»curl -sS -X DELETE \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX"Listar miembros
Sección titulada «Listar miembros»/api/v1/groups/{userGroupUlid}/membersDevuelve los miembros del grupo, ordenados por fecha de ingreso ascendente. Paginación basada en
páginas (page, pageSize; los mismos valores por defecto que Listar). Lectura abierta
para cualquier miembro de la organización. Un miembro que dejó la organización queda excluido tanto de
esta lista como del userCount del grupo.
Respuesta
Sección titulada «Respuesta»200 con { items, page, pageSize, total }. Cada elemento tiene la misma forma que un miembro de la
organización (ver Organizaciones), sin el campo joinedAt.
Ejemplo
Sección titulada «Ejemplo»curl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX/members"Agregar un miembro
Sección titulada «Agregar un miembro»/api/v1/groups/{userGroupUlid}/membersRequiere owner o admin. Agrega UNO o VARIOS miembros en la misma solicitud — es una sola
operación sobre una sola ruta, no dos.
Cuerpo de la solicitud
Sección titulada «Cuerpo de la solicitud»Exactamente una de estas dos formas — un cuerpo con ambas claves, o con ninguna, responde
422 validation_failed:
| Campo | Requerido | Notas |
|---|---|---|
userUlid | una de las dos | ULID de un único miembro activo de la organización (la forma original) |
userUlids | una de las dos | Arreglo de ULIDs de miembros activos a agregar juntos, hasta 200, .min(1) |
userUlids se deduplica silenciosamente antes de resolverse. La escritura es todo o nada: un ULID
que ya dejó la organización (o que nunca fue miembro) en cualquier parte de la solicitud — incluida la
forma escalar userUlid — rechaza la solicitud completa con 404 not_found y no agrega a nadie.
Esto vale incluso para un ULID que ya es miembro: la verificación de miembro activo corre antes que la
verificación de “ya es miembro”, así que un miembro existente que ya dejó la organización sigue
respondiendo 404, nunca 409.
Una solicitud que no agregaría ninguna membresía nueva — todos los ULID listados ya son
miembros — responde 409 conflict y no agrega nada; es exactamente el comportamiento que
{ userUlid } ya tenía para un único ULID que ya era miembro, generalizado y no reemplazado. Una
selección que mezcla miembros nuevos y existentes agrega solo los nuevos y sigue respondiendo 201.
Admite el mismo encabezado Idempotency-Key que Crear.
Respuesta
Sección titulada «Respuesta»201 con el cuerpo vacío. No se reporta ningún resultado por miembro — una fila de membresía no
tiene ningún efecto secundario no transaccional que reportar.
Ejemplo
Sección titulada «Ejemplo»Agregar un miembro (sin cambios):
curl -sS -X POST \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"userUlid":"01JMEMBERXXXXXXXXXXXXXXXXXX"}' \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX/members"Agregar varios miembros en una sola solicitud:
curl -sS -X POST \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"userUlids":["01JMEMBERAXXXXXXXXXXXXXXXX","01JMEMBERBXXXXXXXXXXXXXXXX"]}' \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX/members"Quitar un miembro
Sección titulada «Quitar un miembro»/api/v1/groups/{userGroupUlid}/members/{userUlid}Requiere owner o admin. Quitar a alguien no requiere que su membresía esté activa: la
fila de un miembro que ya dejó la organización también puede quitarse. Devuelve 204 cuando se
completa; 404 not_found cuando el par no existe.
Ejemplo
Sección titulada «Ejemplo»curl -sS -X DELETE \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX/members/01JMEMBERXXXXXXXXXXXXXXXXXX"Listar proyectos asignados
Sección titulada «Listar proyectos asignados»/api/v1/groups/{userGroupUlid}/projectsDevuelve los proyectos a los que está asignado este grupo, ordenados por nombre ascendente.
Paginación basada en páginas (page, pageSize — mismos valores por defecto que
Listar). Abierto a lectura para cualquier miembro de la organización, como los otros
tres GET de grupo de arriba — pero, a diferencia de ellos, este listado filtra sus
resultados: un proyecto privado que el llamante no pueda descubrir de otra forma (ver
Acceso a proyectos) se omite silenciosamente tanto de
items como de total. Un owner/admin de la organización ve todas las asignaciones,
ya que ese rol ya puede descubrir cualquier proyecto.
Esta es deliberadamente una pregunta distinta al projectCount propio del grupo, más abajo,
que se mantiene como un conteo sin filtrar — una asignación privada que el llamante no puede ver
sigue contando para projectCount, aunque esté ausente de este listado. Un conteo no es un
nombre.
Respuesta
Sección titulada «Respuesta»200 con { items, page, pageSize, total }. Cada elemento:
| Campo | Tipo | Notas |
|---|---|---|
ulid | string (ULID) | Identificador único del proyecto, asignado por el servidor |
id | string | El código del proyecto, de cara al usuario (por ejemplo ACME) |
name | string | El nombre del proyecto |
mode | string | El modo de acceso propio y almacenado del proyecto, "public" o "private" |
avatarKey | string | null | Clave de almacenamiento del avatar del proyecto; null si no tiene |
avatarVersion | integer | Versión del avatar para invalidar caché; 0 si no tiene |
{ "items": [ { "ulid": "01J...PROJECT", "id": "ACME", "name": "Acme Corp", "mode": "private", "avatarKey": "avatars/projects/01J...PROJECT.webp", "avatarVersion": 3 } ], "page": 1, "pageSize": 50, "total": 1}Ejemplo
Sección titulada «Ejemplo»curl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX/projects"Asignar proyectos
Sección titulada «Asignar proyectos»/api/v1/groups/{userGroupUlid}/projectsAsigna el grupo a uno o varios proyectos en la misma solicitud — un lote es una sola operación sobre
una sola ruta, no N llamadas a los
endpoints de grupo del plano de proyecto.
Sin ninguna barrera de rol de organización propia. La autorización corre por proyecto, dentro
de la misma transacción, con la misma regla que ya aplica
POST /api/v1/projects/{projectId}/access/groups (owner/admin de la organización, o el propietario
actual del proyecto) — apilar encima una barrera de gestión de grupos volvería ese chequeo por proyecto
inalcanzable como rechazo para un llamante member/viewer que legítimamente es propietario de un
proyecto, y deliberadamente no se hace aquí.
Cuerpo de la solicitud
Sección titulada «Cuerpo de la solicitud»| Campo | Requerido | Notas |
|---|---|---|
projectUlids | sí | ULIDs de proyectos a asignar, hasta 200, .min(1) |
Una clave desconocida, un arreglo vacío o más de 200 ULIDs distintos responde 422 validation_failed.
La escritura es todo o nada: un ULID de proyecto que no puede resolverse, o uno para el que el
llamante no está autorizado a asignar, en cualquier parte del lote rechaza la solicitud completa y
no confirma nada — incluso un proyecto anterior en el mismo lote para el que el llamante sí estaba
autorizado. Un ULID de proyecto no resoluble responde 404 not_found; un fallo de autorización responde
403 forbidden. Los ULIDs repetidos dentro de una misma solicitud se deduplican silenciosamente a una
sola asignación. Un proyecto ya asignado dentro del lote nunca es un error (coincide con la semántica
propia de onConflictDoNothing de la lista de acceso) — la solicitud sigue respondiendo 201, nunca
409, y el resto del lote sigue confirmándose. Un proyecto público destino nunca se rechaza, igual que
las asignaciones incorporadas de Crear.
Cada asignación nueva escrita emite su propio evento de auditoría project.access_changed en el feed
de actividad del proyecto, con reason: "group_granted" — incluida una reasignación de un proyecto
ya asignado, ya que el delegado subyacente emite el evento incondicionalmente.
Admite el mismo encabezado Idempotency-Key que Crear.
Respuesta
Sección titulada «Respuesta»201 con el cuerpo vacío.
Ejemplo
Sección titulada «Ejemplo»curl -sS -X POST \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"projectUlids":["01JPROJECTAXXXXXXXXXXXXXXXX","01JPROJECTBXXXXXXXXXXXXXXXX"]}' \ "https://app.probara.net/api/v1/groups/01JXXXXXXXXXXXXXXXXXXXXXXXXX/projects"Campos de la respuesta
Sección titulada «Campos de la respuesta»Todos los endpoints de mutación (POST, PATCH) y GET /api/v1/groups/{userGroupUlid} devuelven un
solo objeto de grupo.
| Campo | Tipo | Notas |
|---|---|---|
ulid | string (ULID) | Identificador único asignado por el servidor |
name | string | 1–120 caracteres, único dentro de la organización |
description | string | null | Descripción opcional, hasta 500 caracteres |
userCount | number | Conteo de miembros activos, derivado de las filas de membresía del grupo |
projectCount | number | Conteo real y sin filtrar de proyectos asignados al grupo — ver Listar proyectos asignados para el listado de nombres filtrado por llamante |
createdAt | number | Milisegundos desde epoch Unix |
updatedAt | number | Milisegundos desde epoch Unix |
Eventos de auditoría
Sección titulada «Eventos de auditoría»Las mutaciones de grupos emiten los siguientes eventos en el feed de actividad de la organización:
| Acción | Disparador |
|---|---|
user_group.created | Grupo creado |
user_group.updated | Nombre o descripción actualizados |
user_group.deleted | Grupo eliminado |
user_group.member_added | Miembro agregado al grupo |
user_group.member_removed | Miembro quitado del grupo |
Una creación con memberUserUlids emite un evento user_group.member_added por cada miembro
incorporado, además del único evento user_group.created — nunca un solo evento que resuma un
conteo. El feed de actividad se lee igual sin importar si un miembro se incorporó al crear el grupo o
se agregó después.
Ver Eventos de auditoría.