> For the complete documentation index, see [llms.txt](https://docs.statuspal.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.statuspal.io/next/developers/api-reference/service-groups.md).

# Service Groups

Manage service groups — named, ordered clusters of services — on a status page

## List service groups

> Service groups in display order. A group's \`order\` shares one sequence with \`order\` on services, so groups and ungrouped services can be interleaved.<br>

```json
{"openapi":"3.1.0","info":{"title":"StatusPal Public API","version":"1"},"tags":[{"name":"Service Groups","description":"Manage service groups — named, ordered clusters of services — on a status page"}],"servers":[{"url":"https://next.statuspal.io/api/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Organization API key — issue one via the Rails console (management UI coming soon)"}},"schemas":{"ServiceGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"slug":{"type":"string","readOnly":true},"name":{"type":"string"},"scope":{"type":"string","readOnly":true,"enum":["global","containers"],"description":"Which kind of service this group holds. Every member shares it: a group never mixes Global and Container Services.\nRead from the group's members while it has any. An empty group reports the kind it was created for, and a service moved into it on its own has to be of that kind. Move a whole group between scopes by writing `containers` on the group, which re-scopes every member at once.\n"},"order":{"type":["integer","null"],"description":"Display position on the status page. Groups and ungrouped services share one sequence, so a group and a service never need to be compared across two scales — sort both by `order`. `null` when the group was created without one; it then follows every positioned group and service, by name.\n"},"status":{"type":"string","enum":["ok","minor","major","critical","maintenance"],"readOnly":true,"description":"Worst-case status across the group's current members, for rendering a collapsed group. Derived per response and never stored: a group has no status of its own. `ok` when the group is empty.\n"},"services":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"Slugs of the services in this group, in display order. Empty for a group that has no services yet.\n"},"created_at":{"type":"string","format":"date-time","readOnly":true},"updated_at":{"type":"string","format":"date-time","readOnly":true}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"AccountPendingActivation":{"description":"The account is pending activation. A newly created account must be activated by an administrator before its API key can be used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/status_pages/{subdomain}/service_groups":{"get":{"summary":"List service groups","operationId":"listServiceGroups","description":"Service groups in display order. A group's `order` shares one sequence with `order` on services, so groups and ungrouped services can be interleaved.\n","tags":["Service Groups"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ServiceGroup"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/AccountPendingActivation"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## POST /status\_pages/{subdomain}/service\_groups

> Create a service group

```json
{"openapi":"3.1.0","info":{"title":"StatusPal Public API","version":"1"},"tags":[{"name":"Service Groups","description":"Manage service groups — named, ordered clusters of services — on a status page"}],"servers":[{"url":"https://next.statuspal.io/api/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Organization API key — issue one via the Rails console (management UI coming soon)"}},"schemas":{"ServiceGroupCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":255,"description":"Unique within the status page, case-insensitively."},"slug":{"type":"string","description":"Optional URL-safe identifier. If omitted, it is auto-generated from `name`. If a non-unique value is provided, a numeric suffix is appended (e.g. `it-services-1`).\n"},"services":{"type":"array","items":{"type":"string"},"description":"The services to put in this group, by slug. Optional: omit it, or send an empty array, to create an empty group, which holds Container Services when the page has containers and Global Services otherwise. An unknown slug returns 422.\nEvery listed service must share one scope: all Global or all Container-scoped. Mixing them returns 422. The group's own `scope` is read back from them. A service already in another group is moved into this one.\n"},"order":{"type":"integer","description":"Optional display position. Omitted groups sort after positioned ones.\n"}}},"ServiceGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"slug":{"type":"string","readOnly":true},"name":{"type":"string"},"scope":{"type":"string","readOnly":true,"enum":["global","containers"],"description":"Which kind of service this group holds. Every member shares it: a group never mixes Global and Container Services.\nRead from the group's members while it has any. An empty group reports the kind it was created for, and a service moved into it on its own has to be of that kind. Move a whole group between scopes by writing `containers` on the group, which re-scopes every member at once.\n"},"order":{"type":["integer","null"],"description":"Display position on the status page. Groups and ungrouped services share one sequence, so a group and a service never need to be compared across two scales — sort both by `order`. `null` when the group was created without one; it then follows every positioned group and service, by name.\n"},"status":{"type":"string","enum":["ok","minor","major","critical","maintenance"],"readOnly":true,"description":"Worst-case status across the group's current members, for rendering a collapsed group. Derived per response and never stored: a group has no status of its own. `ok` when the group is empty.\n"},"services":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"Slugs of the services in this group, in display order. Empty for a group that has no services yet.\n"},"created_at":{"type":"string","format":"date-time","readOnly":true},"updated_at":{"type":"string","format":"date-time","readOnly":true}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"AccountPendingActivation":{"description":"The account is pending activation. A newly created account must be activated by an administrator before its API key can be used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/status_pages/{subdomain}/service_groups":{"post":{"summary":"Create a service group","operationId":"createServiceGroup","tags":["Service Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceGroupCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ServiceGroup"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/AccountPendingActivation"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"}}}}}}
```

## GET /status\_pages/{subdomain}/service\_groups/{service\_group\_slug}

> Get a service group

```json
{"openapi":"3.1.0","info":{"title":"StatusPal Public API","version":"1"},"tags":[{"name":"Service Groups","description":"Manage service groups — named, ordered clusters of services — on a status page"}],"servers":[{"url":"https://next.statuspal.io/api/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Organization API key — issue one via the Rails console (management UI coming soon)"}},"schemas":{"ServiceGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"slug":{"type":"string","readOnly":true},"name":{"type":"string"},"scope":{"type":"string","readOnly":true,"enum":["global","containers"],"description":"Which kind of service this group holds. Every member shares it: a group never mixes Global and Container Services.\nRead from the group's members while it has any. An empty group reports the kind it was created for, and a service moved into it on its own has to be of that kind. Move a whole group between scopes by writing `containers` on the group, which re-scopes every member at once.\n"},"order":{"type":["integer","null"],"description":"Display position on the status page. Groups and ungrouped services share one sequence, so a group and a service never need to be compared across two scales — sort both by `order`. `null` when the group was created without one; it then follows every positioned group and service, by name.\n"},"status":{"type":"string","enum":["ok","minor","major","critical","maintenance"],"readOnly":true,"description":"Worst-case status across the group's current members, for rendering a collapsed group. Derived per response and never stored: a group has no status of its own. `ok` when the group is empty.\n"},"services":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"Slugs of the services in this group, in display order. Empty for a group that has no services yet.\n"},"created_at":{"type":"string","format":"date-time","readOnly":true},"updated_at":{"type":"string","format":"date-time","readOnly":true}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"AccountPendingActivation":{"description":"The account is pending activation. A newly created account must be activated by an administrator before its API key can be used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/status_pages/{subdomain}/service_groups/{service_group_slug}":{"get":{"summary":"Get a service group","operationId":"getServiceGroup","tags":["Service Groups"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ServiceGroup"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/AccountPendingActivation"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## Delete a service group

> Deleting a group never deletes its services — they become ungrouped and keep their statuses. This always succeeds.<br>

```json
{"openapi":"3.1.0","info":{"title":"StatusPal Public API","version":"1"},"tags":[{"name":"Service Groups","description":"Manage service groups — named, ordered clusters of services — on a status page"}],"servers":[{"url":"https://next.statuspal.io/api/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Organization API key — issue one via the Rails console (management UI coming soon)"}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"AccountPendingActivation":{"description":"The account is pending activation. A newly created account must be activated by an administrator before its API key can be used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}},"paths":{"/status_pages/{subdomain}/service_groups/{service_group_slug}":{"delete":{"summary":"Delete a service group","operationId":"deleteServiceGroup","description":"Deleting a group never deletes its services — they become ungrouped and keep their statuses. This always succeeds.\n","tags":["Service Groups"],"responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/AccountPendingActivation"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## Update a service group

> \`scope\` is never written directly: it is derived from the group's members. Write \`containers\` to move the whole group between scopes, and \`services\` to change who belongs to it.\
> A group may hold no services at all — see \`services\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"StatusPal Public API","version":"1"},"tags":[{"name":"Service Groups","description":"Manage service groups — named, ordered clusters of services — on a status page"}],"servers":[{"url":"https://next.statuspal.io/api/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Organization API key — issue one via the Rails console (management UI coming soon)"}},"schemas":{"ServiceGroupUpdate":{"type":"object","properties":{"name":{"type":"string","maxLength":255},"slug":{"type":"string"},"order":{"type":"integer"},"services":{"type":"array","items":{"type":"string"},"description":"Replaces the group's entire membership. Services omitted from the list become ungrouped.\nA group may be left with no services — an empty array empties it without deleting it. An unknown slug returns 422, as does a list mixing Global and Container-scoped services.\n"},"containers":{"type":"array","items":{"type":"string"},"description":"Re-scopes every service in the group at once, by slug, exactly as writing `containers` on each member would. Passing an empty array makes the whole group Global; passing one or more containers makes it a Container Group. An unknown slug returns 422.\nThis is how a group moves between scopes — the equivalent of dragging it from one section to the other. Member statuses are preserved across the change under the same rules as a single service: a Global-to-Container switch seeds every newly-included container with the service's current status, and the reverse keeps the worst-case of the discarded per-container statuses.\n"}}},"ServiceGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"slug":{"type":"string","readOnly":true},"name":{"type":"string"},"scope":{"type":"string","readOnly":true,"enum":["global","containers"],"description":"Which kind of service this group holds. Every member shares it: a group never mixes Global and Container Services.\nRead from the group's members while it has any. An empty group reports the kind it was created for, and a service moved into it on its own has to be of that kind. Move a whole group between scopes by writing `containers` on the group, which re-scopes every member at once.\n"},"order":{"type":["integer","null"],"description":"Display position on the status page. Groups and ungrouped services share one sequence, so a group and a service never need to be compared across two scales — sort both by `order`. `null` when the group was created without one; it then follows every positioned group and service, by name.\n"},"status":{"type":"string","enum":["ok","minor","major","critical","maintenance"],"readOnly":true,"description":"Worst-case status across the group's current members, for rendering a collapsed group. Derived per response and never stored: a group has no status of its own. `ok` when the group is empty.\n"},"services":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"Slugs of the services in this group, in display order. Empty for a group that has no services yet.\n"},"created_at":{"type":"string","format":"date-time","readOnly":true},"updated_at":{"type":"string","format":"date-time","readOnly":true}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"AccountPendingActivation":{"description":"The account is pending activation. A newly created account must be activated by an administrator before its API key can be used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/status_pages/{subdomain}/service_groups/{service_group_slug}":{"patch":{"summary":"Update a service group","operationId":"updateServiceGroup","description":"`scope` is never written directly: it is derived from the group's members. Write `containers` to move the whole group between scopes, and `services` to change who belongs to it.\nA group may hold no services at all — see `services`.\n","tags":["Service Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceGroupUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ServiceGroup"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/AccountPendingActivation"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"}}}}}}
```
