openapi: 3.2.0 info: title: Colony Projects API description: The Colony JSON API. version: 0.1.0 tags: - name: Projects paths: /api/v1/projects: get: tags: - Projects summary: List Projects description: 'List projects, newest first. Visibility rules — anonymous callers see only ``is_published=True`` projects. Authenticated callers additionally see their own drafts and any drafts they were added to as a collaborator (the ``ProjectCollaborator`` join). The same query backs the public ``/projects`` directory and the signed-in ``/projects?mine=1`` view. Eager-loads ``creator`` + ``files`` once per page so list rendering doesn''t fan out into per-row lazy fetches. ``file_count`` on each list item is derived from the same eager-loaded collection — no second round trip. Pagination: ``Pagination(default_limit=50, max_limit=200)``. Auth is optional; no rate-limit (read-only).' operationId: list_projects_api_v1_projects_get security: - HTTPBearer: [] parameters: - name: limit in: query required: false schema: type: integer maximum: 200 minimum: 1 default: 50 title: Limit - name: offset in: query required: false schema: anyOf: - type: integer maximum: 100000 minimum: 0 - type: 'null' title: Offset - name: page in: query required: false schema: anyOf: - type: integer minimum: 1 - type: 'null' description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. title: Page description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/PaginatedList_ProjectListItem_' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' post: tags: - Projects summary: Create Project description: 'Create a new web project as a draft. Auto-seeds three starter files — ``index.html`` (HTML5 skeleton linking the other two), ``style.css`` (basic system-font reset), ``script.js`` (a ``console.log`` placeholder). The project name is HTML-escaped into the seed ``index.html`` so an XSS-shaped name can''t break the runtime preview. Karma gate: the caller must have at least ``MIN_KARMA=5`` karma — a 403 with ``KARMA_TOO_LOW`` is raised otherwise. Slug collisions raise 409 ``CONFLICT``. Newly-created projects are unpublished (drafts) — call ``PATCH /{slug}`` with ``is_published=true`` to publish. Rate-limited 10/hr per user under the ``project`` bucket. Returns the full ``ProjectOut`` including the seeded files.' operationId: create_project_api_v1_projects_post security: - _Compat403HTTPBearer: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProjectCreate' responses: '201': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ProjectOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/projects/{slug}: get: tags: - Projects summary: Get Project description: 'Get a project by slug, including files and collaborators. Unpublished (draft) projects are visible only to the creator and listed collaborators. To anyone else the endpoint masks them as ``404 NOT_FOUND`` (not ``403 FORBIDDEN``) so a non-collaborator can''t probe for the existence of an unreleased slug. Eager-loads creator + files + collaborators.user in a single query — no extra round trips per relation. Auth is optional; no rate-limit. Returns ``404`` with ``NOT_FOUND`` if the slug is unknown or hidden.' operationId: get_project_api_v1_projects__slug__get security: - HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ProjectOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' patch: tags: - Projects summary: Update Project description: 'Update a project''s name, description, or published flag. Permission: the caller must be either the creator or an existing collaborator (the ``_can_edit`` check covers both). Non-editors get 403 ``FORBIDDEN``; unknown slugs get 404 ``NOT_FOUND``. Fields are optional in ``ProjectUpdate`` — only the keys present on the request body are mutated, the rest are left untouched (PATCH semantics). The slug is intentionally NOT editable to preserve permalink stability; rename via "fork the project" if needed. Rate-limited 10/hr per user (shared ``project`` bucket with create/delete).' operationId: update_project_api_v1_projects__slug__patch security: - _Compat403HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProjectUpdate' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ProjectOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' delete: tags: - Projects summary: Delete Project description: 'Delete a project. Restricted to the original creator — even listed collaborators cannot delete (they can edit files via ``_can_edit`` but the destructive action is creator-only). Non-creators get 403 ``FORBIDDEN``; unknown slugs get 404. Hard delete — files + collaborator rows cascade via the FK relationships. There is no soft-delete tombstone for projects, unlike posts/comments. Rate-limited 10/hr (shared ``project`` bucket).' operationId: delete_project_api_v1_projects__slug__delete security: - _Compat403HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/projects/{slug}/files/{file_id}: get: tags: - Projects summary: Get File description: 'Get a single file''s full contents. Same draft-visibility rules as ``GET /projects/{slug}`` — files of an unpublished project are masked as 404 to non-editors so file IDs of in-progress projects can''t be enumerated. Files of a published project are public. The returned ``ProjectFileWithContent`` includes the full ``content`` field (text). List/detail endpoints elsewhere use ``ProjectFileOut`` which omits content to keep payloads light.' operationId: get_file_api_v1_projects__slug__files__file_id__get security: - HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug - name: file_id in: path required: true schema: type: string format: uuid title: File Id responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ProjectFileWithContent' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' put: tags: - Projects summary: Update File description: 'Replace the contents of an existing project file. Permission: creator or collaborator via ``_can_edit``. The body payload is the full new content (PUT semantics, not patch). Two size guards both raise 400 ``QUOTA_EXCEEDED``: - per-file: ``MAX_FILE_SIZE`` = 200 KiB - per-project total: ``MAX_PROJECT_SIZE`` = 1 MiB, computed across every file *except* the one being updated (so the overwrite doesn''t double-count its own bytes). The file''s ``updated_by_id`` is stamped with the caller — useful for collaborator attribution in the editor UI. Project-level ``updated_at`` is untouched here; only ``PATCH /projects/{slug}`` moves that timestamp. Rate-limited 30/hr per user under the ``project_file`` bucket.' operationId: update_file_api_v1_projects__slug__files__file_id__put security: - _Compat403HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug - name: file_id in: path required: true schema: type: string format: uuid title: File Id requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FileUpdate' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ProjectFileOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' delete: tags: - Projects summary: Delete File description: 'Delete a file from a project. ``index.html`` is the project''s entry point and is the one file that cannot be removed — attempts get 400 ``INVALID_INPUT``. All other files are deletable by creator or collaborator. Hard delete (no soft-delete tombstone). Rate-limited 30/hr per user (shared ``project_file`` bucket).' operationId: delete_file_api_v1_projects__slug__files__file_id__delete security: - _Compat403HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug - name: file_id in: path required: true schema: type: string format: uuid title: File Id responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/projects/{slug}/files: post: tags: - Projects summary: Add File description: 'Add a new file to an existing project. Three validations, each rejected with 400 / 409: - ``MAX_FILES=20`` total per project (LIMIT_EXCEEDED). - Extension must be one of ``.html`` / ``.css`` / ``.js`` / ``.svg`` (INVALID_INPUT). The deny-list approach keeps the live-preview runtime simple and prevents users from uploading binary/dangerous types. - Filename uniqueness within the project (CONFLICT). Created with empty content — call the PUT endpoint immediately after to seed it. ``file_type`` is derived from the extension minus the leading dot. Rate-limited 30/hr per user under the ``project_file`` bucket (shared with file updates).' operationId: add_file_api_v1_projects__slug__files_post security: - _Compat403HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FileCreate' responses: '201': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ProjectFileOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/projects/{slug}/collaborators/{username}: post: tags: - Projects summary: Add Collaborator description: 'Add a user as a collaborator. ``username`` is a username or a user ID. Creator-only — listed collaborators cannot promote others. Four rejection paths: - 404 if the slug or username doesn''t resolve. - 403 ``FORBIDDEN`` if the caller isn''t the creator. - 400 if the target is the creator themselves (already implicit). - 403 ``KARMA_TOO_LOW`` if the target has fewer than ``MIN_KARMA=5`` karma — same gate as project creation. - 409 ``CONFLICT`` if they''re already a collaborator. Collaborators get the same file edit + create + delete + project PATCH permissions as the creator (via ``_can_edit``); they do NOT get to delete the project or add/remove other collaborators. Rate-limited 10/hr per user under ``project_collab``.' operationId: add_collaborator_api_v1_projects__slug__collaborators__username__post security: - _Compat403HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug - name: username in: path required: true schema: type: string minLength: 1 maxLength: 64 description: A username or a user ID. title: Username description: A username or a user ID. responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/projects/{slug}/collaborators/{user_id}: delete: tags: - Projects summary: Remove Collaborator description: 'Remove a collaborator from a project. ``user_id`` is a username or a user ID. Creator-only. The target loses every edit permission immediately — there''s no grace period or "transferred ownership of their contributions" step (file rows are owned by the project, not by the contributor, and ``updated_by_id`` history stays as a historical record). A removed collaborator can still see the project (publish state governs visibility, not collaboration); they just can''t edit. Returns 404 if no such collaborator. Rate-limited 10/hr (shared ``project_collab`` bucket).' operationId: remove_collaborator_api_v1_projects__slug__collaborators__user_id__delete security: - _Compat403HTTPBearer: [] parameters: - name: slug in: path required: true schema: type: string title: Slug - name: user_id in: path required: true schema: type: string minLength: 1 maxLength: 64 description: A username or a user ID. title: User Id description: A username or a user ID. responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' components: schemas: FileUpdate: properties: content: type: string maxLength: 204800 title: Content type: object required: - content title: FileUpdate ProjectFileOut: properties: id: type: string format: uuid title: Id filename: type: string title: Filename file_type: type: string title: File Type created_at: type: string format: date-time title: Created At updated_at: type: string format: date-time title: Updated At type: object required: - id - filename - file_type - created_at - updated_at title: ProjectFileOut ProjectFileWithContent: properties: id: type: string format: uuid title: Id filename: type: string title: Filename file_type: type: string title: File Type created_at: type: string format: date-time title: Created At updated_at: type: string format: date-time title: Updated At content: type: string title: Content type: object required: - id - filename - file_type - created_at - updated_at - content title: ProjectFileWithContent ProjectUpdate: properties: name: anyOf: - type: string maxLength: 200 minLength: 1 - type: 'null' title: Name description: anyOf: - type: string - type: 'null' title: Description is_published: anyOf: - type: boolean - type: 'null' title: Is Published type: object title: ProjectUpdate ProjectOut: properties: id: type: string format: uuid title: Id name: type: string title: Name slug: type: string title: Slug description: anyOf: - type: string - type: 'null' title: Description creator: $ref: '#/components/schemas/ProjectAuthor' is_published: type: boolean title: Is Published files: items: $ref: '#/components/schemas/ProjectFileOut' type: array title: Files collaborators: items: $ref: '#/components/schemas/ProjectAuthor' type: array title: Collaborators created_at: type: string format: date-time title: Created At updated_at: type: string format: date-time title: Updated At type: object required: - id - name - slug - creator - is_published - files - collaborators - created_at - updated_at title: ProjectOut FileCreate: properties: filename: type: string maxLength: 100 minLength: 3 pattern: ^[a-zA-Z0-9][a-zA-Z0-9._-]{0,98}[a-zA-Z0-9]$ title: Filename type: object required: - filename title: FileCreate ProjectAuthor: properties: id: type: string format: uuid title: Id username: type: string title: Username display_name: type: string title: Display Name type: object required: - id - username - display_name title: ProjectAuthor HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError PaginatedList_ProjectListItem_: properties: items: items: $ref: '#/components/schemas/ProjectListItem' type: array title: Items total: type: integer title: Total has_more: type: boolean title: Has More type: object required: - items - total - has_more title: PaginatedList[ProjectListItem] ProjectListItem: properties: id: type: string format: uuid title: Id name: type: string title: Name slug: type: string title: Slug description: anyOf: - type: string - type: 'null' title: Description creator: $ref: '#/components/schemas/ProjectAuthor' is_published: type: boolean title: Is Published file_count: type: integer title: File Count created_at: type: string format: date-time title: Created At updated_at: type: string format: date-time title: Updated At type: object required: - id - name - slug - creator - is_published - file_count - created_at - updated_at title: ProjectListItem ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type input: title: Input ctx: type: object title: Context type: object required: - loc - msg - type title: ValidationError ProjectCreate: properties: name: type: string maxLength: 200 minLength: 1 title: Name slug: type: string maxLength: 200 minLength: 1 pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$ title: Slug description: anyOf: - type: string maxLength: 2000 - type: 'null' title: Description type: object required: - name - slug title: ProjectCreate securitySchemes: _Compat403HTTPBearer: type: http scheme: bearer HTTPBearer: type: http scheme: bearer