Cria um space
POST/api/spaces
- Auth
- Jeton requis
- Qui peut l’appeler:
- owner
- admin
- Clé d’API:
- community:write
- X-CommunityId
X-CommunityIdEnvoyez le X-CommunityId de la communauté où l’opération a lieu.
Sujeito a verificação de papel (RBAC): owner e admin. A comunidade é a do `X-CommunityId`.
**Extensões:** um módulo das categorias `people` ou `coworking` que o plano da comunidade não libera (`locked: true` em `GET /api/spaces/modules`) responde 403 (`errors[].param = "module"`).
**Reservas:** só um espaço `bookings` por comunidade (409 ao criar ou mudar outro para Reservas). Em `bookings` e `scheduling`, chave desconhecida na raiz de `moduleConfig` dá 400 por campo (`errors[].param = "moduleConfig.<chave>"`); só `moduleConfig.bookings` / `moduleConfig.scheduling` valem.
**"Um curso só":** para `module: courses`, `moduleConfig: { mode: "single" }` e o objeto opcional `course: { title, level, description }` criam o espaço e o curso numa só transação (o curso em rascunho). Sem `course`, o espaço nasce sem curso. Ver `moduleConfig` e `course`.
En-têtes
| Nom | Type | Description |
|---|---|---|
X-CommunityIdfacultatif | string (uuid) | ID da comunidade em que a operação acontece. Obrigatório na maioria dos endpoints com escopo de comunidade. |
Corps de la requête application/json
| Nom | Type | Description |
|---|---|---|
sectionIdobligatoire | string (uuid) | Valid section ID is required |
titleobligatoire | string | Title is required · max 255 |
descriptionfacultatif | string | Description must be a string |
slugfacultatif | string | Letras minúsculas, números e hífen. Não pode começar com _ (rotas internas do app ficam em /_/): 400 com param = slug. Gerado do título quando ausente, nunca com _. · max 255 |
iconfacultatif | string | Icon must be 100 characters or less · max 100 |
orderfacultatif | integer | Order must be a non-negative integer |
moduleobligatoire | string | Invalid module type |
externalUrlfacultatif | string (uri) | Invalid URL format |
visibilityfacultatif | public | members | plan | private | Nunca mais aberta que a da seção que contém o space, na ordem `public` < `members` < `plan` < `private`. Mais aberta devolve 400. |
accessfacultatif | object | Quem entra quando `visibility` é `private`. Opcional e **aditivo**: ausente não mexe em nada. Cada lista (`groupIds`, `productIds`, `subscriptionGroupIds`, `badgeIds`) e o nível (`minLevelId`/`minLevel`) só mudam quando vêm no pedido: **ausente (ou `null`) mantém** o que está lá, a lista enviada **substitui** a daquele tipo e `[]` tira todos. Mandar só o nível não mexe em grupos nem produtos. As listas são **união** — satisfazer qualquer uma basta. Um id de outra comunidade, ou uma lista que não é array, devolve 400 com `errors`. |
access.groupIdsfacultatif | string (uuid)[] | |
access.productIdsfacultatif | string (uuid)[] | |
access.subscriptionGroupIdsfacultatif | string (uuid)[] | |
access.badgeIdsfacultatif | string (uuid)[] | Badges da comunidade (não apagados); quem tem um deles (não revogado) entra. Como as outras listas, **ausente (ou `null`) mantém** os badges que já estão lá; `[]` tira todos. Badge de outra comunidade, apagado ou id inválido devolve 400 com `errors`. |
access.minLevelIdfacultatif | string (uuid) | "A partir do nível": quem está nesse nível do Engajamento ou acima (o nível gravado, o mesmo do perfil e do ranking) entra. Um por espaço ou seção. Ausente mantém; `null` tira. Nível de outra comunidade ou id inválido devolve 400 com `errors`. Com a gamificação desligada, o nível não abre nada. |
access.minLevelfacultatif | integer | O mesmo nível pelo número (1 é o primeiro), para a escada ainda nos níveis padrão, que não têm id antes do primeiro uso; vira `minLevelId`. Número fora da escada → 400. · ≥ 1 |
moduleConfigfacultatif | object | Configuração específica do módulo, preenchida no wizard. A forma depende do módulo — `call4papers` guarda o modo do space e a primeira chamada. Enviar `null` limpa. **Presença (`attendance`) e Encomendas (`parcels`): `retentionMonths`.** Dentro da chave do módulo (`moduleConfig.attendance.retentionMonths`, `moduleConfig.parcels.retentionMonths`), um inteiro de 6 a 60, padrão 12: por quantos meses os dados de visitantes (contados da chegada ou do dia esperado) e de encomendas fechadas (contados de quando foram recebidas e fechadas) ficam guardados antes de a rotina diária de retenção apagá-los. Fora da faixa: 400 com `errors[].param = "moduleConfig.<módulo>.retentionMonths"`. Um prazo **menor** que o em vigor só passa a valer 7 dias depois (até lá vale o anterior; a configuração lida traz `retentionMonthsPrevious` e `retentionMonthsEffectiveAt` enquanto isso), um maior vale na hora, e a troca entra no log de atividades da equipe (`space.retention_changed`, com o antes e o depois). Na edição o prazo nunca se perde: `moduleConfig: null`, sem a chave do módulo ou sem `retentionMonths` mantêm o salvo. Os dois campos de data são só do servidor (ignorados se enviados). Antes de salvar, `GET /api/spaces/{id}/retention-preview?months=N` diz quantos registros sairiam. **Espaço de Suporte (`support`): chave `support`.** `allowUrgentFlag` (boolean, padrão `true`), `maxOpenPerMember` (0 a 1000, padrão 10; 0 = sem teto), `locationHint` (texto até 200), `responders` e `sla`. `responders` diz **quem responde** os chamados: `{ tags: [nome], groupIds: [uuid], memberIds: [uuid], byCategory: { INTERNET: { tags, groupIds, memberIds } } }`. Tags valem pelo **nome** (sem diferenciar maiúsculas; renomear a tag atualiza a configuração), grupos contam só os membros diretos e as pessoas precisam ser da equipe (owner, admin, moderador ou financeiro); o resultado é cruzado com os papéis da equipe, então um membro comum nunca responde. `byCategory` sobrescreve o space para aquela categoria. Vazio = toda a equipe responde; seleção configurada que não acha ninguém = só owner e admin. `sla` guarda `{ enabled, categories: { FACILITIES: { firstResponse: { value, unit }, resolution: { value, unit } } } }` com `unit` `hours` ou `days` mais `businessHours` (`{ days: [1,2,3,4,5], start: "09:00", end: "18:00" }`, padrão seg a sex 09-18); o prazo sai do horário de atendimento no fuso da comunidade. `defaultAssigneeId` (uuid ou `null`) é quem recebe os chamados novos: precisa ser da equipe e responder ao menos uma categoria, senão 400. Campo inválido: 400, um item em `errors` por campo (`moduleConfig.support.…`). O residente nunca recebe `responders` (nem em `GET /api/spaces`). **Espaço de Cursos (`courses`): chave `mode`.** `"multi"` (lista de cursos, o padrão quando a chave não existe) ou `"single"` ("Um curso só": o espaço é um curso). Outro valor, 400 com `errors[0].param = "moduleConfig.mode"`; a chave num espaço de outro módulo, 400. Na edição, num espaço de Cursos a gravação é **por chave** (mescla com o que já existe) e `mode` só muda quando a requisição o traz: gravar outra chave nunca o apaga, e `null` limpa as outras chaves mas mantém o `mode`. Trocar para `"single"` com 0 ou 1 curso passa; com 2 ou mais, 409 dizendo quantos há (rascunhos e arquivados contam; nada é apagado). Trocar para `"multi"` sempre passa, com matrículas e progresso intactos. A conferência roda sob trava na linha do espaço, contra criação de curso em paralelo. |
canCreatefacultatif | anyone | staff | admins | Quem cria conteúdo no space (post, status, evento, curso, imagem de galeria). `anyone`: quem a rota já aceita; `staff`: owner, admin, moderator e finance; `admins`: owner e admin. Opcional: na criação o padrão é `anyone`; na edição, ausente não mexe. Comentar e reagir não dependem disso. |
feedScopefacultatif | space | section | Só no módulo `feed` (em outro módulo, 400 com `errors[0].param = "feedScope"`). O que o feed mostra em `GET /api/feed?spaceId=`: `space`, só os posts deste espaço; `section`, os itens de todos os espaços da seção. Ausente na criação, o Feed nasce `section` se é o único Feed da seção e `space` se a seção já tem outro. Coluna própria: mudar a opção não mexe no `moduleConfig`. Na edição, ausente não mexe. |
feedWidgetsfacultatif | object | Só no módulo `feed` (em outro módulo, 400 com `errors[0].param = "feedWidgets"`). Os widgets da coluna lateral do Feed (`GET /api/feed/sidebar`), cada um `true` ou `false`. Merge parcial: as chaves enviadas substituem as gravadas e as outras ficam como estão; `null` volta ao padrão. Sem nada gravado: `events` e `trending` ligados, `newMembers` e `leaderboard` desligados. Coluna própria: não mexe no `moduleConfig`. Só owner e admin. |
feedWidgets.eventsfacultatif | boolean | |
feedWidgets.trendingfacultatif | boolean | |
feedWidgets.newMembersfacultatif | boolean | |
feedWidgets.leaderboardfacultatif | boolean | |
layoutfacultatif | feed | cards | list | compact | grid | showcase | calendar | "Exibição": como o espaço lista os itens. Só os valores de `layouts.options` do módulo em `GET /api/spaces/modules` (Conteúdo `feed`, `cards`, `list`, `compact`; Eventos `cards`, `list`, `compact`, `calendar`; Cursos `cards`, `list`, `showcase` (Vitrine: grade de pôsteres); Downloads `cards`, `list`, `compact`; Classificados `cards`, `list`; Galeria `grid`, `feed`). Fora disso (`showcase` fora de Cursos, `calendar` fora de Eventos), ou em módulo sem a opção: 400 com `errors[0].param = "layout"`. Ausente ou `null`: o padrão do módulo (`layouts.default`), e a coluna fica nula. Coluna própria: não mexe no `moduleConfig`. |
lockedDisplayfacultatif | inherit | hide | show | "Para quem não tem acesso": `inherit` (igual à seção, o padrão), `hide` (some) ou `show` (aparece com cadeado). Quando a seção barrou a pessoa, `inherit` segue a seção; quando a seção deixou passar e quem barra é o space, `inherit` mostra com cadeado salvo se a seção está em `hide`. Com a seção em `section`, os spaces nunca aparecem. Área só administração nunca vira cadeado. Na edição, ausente não mexe. |
coursefacultatif | object | Só para `module: courses` com `moduleConfig.mode = "single"` ("Um curso só"): cria o espaço e o curso **juntos, numa única transação** (se o curso falhar, nada é criado). O curso nasce em **rascunho**, com o slug gerado do título. Em qualquer outro caso, 400 com `errors[0].param = "course"`. A resposta traz o curso criado em `data.course`, com `data.course.certificate` (a configuração do certificado). |
course.titleobligatoire | string | max 255 |
course.levelfacultatif | beginner | intermediate | advanced | |
course.descriptionfacultatif | string | |
course.certificatefacultatif | object | Certificado na conclusão, o mesmo bloco de `POST /api/courses`: `{ enabled, templateId?, text? }`. Ausente, o curso nasce desligado. Erros em `errors[].param = certificate.*`; se algo falhar, nem o espaço nem o curso nascem. |
Réponses
| Statut | Description |
|---|---|
201 | Space criado (e o curso, em `data.course`, quando veio `course`). |
400 | Campo inválido; `moduleConfig.mode` inválido ou fora de um espaço de Cursos; `course` fora de "Um curso só". |
401 | Token ausente, inválido ou expirado; token de outra comunidade (o `X-CommunityId` ou o `communityId` do corpo não é o do token); token antigo ou de uma sessão encerrada (troca de senha, convite aceito). Nesses casos a mensagem pede para entrar de novo (`auth.signInAgain`). |
403 | Autenticado, mas sem permissão para esta operação. |
500 | Erro interno do servidor. |
Exemple curl
curl -X POST "https://api.memberfy.net/api/spaces" \
-H "Authorization: Bearer $TOKEN" \
-H "X-CommunityId: $COMMUNITY_ID" \
-H "Content-Type: application/json" \
-d '{"sectionId":"00000000-0000-0000-0000-000000000000","title":"string","module":"string"}'