{"openapi":"3.0.0","paths":{"/healthcheck":{"get":{"description":"Verifies that the API can reach its Redis and Postgres dependencies. Used by infrastructure liveness/readiness probes.","operationId":"HealthcheckController_healthcheck","parameters":[],"responses":{"200":{"description":"All dependencies are reachable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthcheckResponseDto"}}}},"500":{"description":"One or more dependencies are unreachable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"}}}}},"summary":"Service health check","tags":["System"]}},"/api/users":{"get":{"description":"Returns a paginated list of users that belong to the organization the API key is scoped to, ordered by email. Optionally filters by a case-insensitive email substring match. Requires the `workspace:read` scope.","operationId":"UsersController_list","parameters":[{"name":"email","required":false,"in":"query","description":"Case-insensitive substrings to match against user emails (ILIKE). Repeat the parameter to filter by multiple emails; users matching any of them are returned.","schema":{"example":["jane"],"type":"array","items":{"type":"string"}}},{"name":"page","required":false,"in":"query","description":"1-based page number. Defaults to 1.","schema":{"example":1,"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (1-100). Defaults to 20.","schema":{"example":20,"type":"number"}}],"responses":{"200":{"description":"A page of users in the organization.","headers":{"X-RateLimit-Limit":{"description":"Maximum number of requests allowed per minute.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the current window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["results","pagination"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationUserDto"}},"pagination":{"$ref":"#/components/schemas/PaginationMetaDto"}}}}}},"400":{"description":"Invalid filter/pagination parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INVALID_REQUEST","message":"'page' must be a positive integer."}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"UNAUTHORIZED","message":"A valid API key is required."}}}},"403":{"description":"The API key lacks the required scope, or the organization plan does not include public API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INSUFFICIENT_SCOPE","message":"This key does not have the 'workspace:read' scope."}}}},"429":{"description":"Rate limit exceeded for the organization. See the X-RateLimit-* and Retry-After response headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Limit: 60 req/min. Resets at 2026-06-12T12:00:00.000Z."}}}},"500":{"description":"An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INTERNAL_ERROR","message":"An unexpected error occurred"}}}}},"security":[{"bearer":[]}],"summary":"List organization users","tags":["Users"]}},"/api/teams":{"get":{"description":"Returns a paginated list of teams that belong to the organization the API key is scoped to, ordered by name. Optionally filters by a case-insensitive team name substring match or by archived flag. Requires the `workspace:read` scope.","operationId":"TeamsController_list","parameters":[{"name":"isArchived","required":false,"in":"query","description":"Flag indicating whether to include archived teams in the results.","schema":{"type":"boolean"}},{"name":"teamName","required":false,"in":"query","description":"Case-insensitive substrings to match against team names (ILIKE). Repeat the parameter to filter by multiple names; teams matching any of them are returned.","schema":{"example":["design"],"type":"array","items":{"type":"string"}}},{"name":"page","required":false,"in":"query","description":"1-based page number. Defaults to 1.","schema":{"example":1,"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (1-100). Defaults to 20.","schema":{"example":20,"type":"number"}}],"responses":{"200":{"description":"A page of teams in the organization.","headers":{"X-RateLimit-Limit":{"description":"Maximum number of requests allowed per minute.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the current window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["results","pagination"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/TeamDto"}},"pagination":{"$ref":"#/components/schemas/PaginationMetaDto"}}}}}},"400":{"description":"Invalid filter/pagination parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INVALID_REQUEST","message":"'page' must be a positive integer."}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"UNAUTHORIZED","message":"A valid API key is required."}}}},"403":{"description":"The API key lacks the required scope, or the organization plan does not include public API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INSUFFICIENT_SCOPE","message":"This key does not have the 'workspace:read' scope."}}}},"429":{"description":"Rate limit exceeded for the organization. See the X-RateLimit-* and Retry-After response headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Limit: 60 req/min. Resets at 2026-06-12T12:00:00.000Z."}}}},"500":{"description":"An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INTERNAL_ERROR","message":"An unexpected error occurred"}}}}},"security":[{"bearer":[]}],"summary":"List organization teams","tags":["Teams"]}},"/api/teams/{teamId}/assets":{"get":{"description":"Returns a paginated list of assets stored in a team’s asset library, filtered by asset type. The response spans every sub-folder under the team’s library root. Requires the `workspace:read` scope.","operationId":"TeamAssetsController_list","parameters":[{"name":"teamId","required":true,"in":"path","description":"UUID of the team whose asset library to list.","schema":{"example":"a1b2c3d4-0000-0000-0000-000000000000","type":"string"}},{"name":"type","required":true,"in":"query","description":"Asset type to list. One of `image`, `mesh`, `video`, `prompt`, `color`, `palette`.","schema":{"enum":["image","mesh","video","prompt","color","palette"],"type":"string"}},{"name":"page","required":false,"in":"query","description":"1-based page number. Defaults to 1.","schema":{"example":1,"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (1-100). Defaults to 20.","schema":{"example":20,"type":"number"}}],"responses":{"200":{"description":"A page of assets in the team library.","headers":{"X-RateLimit-Limit":{"description":"Maximum number of requests allowed per minute.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the current window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["results","pagination"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/TeamAssetDto"}},"pagination":{"$ref":"#/components/schemas/PaginationMetaDto"}}}}}},"400":{"description":"Invalid `type`, `teamId`, or pagination parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INVALID_REQUEST","message":"'type' must be one of: image, mesh, video, prompt, color, palette."}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"UNAUTHORIZED","message":"A valid API key is required."}}}},"403":{"description":"The API key lacks the required scope, or the organization plan does not include public API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INSUFFICIENT_SCOPE","message":"This key does not have the 'workspace:read' scope."}}}},"404":{"description":"The team does not exist or is not part of your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"NOT_FOUND","message":"Team 'a1b2c3d4-0000-0000-0000-000000000000' not found."}}}},"429":{"description":"Rate limit exceeded for the organization. See the X-RateLimit-* and Retry-After response headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Limit: 60 req/min. Resets at 2026-06-12T12:00:00.000Z."}}}},"500":{"description":"An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INTERNAL_ERROR","message":"An unexpected error occurred"}}}}},"security":[{"bearer":[]}],"summary":"List team library assets","tags":["Teams"]}},"/api/organizations/{organizationId}/logs":{"get":{"description":"Returns a cursor-paginated list of audit log entries for the organization, newest first. `type=activity` lists member actions (logins, workbench/drawing changes, exports, member management, …); `type=access` lists access events with IP address and device information. Pass `pagination.nextCursor` from a response as the `cursor` parameter to fetch the next page. Requires the `workspace:read` scope and a plan that includes audit logs.","operationId":"OrganizationLogsController_list","parameters":[{"name":"organizationId","required":true,"in":"path","description":"UUID of the organization whose logs to list. Must be the organization the API key belongs to.","schema":{"example":"a1b2c3d4-0000-0000-0000-000000000000","type":"string"}},{"name":"type","required":true,"in":"query","description":"Log type to list. One of `activity`, `access`.","schema":{"enum":["activity","access"],"type":"string"}},{"name":"email","required":false,"in":"query","description":"Case-insensitive substring to match against the user email of each entry.","schema":{"example":"jane","type":"string"}},{"name":"dateFrom","required":false,"in":"query","description":"Only include entries at or after this ISO 8601 timestamp.","schema":{"example":"2026-01-01T00:00:00Z","type":"string"}},{"name":"dateTo","required":false,"in":"query","description":"Only include entries at or before this ISO 8601 timestamp.","schema":{"example":"2026-02-01T00:00:00Z","type":"string"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (1-100). Defaults to 20.","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Opaque cursor from a previous response (`pagination.nextCursor`). Omit to fetch the first page. Filters must stay the same across pages of one listing.","schema":{"type":"string"}},{"name":"actionType","required":false,"in":"query","description":"Exact action types to filter by, e.g. `Login succeeded`, `Created workspace`. Repeat the parameter to filter by multiple types. Only valid with `type=activity`.","schema":{"example":["Login succeeded"],"type":"array","items":{"type":"string"}}}],"responses":{"200":{"description":"A page of audit log entries, newest first.","headers":{"X-RateLimit-Limit":{"description":"Maximum number of requests allowed per minute.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the current window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["results","pagination"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationLogDto"}},"pagination":{"$ref":"#/components/schemas/CursorPaginationMetaDto"}}}}}},"400":{"description":"Invalid `type`, filter, cursor, or pagination parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INVALID_REQUEST","message":"'type' must be one of: activity, access."}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"UNAUTHORIZED","message":"A valid API key is required."}}}},"403":{"description":"The API key lacks the required scope, or the organization plan does not include public API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INSUFFICIENT_SCOPE","message":"This key does not have the 'workspace:read' scope."}}}},"404":{"description":"The organization does not match the API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"NOT_FOUND","message":"Organization 'a1b2c3d4-0000-0000-0000-000000000000' not found."}}}},"429":{"description":"Rate limit exceeded for the organization. See the X-RateLimit-* and Retry-After response headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Limit: 60 req/min. Resets at 2026-06-12T12:00:00.000Z."}}}},"500":{"description":"An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponseDto"},"example":{"code":"INTERNAL_ERROR","message":"An unexpected error occurred"}}}}},"security":[{"bearer":[]}],"summary":"List organization audit logs","tags":["Logs"]}}},"info":{"title":"Vizcom API","description":"Public API for the Vizcom platform. Authenticate with your API key as a bearer token. Requests are rate-limited per organization and scoped to the permissions granted to the key.","version":"1.0","contact":{}},"tags":[{"name":"System","description":"Service metadata and health checks."},{"name":"Users","description":"Read users that belong to your organization."},{"name":"Teams","description":"Read teams that belong to your organization."},{"name":"Logs","description":"Read activity and access logs for your organization."}],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API key","type":"http","description":"Provide your Vizcom API key as a bearer token."}},"schemas":{"HealthcheckResponseDto":{"type":"object","properties":{"status":{"type":"string","description":"Overall service status. Always \"ok\" when the API is healthy.","example":"ok"}},"required":["status"]},"ApiErrorDetailDto":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable error code.","example":"INSUFFICIENT_SCOPE"},"message":{"type":"string","description":"Human-readable description of what went wrong.","example":"This key does not have the 'workspace:read' scope."}},"required":["code","message"]},"ApiErrorResponseDto":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetailDto"}},"required":["error"]},"PaginationMetaDto":{"type":"object","properties":{"totalPages":{"type":"number","description":"Total number of pages available for the current page size.","example":5},"currentPage":{"type":"number","description":"The 1-based page number of this response.","example":1},"totalCount":{"type":"number","description":"Total number of items matching the query across all pages.","example":92},"pageSize":{"type":"number","description":"Maximum number of items returned per page.","example":20}},"required":["totalPages","currentPage","totalCount","pageSize"]},"OrganizationUserDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the user.","example":"a1b2c3d4-0000-0000-0000-000000000000"},"email":{"type":"string","description":"Email address of the user.","example":"jane@example.com"}},"required":["id","email"]},"TeamDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the team.","example":"a1b2c3d4-0000-0000-0000-000000000000"},"teamName":{"type":"string","description":"Name of the team.","example":"Design Team"}},"required":["id","teamName"]},"PaletteColorEntry":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name of the color swatch.","example":"Vizcom Blue"},"color":{"type":"string","description":"Hex color value including the leading `#` (6 hex digits).","example":"#3366FF","pattern":"^#[0-9A-Fa-f]{6}$"}},"required":["name","color"]},"TeamAssetDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the asset.","example":"a1b2c3d4-0000-0000-0000-000000000000"},"type":{"type":"string","description":"Asset type. Determines which optional fields are populated.","enum":["image","mesh","video","prompt","color","palette"],"example":"image"},"name":{"type":"string","description":"Name of the asset.","example":"Front bumper reference"},"createdBy":{"type":"object","description":"Email address of the user who created the asset, or null for system-created assets.","nullable":true,"example":"jane@example.com"},"createdAt":{"type":"string","description":"ISO 8601 timestamp of asset creation.","example":"2026-01-15T12:34:56.000Z"},"updatedAt":{"type":"string","description":"ISO 8601 timestamp of the last update.","example":"2026-01-15T12:34:56.000Z"},"description":{"type":"object","description":"Free-text description. Populated for image, mesh, and video assets.","nullable":true},"imagePath":{"type":"object","description":"Time-limited download URL of the image file. Populated for image assets.","nullable":true},"meshPath":{"type":"object","description":"Time-limited download URL of the mesh file (GLB format, downloads with a `.glb` filename). Populated for mesh assets.","nullable":true},"videoPath":{"type":"object","description":"Time-limited download URL of the video file. Populated for video assets.","nullable":true},"thumbnailPath":{"type":"object","description":"Time-limited download URL of the thumbnail image. Populated for mesh, video, and prompt assets.","nullable":true},"promptText":{"type":"string","description":"Prompt body text. Populated for prompt assets."},"red":{"type":"number","description":"Red channel (0-255). Populated for color assets.","example":255},"green":{"type":"number","description":"Green channel (0-255). Populated for color assets.","example":128},"blue":{"type":"number","description":"Blue channel (0-255). Populated for color assets.","example":64},"colors":{"description":"Ordered list of named hex color swatches. Populated for palette assets.","type":"array","items":{"$ref":"#/components/schemas/PaletteColorEntry"}}},"required":["id","type","name","createdAt","updatedAt"]},"CursorPaginationMetaDto":{"type":"object","properties":{"pageSize":{"type":"number","description":"Maximum number of items returned per page.","example":20},"nextCursor":{"type":"object","description":"Opaque cursor to pass as the `cursor` query parameter to fetch the next page, or null when there are no further results.","nullable":true,"example":"eyJldmVudERhdGUiOiIyMDI2LTAxLTE1VDEyOjM0OjU2LjEyMzQ1NloiLCJpZCI6IjEyMzQ1In0"}},"required":["pageSize","nextCursor"]},"OrganizationLogDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the log entry.","example":"12345"},"type":{"type":"string","description":"Log type. Determines which optional fields are populated.","enum":["activity","access"],"example":"activity"},"eventDate":{"type":"string","description":"ISO 8601 timestamp of the event (UTC, microsecond precision).","example":"2026-01-15T12:34:56.123456Z"},"userEmail":{"type":"string","description":"Email address of the user who triggered the event.","example":"jane@example.com"},"actionType":{"type":"string","description":"Action performed, e.g. `Login succeeded`, `Created workspace`. Populated for activity logs.","example":"Created workspace"},"actingAdminEmail":{"type":"object","description":"Email of the Vizcom staff member who performed the action on behalf of the user, or null when the user acted themselves. Populated for activity logs.","nullable":true,"example":null},"targetType":{"type":"string","description":"Type of the entity the action targeted, e.g. `Team`, `Drawing`. Populated for activity logs.","example":"Team"},"targetId":{"type":"string","description":"Identifier of the entity the action targeted. Populated for activity logs.","example":"a1b2c3d4-0000-0000-0000-000000000000"},"metadata":{"type":"object","description":"Additional action details (names, roles, workspaces, …). Shape varies by action type. Populated for activity logs.","nullable":true,"additionalProperties":true},"ipAddress":{"type":"object","description":"IP address the request originated from. Populated for access and activity logs.","nullable":true,"example":"203.0.113.42"},"deviceInfo":{"type":"object","description":"Device and browser information captured at login. Populated for access logs.","nullable":true,"additionalProperties":true}},"required":["id","type","eventDate","userEmail"]}}}}