{"openapi":"3.1.0","info":{"title":"Photon API","description":"TIHLDEs nye backend","version":"1.0.0"},"servers":[{"url":"http://localhost:4000","description":"Local Server"},{"url":"https://photon.tihlde.org","description":"Production Server"}],"paths":{"/api/api-keys":{"post":{"operationId":"createApiKey","tags":["api-keys"],"summary":"Create API key","description":"Create a new API key. The full key is returned only once and cannot be retrieved again. Requires 'api-keys:create' permission.","responses":{"201":{"description":"API key created successfully. The 'key' field contains the full API key and will not be shown again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKey"}}}}},"get":{"operationId":"listApiKeys","tags":["api-keys"],"summary":"List API keys","description":"Get a list of all API keys. Does not include the full key values. Requires 'api-keys:view' permission.","responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/api-keys/{id}":{"get":{"operationId":"getApiKey","tags":["api-keys"],"summary":"Get API key","description":"Get a single API key by ID. Does not include the full key value. Requires 'api-keys:view' permission.","responses":{"200":{"description":"API key details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - API key not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"API key ID"}]},"patch":{"operationId":"updateApiKey","tags":["api-keys"],"summary":"Update API key","description":"Update an API key's metadata (name, description, permissions, metadata). Cannot update the key itself - use regenerate for that. Requires 'api-keys:update' permission.","responses":{"200":{"description":"API key updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - API key not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"API key ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateApiKey"}}}}},"delete":{"operationId":"deleteApiKey","tags":["api-keys"],"summary":"Delete API key","description":"Delete an API key. This action is irreversible. Requires 'api-keys:delete' permission.","responses":{"200":{"description":"API key deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteApiKeyResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - API key not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"API key ID"}]}},"/api/api-keys/{id}/regenerate":{"post":{"operationId":"regenerateApiKey","tags":["api-keys"],"summary":"Regenerate API key","description":"Generate a new key value for an existing API key. The old key will be invalidated. The new full key is returned only once and cannot be retrieved again. Requires 'api-keys:update' permission.","responses":{"200":{"description":"API key regenerated successfully. The 'key' field contains the new API key and will not be shown again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - API key not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"API key ID"}]}},"/api/api-keys/validate":{"post":{"operationId":"validateApiKey","tags":["api-keys"],"summary":"Validate API key","description":"Check if an API key is valid and return its details. This is a public endpoint that does not require authentication. Updates the lastUsedAt timestamp if the key is valid.","responses":{"200":{"description":"Validation result. If valid=true, includes the API key details and permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateApiKeyResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateApiKeyInput"}}}}}},"/api/applications/options":{"get":{"operationId":"getApplicationOptions","tags":["applications"],"summary":"Get application form options","description":"Groups selectable in the application forms, their økonomiansvarlig addresses, and the budget/case type labels.","responses":{"200":{"description":"Form options","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationOptions"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/applications/mine":{"get":{"operationId":"listMyApplications","tags":["applications"],"summary":"List my applications","description":"Lists the authenticated user's own applications and their processing status.","responses":{"200":{"description":"The user's applications, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"$ref":"#/components/parameters/ListApplicationsQuery"}]}},"/api/applications/expense":{"post":{"operationId":"createExpenseApplication","tags":["applications"],"summary":"Submit an expense claim","description":"Submit an utlegg. Any authenticated member may submit; the claim is handled by the Finansminister.","responses":{"201":{"description":"Application submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApplicationResponse"}}}},"400":{"description":"Bad Request - Unknown group or invalid attachment"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateExpenseApplication"}}}}}},"/api/applications/support":{"post":{"operationId":"createSupportApplication","tags":["applications"],"summary":"Apply for financial support","description":"Submit a søknad om støtte. Handled by the Finansminister and Hovedstyret.","responses":{"201":{"description":"Application submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApplicationResponse"}}}},"400":{"description":"Bad Request - Unknown group or invalid attachment"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupportApplication"}}}}}},"/api/applications/sports-support":{"post":{"operationId":"createSportsSupportApplication","tags":["applications"],"summary":"Apply for sports club support","description":"Submit a søknad om støtte til idrettslag. Handled by IdKom.","responses":{"201":{"description":"Application submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApplicationResponse"}}}},"400":{"description":"Bad Request - Unknown group or invalid attachment"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupportApplication"}}}}}},"/api/applications/hs-case":{"post":{"operationId":"createHsCaseApplication","tags":["applications"],"summary":"Submit a case to Hovedstyret","description":"Submit a sak til HS. A Vedtakssak must include a recommendation.","responses":{"201":{"description":"Application submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApplicationResponse"}}}},"400":{"description":"Bad Request - Invalid attachment or missing innstilling"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHsCaseApplication"}}}}}},"/api/applications":{"get":{"operationId":"listApplications","tags":["applications"],"summary":"List applications for handling","description":"Lists applications the caller is allowed to handle. Results are limited to the types they have view permission for.","responses":{"200":{"description":"Applications, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"$ref":"#/components/parameters/ListApplicationsQuery"}]}},"/api/applications/{id}/pdf":{"get":{"operationId":"getApplicationPdf","tags":["applications"],"summary":"Download an application's PDF","description":"Streams the generated PDF. Available to the submitter and to handlers of that application type.","responses":{"200":{"description":"The application PDF"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Unknown application, or no PDF generated"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/applications/{id}/attachments/{assetKey}":{"get":{"operationId":"getApplicationAttachment","tags":["applications"],"summary":"Download an application attachment","description":"Streams one of an application's attachments. Available to the submitter and to handlers of that application type.","responses":{"200":{"description":"The attachment"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Unknown application or attachment"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"assetKey","required":true}]}},"/api/applications/{id}/pdf/regenerate":{"post":{"operationId":"regenerateApplicationPdf","tags":["applications"],"summary":"Regenerate an application's PDF","description":"Re-renders and stores the PDF for an application. Used when generation failed at submission time.","responses":{"200":{"description":"The new PDF asset key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegenerateApplicationPdfResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/applications/{id}/status":{"patch":{"operationId":"updateApplicationStatus","tags":["applications"],"summary":"Update an application's status","description":"Sets the processing status and optional internal note. Approving or rejecting emails the submitter; moving to 'under behandling' does not.","responses":{"200":{"description":"The updated application","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationDetail"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateApplicationStatus"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/applications/{id}":{"get":{"operationId":"getApplication","tags":["applications"],"summary":"Get an application","description":"Returns a single application. Available to the submitter and to anyone with view permission for that application's type. The internal comment is only included for handlers.","responses":{"200":{"description":"The application","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationDetail"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Unknown application, or not yours"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/assets":{"post":{"operationId":"uploadAsset","tags":["assets"],"summary":"Upload a file","description":"Upload a file to staging. The file will be automatically cleaned up after 2 days if not promoted.\n\nRequires either session authentication or a valid API key.\n\n**Constraints:**\n- Maximum file size: 50MB\n- Allowed MIME types: image/jpeg, image/png, image/gif, image/webp, application/pdf\n\n**Image optimization:**\nJPEG, PNG and WebP uploads are re-encoded to WebP, scaled to fit within 2560x2560 px and stripped of metadata. The response therefore reports the stored `contentType`, `size` and `originalFilename` (extension swapped to `.webp`), which may differ from what was sent. GIF is passed through untouched so animation survives.\n\n**Usage:**\nSend the file as multipart/form-data with a field named \"file\".\n\nPass an optional \"visibility\" field set to \"private\" for files that must not be served by the open `GET /api/assets/:key` route — those are only reachable through a route that does its own authorization (e.g. application attachments).","responses":{"201":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}},"400":{"description":"Bad Request - Invalid file type or size exceeded"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/assets/metadata/{key}":{"get":{"operationId":"getAssetMetadata","tags":["assets"],"summary":"Get asset metadata","description":"Get metadata for an asset by its key. No authentication required.\n\nThe key is the full path returned when uploading, e.g., `uploads/2024/01/uuid_filename.jpg`","responses":{"200":{"description":"Asset metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetMetadata"}}}},"404":{"description":"Not Found - Asset not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"key","required":true}]}},"/api/assets/promote/{key}":{"post":{"operationId":"promoteAsset","tags":["assets"],"summary":"Promote an uploaded asset","description":"Mark an uploaded asset as claimed so it survives the staging cleanup that deletes unclaimed uploads after 2 days.\n\nThe key is the full path returned when uploading, e.g. `uploads/2026/08/uuid_filename.webp`.\n\nCall this once the URL has been stored somewhere permanent. Routes within Photon do it for you; external clients that keep the URL in their own database must call it themselves.\n\nOnly the account that uploaded the asset may promote it. Promoting an already-promoted asset is a no-op and still answers 200.","responses":{"200":{"description":"Asset promoted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetMetadata"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Du kan bare promotere filer du selv har lastet opp.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Asset not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"key","required":true}]}},"/api/assets/{key}":{"get":{"operationId":"downloadAsset","tags":["assets"],"summary":"Download a file","description":"Download a file by its key. No authentication required.\n\nThe key is the full path returned when uploading, e.g., `uploads/2024/01/uuid_filename.jpg`\n\nPrivate assets (e.g. contract signatures) are never served here — they are only\nreachable through routes that perform their own authorization.\n\n**Resized variants:**\nPass `?w=` with one of 160, 320, 480, 640, 960, 1280, 1920 to get the image\nre-encoded as WebP at that width, keeping its aspect ratio and never upscaling.\nVariants are generated on first request and cached, so a card never has to\ndownload a full-resolution original. Any other width is rejected, and the\nparameter is ignored for non-image assets (PDF, GIF).","responses":{"200":{"description":"File content with appropriate Content-Type header"},"400":{"description":"Bad Request - Unsupported width"},"404":{"description":"Not Found - Asset not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"key","required":true}]}},"/api/banners/visible":{"get":{"operationId":"listVisibleBanners","tags":["banners"],"summary":"List currently visible banners","description":"Banners whose visibility window covers the current moment, newest first. Public endpoint — this is what the front page renders.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisibleBannerList"}}}}}}},"/api/banners":{"get":{"operationId":"listBanners","tags":["banners"],"summary":"List all banners","description":"Every banner regardless of visibility window, for the admin panel. Requires 'banners:view' or 'banners:manage', held globally or for any single group. There are only ever a handful of banners, so the list is unpaginated.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BannerList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}},"post":{"operationId":"createBanner","tags":["banners"],"summary":"Create banner","description":"Create a new front-page banner. Requires 'banners:create' or 'banners:manage', held globally or for any single group.","responses":{"201":{"description":"Banner created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Banner"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBanner"}}}}}},"/api/banners/{id}":{"patch":{"operationId":"updateBanner","tags":["banners"],"summary":"Update banner","description":"Update a banner. Requires 'banners:update' or 'banners:manage', held globally or for any single group.","responses":{"200":{"description":"Banner updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Banner"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Banner not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBanner"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"delete":{"operationId":"deleteBanner","tags":["banners"],"summary":"Delete banner","description":"Delete a banner. Requires 'banners:delete' or 'banners:manage', held globally or for any single group.","responses":{"200":{"description":"Banner deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteBannerResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Banner not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/account-link/help":{"post":{"operationId":"requestAccountLinkHelp","tags":["account-link"],"summary":"Ask a human to link a Feide login to an old account","description":"Last resort for a member migrated from Lepton whose stored username differs from their NTNU one and who no longer has the email the old account was registered with — nothing in the flow can verify them, so this notifies an admin to do it by hand. Rate limited per client.","responses":{"200":{"description":"Request delivered to an admin","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountLinkHelpResponse"}}}},"400":{"description":"Bad Request - Invalid input"},"429":{"description":"Too many requests from this client"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountLinkHelp"}}}}}},"/api/account-link/sync":{"post":{"operationId":"syncFeideAccount","tags":["account-link"],"summary":"Sync study data from Feide for the current user","description":"Reads the caller's study programmes, cohort and campus from Feide and applies them, including derived study groups and the member/alumni role. Run after linking a Feide account, which cannot sync on its own. Safe to repeat.","responses":{"200":{"description":"Study data synced","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountLinkSyncResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"409":{"description":"No Feide account is linked to this user"},"502":{"description":"Feide could not be reached"}}}},"/api/company/contact":{"post":{"operationId":"submitCompanyContact","tags":["companies"],"summary":"Submit company contact form","description":"Public endpoint used by the company landing page. Stores the enquiry as an application so the Næringslivsminister can handle it in /admin/kontaktskjema, and emails it to TIHLDE's business contact. Rate limited per client.","responses":{"200":{"description":"Submission queued for delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyContactResponse"}}}},"400":{"description":"Bad Request - Invalid input"},"429":{"description":"Too many submissions from this client"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyContact"}}}}}},"/api/email/send":{"post":{"operationId":"sendCustomEmail","tags":["emails"],"summary":"Send custom email","description":"Send a custom email with structured content blocks. Requires API key authentication via Bearer token.","responses":{"200":{"description":"Email queued successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailResponse"}}}},"401":{"description":"Unauthorized - Missing or invalid Authorization header"},"403":{"description":"Forbidden - Invalid API key"},"503":{"description":"Email API not configured"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendCustomEmail"}}}}}},"/api/event/strikes":{"get":{"operationId":"listStrikes","tags":["strikes"],"summary":"List strikes","description":"Retrieve a paginated list of all strikes (prikker), including the affected user and the related event. Optionally filter by user. Requires 'events:strikes:view' or 'events:manage' permission, except when filtering on your own user ID.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrikeList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"userId","schema":{"type":"string"},"description":"Filter strikes by user ID"}]},"post":{"operationId":"createStrike","tags":["strikes"],"summary":"Create strike","description":"Give a user a strike (prikk) connected to an event. Requires 'events:strikes:create' or 'events:manage', globally or for the group arranging the event.","responses":{"201":{"description":"Strike created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Strike"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User or event not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStrike"}}}}}},"/api/event/my-registrations":{"get":{"operationId":"getMyEventHistory","tags":["events"],"summary":"Get my past event registrations","description":"Retrieve a paginated list of past events you were registered for, newest first.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyEventHistory"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"}]}},"/api/event/my-upcoming-registrations":{"get":{"operationId":"getMyUpcomingEvents","tags":["events"],"summary":"Get my upcoming event registrations","description":"Retrieve the events you are registered, waitlisted or pending for that have not ended yet, soonest first.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyUpcomingEvents"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/event/strikes/{strikeId}":{"delete":{"operationId":"deleteStrike","tags":["strikes"],"summary":"Delete strike","description":"Delete a strike (prikk) by its ID. Requires 'events:strikes:delete' or 'events:manage', globally or for the group arranging the strike's event.","responses":{"200":{"description":"Strike deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteStrikeResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Strike not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"strikeId","required":true}]}},"/api/event/calendar/{token}/events.ics":{"get":{"operationId":"getCalendarFeed","tags":["events"],"summary":"Get a user's event calendar as iCalendar","description":"Returns the events the token's owner is registered for as an iCalendar (.ics) feed. Authenticated by the secret token in the path, so calendar clients can subscribe to it directly.","responses":{"200":{"description":"iCalendar feed","content":{"text/calendar":{"schema":{"type":"string"}}}},"404":{"description":"Not Found - Unknown calendar token"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}]}},"/api/event/favorite/{id}":{"put":{"operationId":"updateEventFavorite","tags":["events"],"summary":"Update event favorite","description":"Mark or unmark an event as a favorite for the authenticated user","responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFavoriteResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFavoriteEvent"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/event/favorite":{"get":{"operationId":"getFavoriteEvents","tags":["events"],"summary":"Get all my favorite events","description":"Retrieve a list of all events you have marked as favorite.","responses":{"200":{"description":"List of favorite events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FavoriteEvents"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/event":{"post":{"operationId":"createEvent","tags":["events"],"summary":"Create event","description":"Create a new event. Requires 'events:create' either globally or for the group arranging it.","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventSchema"}}}}},"get":{"operationId":"listEvents","tags":["events"],"summary":"List events","description":"Retrieve a paginated list of all events with basic information including organizer and category details","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventList"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term to filter events by title"},{"in":"query","name":"category","schema":{"type":"array","items":{"type":"string"}},"description":"Only return events in these category slugs. Omit to return every category."},{"in":"query","name":"expired","schema":{"type":"boolean"},"description":"Filter on whether the event has already ended. True returns only past events, false only upcoming ones. Omit to return both."},{"in":"query","name":"openSignUp","schema":{"type":"boolean"},"description":"Whether to include only events with open sign-ups"},{"in":"query","name":"organizerGroupSlug","schema":{"type":"string"},"description":"Only return events organised by this group. Omit to return events from every organizer."},{"in":"query","name":"ordering","schema":{"type":"string","enum":["upcoming","newest","oldest"]},"description":"How to order the result. 'upcoming' puts ongoing and future events first, soonest first, with past events after them most-recent-first. 'newest' and 'oldest' sort every event by start time, descending and ascending respectively. Omit to keep the legacy default: ascending when expired=false, descending otherwise."}]}},"/api/event/{id}":{"put":{"operationId":"updateEvent","tags":["events"],"summary":"Update event","description":"Update an event by its ID. Event creators can update their own events. Users with 'events:update' or 'events:manage' — globally or for the arranging group — can update the events they hold it for.","responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEventResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEventSchema"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/event/{eventId}":{"delete":{"operationId":"deleteEvent","tags":["events"],"summary":"Delete an event","description":"Delete an event by its ID. Event creators can delete their own events. Users with 'events:delete' — globally or for the arranging group — can delete any event they hold it for. This action is irreversible and will remove all associated data, including registrations and feedback.","responses":{"200":{"description":"Event successfully deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteEventResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event with the specified ID does not exist"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]},"get":{"operationId":"getEvent","tags":["events"],"summary":"Get event by ID","description":"Retrieve detailed information about a specific event, including registration status for the authenticated user if available","responses":{"200":{"description":"The event was found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]}},"/api/event/{eventId}/registration":{"post":{"operationId":"createEventRegistration","tags":["events"],"summary":"Register to an event","description":"Create a new registration for the authenticated user to attend an event, initially with pending status. Requires the 'events:registrations:create' permission — granted by the member baseline role (active students), not by the alumni role.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventRegistration"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event not found"},"409":{"description":"Event is not open for registration or user already registered"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventRegistrationBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]},"get":{"operationId":"listEventRegistrations","tags":["events"],"summary":"Get event registrations","description":"Retrieve a paginated list of users registered for a specific event. Members only — who attends is not something the open internet gets to enumerate; the event itself carries a public `registeredCount` for the visible number. Event admins additionally receive email, registration time, waitlist position and payment status, and may filter by registration status.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventRegistrationList"}}}},"400":{"description":"Bad Request - Unknown registration status"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated registration statuses to include. Admin only; defaults to registered,attended,no_show."},{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]},"delete":{"operationId":"deleteEventRegistration","tags":["events"],"summary":"Unregister from event","description":"Remove the authenticated user's registration from an event. If the event can cause strikes and the user unregisters from a confirmed spot after the cancellation deadline, they are given 1 strike.","responses":{"200":{"description":"OK"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]}},"/api/event/{eventId}/registration/{userId}/attendance":{"patch":{"operationId":"setRegistrationAttendance","tags":["events"],"summary":"Set registration attendance","description":"Mark a registered user as attended (checked in) or revert them to registered. Used for event check-in; users left as 'registered' after the event may receive no-show strikes. Requires 'events:update' or 'events:manage', globally or for the arranging group.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attendance"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Registration not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAttendance"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"eventId","required":true},{"schema":{"type":"string"},"in":"path","name":"userId","required":true}]}},"/api/event/{eventId}/payment":{"post":{"operationId":"createEventPayment","tags":["events","payments"],"summary":"Create payment for event","description":"Initiates a Vipps payment for an event registration. User must have a registered status for the event.","responses":{"201":{"description":"Payment created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePaymentResponse"}}}},"400":{"description":"Bad Request - event not found or not a paid event"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event or registration not found"},"409":{"description":"Payment already exists for this user and event"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePaymentBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]}},"/api/event/{eventId}/payments":{"get":{"operationId":"listEventPayments","tags":["events","payments"],"summary":"List event payments","description":"Retrieve a paginated list of every payment for an event, including the payer and a status summary. Requires 'events:payments:view', 'events:manage' or 'events:update' — or being the event's creator.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventPaymentList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","paid","refunded","failed"]},"description":"Only return payments with this status"},{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]}},"/api/event/{eventId}/payments/{paymentId}/refund":{"post":{"operationId":"refundEventPayment","tags":["events","payments"],"summary":"Refund an event payment","description":"Reverse a completed payment with the payment provider and mark it as refunded. The full remaining refundable amount is returned to the payer. This does not cancel the registration — free the spot separately if that is wanted. Requires 'events:payments:refund'.","responses":{"200":{"description":"Payment refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundEventPayment"}}}},"400":{"description":"Bad Request - Invalid event or payment id"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Payment not found on this event"},"409":{"description":"Payment is not in a refundable state"},"502":{"description":"The payment provider rejected the refund"}},"parameters":[{"in":"path","name":"eventId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"paymentId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/api/event/payment/webhook":{"post":{"operationId":"handlePaymentWebhook","tags":["payments","webhooks"],"summary":"Vipps payment webhook","description":"Webhook endpoint for Vipps to notify about payment status changes. Updates payment record based on Vipps payment state.","responses":{"200":{"description":"Webhook processed successfully"},"400":{"description":"Bad Request - Invalid webhook payload"},"401":{"description":"Invalid webhook signature"},"404":{"description":"Not Found - Payment not found"}}}},"/api/event/{eventId}/forms":{"post":{"operationId":"createEventForm","tags":["events","forms"],"summary":"Create event form","description":"Create a survey or evaluation form for an event. Requires event write permission.","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventFormResponse"}}}},"400":{"description":"Bad Request - Form already exists for this event type"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventForm"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]},"get":{"operationId":"listEventForms","tags":["events","forms"],"summary":"List event forms","description":"Get all forms (survey and evaluation) for an event","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventFormList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"eventId","required":true}]}},"/api/event/{eventId}/forms/{type}":{"get":{"operationId":"getEventForm","tags":["events","forms"],"summary":"Get event form","description":"Get a specific form (survey or evaluation) for an event with all fields and options","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventFormDetail"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Form not found"}},"parameters":[{"in":"path","name":"eventId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"type","schema":{"type":"string","enum":["survey","evaluation"]},"required":true}]}},"/api/feedback":{"get":{"operationId":"listFeedback","tags":["feedback"],"summary":"List feedback","description":"Paginated list of ideas and bug reports, newest first. Requires authentication.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"type","schema":{"type":"string","enum":["idea","bug"]}},{"in":"query","name":"status","schema":{"type":"string","enum":["open","in_progress","closed","rejected"]}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Free-text search in title and description"}]},"post":{"operationId":"createFeedback","tags":["feedback"],"summary":"Create feedback","description":"File an idea or a bug report. Any logged-in member may do this — no permission needed.","responses":{"201":{"description":"Feedback created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackItem"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFeedback"}}}}}},"/api/feedback/{id}":{"patch":{"operationId":"updateFeedback","tags":["feedback"],"summary":"Update feedback","description":"Edit a piece of feedback, or move it along its status. Requires 'feedback:update' or 'feedback:manage', or being the author. Only the permission may change the status.","responses":{"200":{"description":"Feedback updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackItem"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Feedback not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"Feedback ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFeedback"}}}}},"delete":{"operationId":"deleteFeedback","tags":["feedback"],"summary":"Delete feedback","description":"Delete a piece of feedback. Requires 'feedback:delete' or 'feedback:manage', or being the author.","responses":{"200":{"description":"Feedback deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackMessageResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Feedback not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"Feedback ID"}]}},"/api/feedback/{id}/vote":{"put":{"operationId":"voteFeedback","tags":["feedback"],"summary":"Vote on feedback","description":"Cast or change your vote on a piece of feedback. One vote per user — voting again replaces the previous one.","responses":{"200":{"description":"Vote registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackVoteCounts"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Feedback not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"Feedback ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoteFeedback"}}}}},"delete":{"operationId":"deleteFeedbackVote","tags":["feedback"],"summary":"Remove your vote","description":"Take back your vote on a piece of feedback.","responses":{"200":{"description":"Vote removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackVoteCounts"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Feedback not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"Feedback ID"}]}},"/api/forms":{"post":{"operationId":"createForm","tags":["forms"],"summary":"Create form","description":"Create a new base form template. Requires 'forms:create' permission.","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateForm"}}}}},"get":{"operationId":"listForms","tags":["forms"],"summary":"List forms","description":"List all form templates by default. Use ?all=true to include all forms. Returns template forms by default.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/forms/{id}":{"get":{"operationId":"getForm","tags":["forms"],"summary":"Get form","description":"Get a form by ID with all fields and options","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormDetail"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Form not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"patch":{"operationId":"updateForm","tags":["forms"],"summary":"Update form","description":"Update a form. Requires permission to manage the form.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFormResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Form not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateForm"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"delete":{"operationId":"deleteForm","tags":["forms"],"summary":"Delete form","description":"Delete a form and all associated data. Requires permission to manage the form.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteFormResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Form not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/forms/{id}/statistics":{"get":{"operationId":"getFormStatistics","tags":["forms"],"summary":"Get form statistics","description":"Get aggregated statistics for a form. Requires permission to manage the form.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormStatistics"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/forms/{formId}/submissions":{"post":{"operationId":"createFormSubmission","tags":["forms"],"summary":"Create submission","description":"Submit answers to a form","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubmissionResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Form not found"},"409":{"description":"Conflict - Duplicate submission not allowed"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubmission"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"formId","required":true}]},"get":{"operationId":"listFormSubmissions","tags":["forms"],"summary":"List submissions","description":"List all submissions for a form. Requires permission to manage the form.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Form not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"formId","required":true}]}},"/api/forms/{formId}/submissions/download":{"get":{"operationId":"downloadFormSubmissions","tags":["forms"],"summary":"Download submissions as CSV","description":"Download all submissions for a form as CSV. Requires permission to manage the form.","responses":{"200":{"description":"Success - Returns CSV file","content":{"text/csv":{}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Form not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"formId","required":true}]}},"/api/forms/{formId}/submissions/{id}":{"get":{"operationId":"getFormSubmission","tags":["forms"],"summary":"Get submission","description":"Get a specific submission. Can view own submission or requires permission to manage the form.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionDetail"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Submission not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"formId","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/forms/{formId}/submissions/{id}/destroy_with_reason":{"delete":{"operationId":"deleteFormSubmission","tags":["forms"],"summary":"Delete submission with reason","description":"Delete a submission and notify the user with a reason. Admin only.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSubmissionResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Submission not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSubmissionWithReason"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"formId","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/galleries":{"post":{"operationId":"createGallery","tags":["gallery"],"summary":"Create gallery","description":"Create a new photo album. Requires 'galleries:create' permission.","responses":{"201":{"description":"Album created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gallery"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGallery"}}}}},"get":{"operationId":"listGalleries","tags":["gallery"],"summary":"List galleries","description":"Get a paginated list of photo albums, newest first. Members only.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GalleryList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Free-text search across title and description"}]}},"/api/galleries/{slug}/pictures":{"get":{"operationId":"listGalleryPictures","tags":["gallery"],"summary":"List pictures in a gallery","description":"Get a paginated list of the pictures in an album. This is what backs the 'Last inn mer' button. Members only.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GalleryPictureList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Gallery not found"}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]},"post":{"operationId":"createGalleryPictures","tags":["gallery"],"summary":"Add pictures to a gallery","description":"Add one or more already-uploaded images to an album. Upload the files via POST /api/assets first, then send the resulting URLs here. Requires 'galleries:pictures:create' or 'galleries:manage' permission.","responses":{"201":{"description":"Pictures added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGalleryPicturesResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Gallery not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGalleryPictures"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]}},"/api/galleries/{slug}/pictures/{pictureId}":{"patch":{"operationId":"updateGalleryPicture","tags":["gallery"],"summary":"Update a picture","description":"Update the title, description or alt text of a picture. Requires 'galleries:pictures:update' or 'galleries:manage' permission.","responses":{"200":{"description":"Picture updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GalleryPicture"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Gallery or picture not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGalleryPicture"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"pictureId","required":true}]},"delete":{"operationId":"deleteGalleryPicture","tags":["gallery"],"summary":"Remove a picture from a gallery","description":"Remove a single picture from an album. Requires 'galleries:pictures:delete' or 'galleries:manage' permission.","responses":{"200":{"description":"Picture removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteGalleryPictureResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Gallery or picture not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"pictureId","required":true}]}},"/api/galleries/{slug}":{"get":{"operationId":"getGallery","tags":["gallery"],"summary":"Get gallery","description":"Get a single album by slug (or ID). Pictures are fetched separately via /api/galleries/{slug}/pictures. Members only.","responses":{"200":{"description":"Album details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gallery"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Gallery not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]},"patch":{"operationId":"updateGallery","tags":["gallery"],"summary":"Update gallery","description":"Update an album. Requires 'galleries:update' or 'galleries:manage' permission, or being the creator. The slug is not regenerated on rename, so existing links keep working.","responses":{"200":{"description":"Album updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gallery"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Gallery not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGallery"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]},"delete":{"operationId":"deleteGallery","tags":["gallery"],"summary":"Delete gallery","description":"Delete an album and every picture in it. Requires 'galleries:delete' or 'galleries:manage' permission, or being the creator.","responses":{"200":{"description":"Album deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteGalleryResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Gallery not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]}},"/api/notification/device":{"post":{"operationId":"registerNotificationDevice","tags":["notifications"],"summary":"Register a device for push notifications","description":"Stores the Expo push token of the calling user's device. Registering a token that already exists moves it to the authenticated user, so a shared phone never keeps notifying its previous owner.","responses":{"200":{"description":"Device registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationDeviceResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterNotificationDevice"}}}}},"delete":{"operationId":"unregisterNotificationDevice","tags":["notifications"],"summary":"Unregister a device from push notifications","description":"Removes the Expo push token, typically on logout. Deleting a token that is not registered is a no-op.","responses":{"200":{"description":"Device unregistered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationDeviceResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnregisterNotificationDevice"}}}}}},"/api/notification":{"get":{"operationId":"listNotifications","tags":["notifications"],"summary":"List notifications for authenticated user","description":"Returns paginated list of notifications for the authenticated user, ordered by most recent first. Pass isRead=false to count unread ones: totalCount answers that without fetching the notifications themselves.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"isRead","schema":{"type":"boolean"},"description":"Filter on read state. False returns only unread notifications, true only read ones. Omit to return both."}]}},"/api/notification/{id}":{"delete":{"operationId":"deleteNotification","tags":["notifications"],"summary":"Delete notification","description":"Delete a notification by ID. User must be authenticated and own the notification.","responses":{"200":{"description":"Notification deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteNotificationResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Notification not found or not owned by user"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/notification/{id}/read":{"patch":{"operationId":"markNotificationRead","tags":["notifications"],"summary":"Mark notification as read or unread","description":"Update the read status of a notification. User must be authenticated and own the notification.","responses":{"200":{"description":"Notification updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkReadResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Notification not found or not owned by user"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkNotificationRead"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/groups":{"get":{"operationId":"listGroups","tags":["groups"],"summary":"List groups","description":"Retrieve a list of all groups. Supports optional filtering by type and search query.","responses":{"200":{"description":"List of groups retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupList"}}}}}},"post":{"operationId":"createGroup","tags":["groups"],"summary":"Create group","description":"Create a new group. Requires 'groups:create' permission.","responses":{"201":{"description":"Group created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Group"}}}},"400":{"description":"Bad Request - Invalid input or slug already exists"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGroup"}}}}}},"/api/groups/mine":{"get":{"operationId":"listMyGroups","tags":["groups"],"summary":"List current user's groups","description":"Retrieve all groups the authenticated user is a member of, including membership info.","responses":{"200":{"description":"List of groups the user is a member of","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyGroupList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/groups/{slug}":{"patch":{"operationId":"updateGroup","tags":["groups"],"summary":"Partially update group","description":"Partially update an existing group by its slug. Only provided fields will be updated. Requires being a group leader OR having 'groups:update' permission (globally or scoped to this group).","responses":{"200":{"description":"Group updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupResponse"}}}},"400":{"description":"Bad Request - Invalid input"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group with the specified slug does not exist"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroup"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]},"delete":{"operationId":"deleteGroup","tags":["groups"],"summary":"Delete a group","description":"Delete a group by its slug. Requires being a group leader OR having 'groups:delete' permission (globally or scoped to this group). This action is irreversible and will remove all associated data, including memberships and fines.","responses":{"204":{"description":"Group successfully deleted"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group with the specified slug does not exist"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]},"get":{"operationId":"getGroup","tags":["groups"],"summary":"Get group by slug","description":"Retrieve detailed information about a specific group by its slug identifier.","responses":{"200":{"description":"Group details retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupDetail"}}}},"403":{"description":"Forbidden - The group is private and you are not a member"},"404":{"description":"Not Found - Group with the specified slug does not exist"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]}},"/api/groups/{groupSlug}/fines":{"get":{"operationId":"listFines","tags":["fines"],"summary":"List fines for a group","description":"Retrieve a paginated list of fines for a group, newest first. Group members can view all fines in their own group (Lepton parity), as can the fines admin and root. Filter with 'status' and 'userId'.","responses":{"200":{"description":"List of fines retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FineList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found, or fines not activated for it"}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","approved","paid","rejected"]},"description":"Only return fines with this status"},{"in":"query","name":"userId","schema":{"type":"string"},"description":"Only return fines given to this user"},{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]},"post":{"operationId":"createFine","tags":["fines"],"summary":"Create fine","description":"Create a new fine for a group member. Requires being a member of the group (bøter follow membership, not permissions). Root may fine across groups.","responses":{"201":{"description":"Fine created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Fine"}}}},"400":{"description":"Bad Request - Invalid input or fines not activated for group"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group or user not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFine"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/fines/statistics":{"get":{"operationId":"getFineStatistics","tags":["fines"],"summary":"Fine totals for a group","description":"Sum of fine amounts per settlement stage for a group: awaiting approval, approved but unpaid, and paid. Rejected fines are excluded. Same audience as the fine list.","responses":{"200":{"description":"Statistics retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FineStatistics"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found, or fines not activated for it"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/fines/users":{"get":{"operationId":"listFineUsers","tags":["fines"],"summary":"List a group's members with their fine totals","description":"Paginated list of the group's members with the sum of their fine amounts, highest first. Members without fines are included with a total of 0. Filter the totals with 'status'.","responses":{"200":{"description":"Members with fine totals retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FineUserList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found, or fines not activated for it"}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","approved","paid","rejected"]},"description":"Only count fines with this status"},{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/fines/batch":{"patch":{"operationId":"batchUpdateFines","tags":["fines"],"summary":"Update several fines at once","description":"Set the same status on a list of fines. Every fine must belong to the group in the path. Requires being the fines admin (botsjef) or the group's leader.","responses":{"200":{"description":"Fines updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUpdateFinesResponse"}}}},"400":{"description":"Bad Request - A fine does not belong to this group"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found, or fines not activated for it"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUpdateFines"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/fines/users/{userId}/batch":{"patch":{"operationId":"batchUpdateUserFines","tags":["fines"],"summary":"Update all of one member's fines","description":"Set the same status on every fine the member has in this group, regardless of any filter the caller is currently looking at (Lepton parity). Requires being the fines admin (botsjef) or the group's leader.","responses":{"200":{"description":"Fines updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUpdateFinesResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found, or fines not activated for it"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUpdateUserFines"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"userId","required":true}]}},"/api/groups/{groupSlug}/fines/{fineId}":{"get":{"operationId":"getFine","tags":["fines"],"summary":"Get fine by ID","description":"Retrieve detailed information about a specific fine. Group members can view every fine in their own group, users can always view their own, and the fines admin and root can view any.","responses":{"200":{"description":"Fine details retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Fine"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Fine or group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"fineId","required":true}]},"patch":{"operationId":"updateFine","tags":["fines"],"summary":"Partially update fine","description":"Partially update a fine. Only provided fields will be updated. Users can add defense to their own fines. Fines admins can update status and approve/reject fines.","responses":{"200":{"description":"Fine updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFineResponse"}}}},"400":{"description":"Bad Request - Invalid status transition"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Fine or group not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFine"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"fineId","required":true}]},"delete":{"operationId":"deleteFine","tags":["fines"],"summary":"Delete a fine","description":"Delete a fine by its ID. Requires being the fines admin (botsjef) or the group's leader. This action is irreversible.","responses":{"204":{"description":"Fine successfully deleted"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Fine or group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"fineId","required":true}]}},"/api/groups/{groupSlug}/laws":{"get":{"operationId":"listLaws","tags":["laws"],"summary":"List a group's laws (lovverk)","description":"Retrieve the fine laws for a group, ordered by paragraph. Group members can view their own group's laws (Lepton parity), as can the fines admin and root.","responses":{"200":{"description":"List of laws retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LawList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]},"post":{"operationId":"createLaw","tags":["laws"],"summary":"Create a law","description":"Add a paragraph to a group's lovverk. Requires being the group's fines admin (botsjef) or a group leader.","responses":{"201":{"description":"Law created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Law"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLaw"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/laws/{lawId}":{"patch":{"operationId":"updateLaw","tags":["laws"],"summary":"Update a law","description":"Update a paragraph in a group's lovverk. Requires being the group's fines admin (botsjef) or a group leader.","responses":{"200":{"description":"Law updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Law"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Law or group not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLaw"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"lawId","required":true}]},"delete":{"operationId":"deleteLaw","tags":["laws"],"summary":"Delete a law","description":"Delete a paragraph from a group's lovverk. Requires being the group's fines admin (botsjef) or a group leader.","responses":{"204":{"description":"Law successfully deleted"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Law or group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"lawId","required":true}]}},"/api/groups/{groupSlug}/members/history":{"get":{"operationId":"listGroupFormerMembers","tags":["groups"],"summary":"List former group members","description":"Retrieve the group's ended memberships — the people who used to be members. Anyone currently in the group is excluded, and a member with several stints is listed once, by their most recent one.","responses":{"200":{"description":"List of former members retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupFormerMemberList"}}}},"403":{"description":"Forbidden - The group is private and you are not a member"},"404":{"description":"Not Found - Group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/members":{"get":{"operationId":"listGroupMembers","tags":["groups"],"summary":"List group members","description":"Retrieve a list of all members in a group.","responses":{"200":{"description":"List of members retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupMemberList"}}}},"403":{"description":"Forbidden - The group is private and you are not a member"},"404":{"description":"Not Found - Group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]},"post":{"operationId":"addGroupMember","tags":["groups"],"summary":"Add member to group","description":"Add a member to a group. Requires 'groups:manage' (globally or scoped to the group), or being the group's leader.","responses":{"201":{"description":"Member added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupMembership"}}}},"400":{"description":"Bad Request - User already a member or user not found"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddGroupMember"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/members/{userId}":{"patch":{"operationId":"updateGroupMemberRole","tags":["groups"],"summary":"Update member role","description":"Update a member's role in a group. Requires 'groups:manage' (globally or scoped to the group), or being the group's leader — a sitting leader may only hand the leadership over to someone else.","responses":{"200":{"description":"Member role updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMemberRoleResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group, user, or membership not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupMemberRole"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"userId","required":true}]},"delete":{"operationId":"removeGroupMember","tags":["groups"],"summary":"Remove member from group","description":"Remove a member from a group. Requires 'groups:manage' (globally or scoped to the group), or being the group's leader.","responses":{"204":{"description":"Member removed successfully"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group, user, or membership not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"userId","required":true}]}},"/api/groups/{groupSlug}/positions":{"get":{"operationId":"listGroupPositions","tags":["positions"],"summary":"List positions for a group","description":"Retrieve all positions (verv) for a group, with their holders.","responses":{"200":{"description":"List of positions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupPositionList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]},"post":{"operationId":"createGroupPosition","tags":["positions"],"summary":"Create a position","description":"Create a new position (verv) in a group. Group leaders can create group-scoped positions limited to permissions they themselves hold in the group. Positions with global scope require the global 'roles:create' permission, and the creator must hold every granted permission globally.","responses":{"201":{"description":"Position created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupPosition"}}}},"400":{"description":"Bad Request - Invalid input"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGroupPosition"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/positions/{positionId}":{"patch":{"operationId":"updateGroupPosition","tags":["positions"],"summary":"Update a position","description":"Update a position's name, description, permissions or scope. The same guardrails as creation apply — including for the position's EXISTING scope, so a group leader cannot edit a global position, and permission changes are limited to permissions the editor holds.","responses":{"200":{"description":"Position updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupPosition"}}}},"400":{"description":"Bad Request - Invalid input"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Position not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupPosition"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"positionId","required":true}]},"delete":{"operationId":"deleteGroupPosition","tags":["positions"],"summary":"Delete a position","description":"Delete a position (verv). Holders lose the position's permissions. Deleting an HS position also takes the holder's seat in HS unless something else warrants it. Deleting a global position requires 'roles:create'.","responses":{"200":{"description":"Position deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupPositionMessage"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Position not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"positionId","required":true}]}},"/api/groups/{groupSlug}/positions/{positionId}/holders":{"post":{"operationId":"assignGroupPosition","tags":["positions"],"summary":"Assign a position to a user","description":"Assign a position (verv) to a group member. A position can be held by at most ONE user — assigning an occupied position fails; unassign the current holder first (or create a second position for a second holder). The assigner must be able to manage the position AND hold all its permissions — you cannot hand out a title you could not have created (prevents e.g. assigning the root title without holding root).","responses":{"200":{"description":"Position assigned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupPositionMessage"}}}},"400":{"description":"Bad Request - User is not a member of the group"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Position not found"},"409":{"description":"Position already has a holder"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignGroupPosition"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"positionId","required":true}]}},"/api/groups/{groupSlug}/positions/{positionId}/holders/{userId}":{"delete":{"operationId":"unassignGroupPosition","tags":["positions"],"summary":"Remove a position from a user","description":"Remove a position (verv) from a holder. Same authorization as assigning.","responses":{"200":{"description":"Position removed from user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupPositionMessage"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Position not found"},"409":{"description":"Position is managed automatically from group leadership"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"positionId","required":true},{"schema":{"type":"string"},"in":"path","name":"userId","required":true}]}},"/api/groups/{groupSlug}/leader-permissions":{"get":{"operationId":"getGroupLeaderPermissions","tags":["positions"],"summary":"Get the group leader's permissions","description":"The permissions held by whoever currently leads the group, scoped to that group. Requires the same rights as managing the group's verv.","responses":{"200":{"description":"The leader's permissions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupLeaderPermissions"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]},"patch":{"operationId":"updateGroupLeaderPermissions","tags":["positions"],"summary":"Set the group leader's permissions","description":"Replace the permissions held by the group's leader. Granted scoped to this group, so they never reach another group's resources. You can only grant permissions you hold yourself for this group.","responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupLeaderPermissions"}}}},"400":{"description":"Bad Request - Unknown permission"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupLeaderPermissions"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{groupSlug}/member-permissions":{"get":{"operationId":"getGroupMemberPermissions","tags":["positions"],"summary":"Get the permissions held by every member of the group","description":"`permissions` is held by every member scoped to this group; `globalPermissions` is held by every member across all of TIHLDE. Requires the same rights as managing the group's verv.","responses":{"200":{"description":"The group's member permissions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupMemberPermissions"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]},"patch":{"operationId":"updateGroupMemberPermissions","tags":["positions"],"summary":"Set the permissions held by every member of the group","description":"Replace both lists. `permissions` is granted scoped to this group; `globalPermissions` is granted across all of TIHLDE and therefore requires holding each of them globally yourself.","responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupMemberPermissions"}}}},"400":{"description":"Bad Request - Unknown permission"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupMemberPermissions"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/groups/{slug}/forms":{"post":{"operationId":"createGroupForm","tags":["groups","forms"],"summary":"Create group form","description":"Create a form for a group. Requires group leader permission or forms:create permission.","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGroupFormResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGroupForm"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]},"get":{"operationId":"listGroupForms","tags":["groups","forms"],"summary":"List group forms","description":"Get all forms for a group, filtered by user permissions","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupFormList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}]}},"/api/institutes":{"get":{"operationId":"listInstitutes","tags":["institutes"],"summary":"List institutes","description":"The NTNU institutes TIHLDE's study programmes run under. Used to pick which institute an event is reserved for. Reference data — public and unpaginated, since there are only ever a couple of them.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstituteList"}}}}}}},"/api/contracts/active":{"get":{"operationId":"getActiveContract","tags":["contracts"],"summary":"Get active contract","description":"Returns the currently active volunteer contract with a direct download URL for the PDF.","responses":{"200":{"description":"Active contract","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveContract"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - No active contract"}}}},"/api/contracts/my-signature":{"get":{"operationId":"getMySignatureStatus","tags":["contracts"],"summary":"Get my signature status","description":"Returns whether the authenticated user has signed the currently active contract.","responses":{"200":{"description":"Signature status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignatureStatus"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/contracts/sign":{"post":{"operationId":"signContract","tags":["contracts"],"summary":"Sign the active contract","description":"Signs the active volunteer contract for the authenticated user.\n\nThe signature PNG is stamped onto the contract at the placement chosen when the\ncontract was uploaded, and the resulting PDF is stored as the signed document.\nBoth the signature and the signed PDF are private assets — fetch the signed PDF\nvia GET /api/contracts/signed-pdf, never the public asset route.\n\nSends email to notification contacts of all groups the user belongs to that\nrequire contract signing.","responses":{"201":{"description":"Contract signed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignContractResponse"}}}},"400":{"description":"Bad Request - Invalid signature image"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - No active contract"},"409":{"description":"Already signed"},"422":{"description":"The active contract has no signature field placed"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignContract"}}}}}},"/api/contracts/signed-pdf":{"get":{"operationId":"getMySignedContractPdf","tags":["contracts"],"summary":"Download my signed contract","description":"Streams the authenticated user's signed copy of the active contract, with their signature stamped in.","responses":{"200":{"description":"The signed contract PDF"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - No active contract, or not signed yet"}}}},"/api/contracts/signed-pdf/{userId}":{"get":{"operationId":"getUserSignedContractPdf","tags":["contracts"],"summary":"Download a member's signed contract","description":"Streams a member's signed copy of the active contract. Requires 'contracts:manage' permission.","responses":{"200":{"description":"The signed contract PDF"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - No active contract, or not signed yet"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"userId","required":true}]}},"/api/contracts":{"get":{"operationId":"listContracts","tags":["contracts"],"summary":"List all contract versions","description":"Returns all contract versions newest first. Requires 'contracts:view' permission.","responses":{"200":{"description":"List of contracts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}},"post":{"operationId":"createContract","tags":["contracts"],"summary":"Create a new contract version","description":"Registers a new contract version. Does not auto-activate. Upload the PDF via POST /api/assets first, then pass the returned fileKey here. Requires 'contracts:create' permission.","responses":{"201":{"description":"Contract created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contract"}}}},"400":{"description":"Bad Request"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContract"}}}}}},"/api/contracts/{id}/activate":{"patch":{"operationId":"activateContract","tags":["contracts"],"summary":"Activate a contract version","description":"Sets the specified contract as active; deactivates all others. Requires 'contracts:update' permission.","responses":{"200":{"description":"Contract activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivateContractResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Contract not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/contracts/groups/{groupSlug}/signatures":{"get":{"operationId":"getGroupContractSignatures","tags":["contracts"],"summary":"Get member signing status for a group","description":"Returns all group members with their signing status for the active contract. Requires being a group leader or 'contracts:view' permission.","responses":{"200":{"description":"Member signing status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupSignatureList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Group not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true}]}},"/api/contracts/groups/{groupSlug}/signatures/{userId}":{"delete":{"operationId":"revokeContractSignature","tags":["contracts"],"summary":"Revoke a member's contract signature","description":"Removes a member's signature from the active contract. Requires being a group leader or 'contracts:manage' permission.","responses":{"200":{"description":"Signature revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeSignatureResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Signature not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"groupSlug","required":true},{"schema":{"type":"string"},"in":"path","name":"userId","required":true}]}},"/api/motetid/events":{"get":{"operationId":"listMotetidEvents","tags":["motetid"],"summary":"List your scheduling events","description":"Every scheduling event the authenticated member created or participates in, newest first.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MotetidEventList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}},"post":{"operationId":"createMotetidEvent","tags":["motetid"],"summary":"Create a scheduling event","description":"Create a when2meet-style scheduling event with a daily time window and either candidate dates (a one-off activity) or candidate weekdays (a recurring weekly slot). Returns the share-link slug.","responses":{"201":{"description":"Event created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMotetidEventResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMotetidEvent"}}}}}},"/api/motetid/events/{slug}":{"get":{"operationId":"getMotetidEvent","tags":["motetid"],"summary":"Get a scheduling event by slug","description":"The full availability board for a scheduling event: participants with their slot selections. Publicly readable via the share link; authentication only enriches the viewer info.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MotetidEvent"}}}},"404":{"description":"Not Found - Event not found"}},"parameters":[{"in":"path","name":"slug","schema":{"type":"string","minLength":1,"maxLength":12},"required":true,"description":"Short share-link slug for the event"}]},"delete":{"operationId":"deleteMotetidEvent","tags":["motetid"],"summary":"Delete a scheduling event","description":"Delete a scheduling event you created. Participants and slots are removed with it.","responses":{"200":{"description":"Event deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteMotetidEventResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event not found"}},"parameters":[{"in":"path","name":"slug","schema":{"type":"string","minLength":1,"maxLength":12},"required":true,"description":"Short share-link slug for the event"}]}},"/api/motetid/events/{slug}/availability":{"put":{"operationId":"saveMotetidAvailability","tags":["motetid"],"summary":"Save your availability","description":"Replace the authenticated member's full slot selection for a scheduling event. Rejected after the event's deadline.","responses":{"200":{"description":"Availability saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveMotetidAvailabilityResponse"}}}},"400":{"description":"Bad Request - A slot references a column not on the board"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event not found"}},"parameters":[{"in":"path","name":"slug","schema":{"type":"string","minLength":1,"maxLength":12},"required":true,"description":"Short share-link slug for the event"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveMotetidAvailability"}}}}}},"/api/motetid/google/connect":{"get":{"operationId":"motetidGoogleConnect","tags":["motetid"],"summary":"Start Google Calendar OAuth","description":"Build a Google OAuth consent URL (calendar.readonly) for the authenticated member. The frontend navigates the browser to the returned URL; Google redirects back to the callback endpoint.","responses":{"200":{"description":"Consent URL built","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MotetidGoogleConnectResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"returnTo","schema":{"type":"string"},"description":"Relative path to return to after OAuth"}]}},"/api/motetid/google/callback":{"get":{"operationId":"motetidGoogleCallback","tags":["motetid"],"summary":"Google Calendar OAuth callback","description":"Handles the redirect back from Google: verifies the signed state, exchanges the code for tokens, stores them, and redirects the browser back to the website.","responses":{"302":{"description":"Redirect back to the website"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/motetid/events/{slug}/sync-calendar":{"post":{"operationId":"motetidSyncCalendar","tags":["motetid"],"summary":"Sync Google Calendar busy times","description":"Fetch the authenticated member's primary Google Calendar over the event's date range and return which grid slots overlap busy events. Refreshes the access token automatically when expired. Weekday-mode boards have no calendar dates, so they always return empty.","responses":{"200":{"description":"Busy slots computed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MotetidSyncCalendarResponse"}}}},"400":{"description":"Google credential missing or invalid — reconnect required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MotetidSyncCalendarError"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Event not found"}},"parameters":[{"in":"path","name":"slug","schema":{"type":"string","minLength":1,"maxLength":12},"required":true,"description":"Short share-link slug for the event"}]}},"/api/news":{"post":{"operationId":"createNews","tags":["news"],"summary":"Create news article","description":"Create a new news article. Requires 'news:create' or 'news:manage', held globally or for any single group.","responses":{"201":{"description":"News article created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsArticle"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateNews"}}}}},"get":{"operationId":"listNews","tags":["news"],"summary":"List news articles","description":"Get a paginated list of all news articles. Public endpoint.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsList"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"}]}},"/api/news/{id}":{"get":{"operationId":"getNews","tags":["news"],"summary":"Get news article","description":"Get a single news article by ID. Public endpoint.","responses":{"200":{"description":"News article details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsArticle"}}}},"404":{"description":"Not Found - News article not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"patch":{"operationId":"updateNews","tags":["news"],"summary":"Update news article","description":"Update a news article. Requires 'news:update' or 'news:manage', held globally or for any single group, or being the creator.","responses":{"200":{"description":"News article updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsArticle"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - News article not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"News article ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateNews"}}}}},"delete":{"operationId":"deleteNews","tags":["news"],"summary":"Delete news article","description":"Delete a news article. Requires 'news:delete' or 'news:manage', held globally or for any single group, or being the creator.","responses":{"200":{"description":"News article deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteNewsResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - News article not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"News article ID"}]}},"/api/news/{id}/reactions":{"post":{"operationId":"createNewsReaction","tags":["news"],"summary":"Add reaction to news","description":"Add or update emoji reaction to a news article. Requires authentication.","responses":{"201":{"description":"Reaction added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsReaction"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - News article not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"News article ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateNewsReaction"}}}}},"delete":{"operationId":"deleteNewsReaction","tags":["news"],"summary":"Remove reaction from news","description":"Remove your emoji reaction from a news article.","responses":{"200":{"description":"Reaction removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteNewsReactionResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - News article or reaction not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"News article ID"}]}},"/api/toddel":{"get":{"operationId":"listToddel","tags":["toddel"],"summary":"List TÖDDEL issues","description":"Every issue of the student magazine, newest first. Public endpoint. The archive is small and rarely changes, so it is returned unpaginated.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToddelList"}}}}}},"post":{"operationId":"createToddel","tags":["toddel"],"summary":"Publish a TÖDDEL issue","description":"Add an issue to the archive. Upload the PDF (and the cover, if there is one) via POST /api/assets first, then pass the returned keys here. Requires 'toddel:create' or 'toddel:manage', held globally or for any single group.","responses":{"201":{"description":"Issue published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToddelIssue"}}}},"400":{"description":"Bad Request - Unknown asset key"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"409":{"description":"Conflict - that edition already exists"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateToddel"}}}}}},"/api/toddel/{edition}":{"patch":{"operationId":"updateToddel","tags":["toddel"],"summary":"Update a TÖDDEL issue","description":"Change the title, date or files of a published issue. Pass a new asset key to replace a file; leaving it out keeps the current one. Requires 'toddel:update' or 'toddel:manage', held globally or for any single group.","responses":{"200":{"description":"Issue updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToddelIssue"}}}},"400":{"description":"Bad Request - Unknown asset key"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Issue not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateToddel"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"edition","required":true}]},"delete":{"operationId":"deleteToddel","tags":["toddel"],"summary":"Remove a TÖDDEL issue","description":"Take an issue out of the archive. The uploaded files are left in storage — a wrongly deleted issue can then be restored by re-creating it with the same keys. Requires 'toddel:delete' or 'toddel:manage', held globally or for any single group.","responses":{"200":{"description":"Issue removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteToddelResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Issue not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"edition","required":true}]}},"/api/qr-codes":{"get":{"operationId":"listQRCodes","tags":["qr-codes"],"summary":"List your QR codes","description":"Every QR code the authenticated member has created, newest first. A member only ever sees their own codes, so there is nothing to page through.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QRCodeList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}},"post":{"operationId":"createQRCode","tags":["qr-codes"],"summary":"Create a QR code","description":"Create a QR code owned by the authenticated member. The code itself is rendered client side from the stored content.","responses":{"201":{"description":"QR code created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QRCode"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateQRCode"}}}}}},"/api/qr-codes/{id}":{"delete":{"operationId":"deleteQRCode","tags":["qr-codes"],"summary":"Delete a QR code","description":"Delete one of your own QR codes. Someone else's code reads as not found rather than forbidden, so the endpoint does not confirm that an id exists.","responses":{"200":{"description":"QR code deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteQRCodeResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - QR code not found"}},"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true,"description":"QR code ID"}]}},"/api/roles":{"get":{"operationId":"listRoles","tags":["roles"],"summary":"List roles","description":"List all RBAC roles with their permissions and assigned users, ordered by hierarchy position (highest first).","responses":{"200":{"description":"List of roles","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}},"post":{"operationId":"createRole","tags":["roles"],"summary":"Create a role","description":"Create a new RBAC role. The role is positioned directly below the creator's highest role. The creator can only grant permissions they hold themselves.","responses":{"201":{"description":"Role created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"400":{"description":"Bad Request - Invalid input or duplicate name"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRole"}}}}}},"/api/roles/{roleId}":{"patch":{"operationId":"updateRole","tags":["roles"],"summary":"Update a role","description":"Update a role's name, description or permissions. Hierarchy applies: you can only edit roles strictly below your own highest role, and only grant permissions you hold yourself.","responses":{"200":{"description":"Role updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"400":{"description":"Bad Request - Invalid input"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Role not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRole"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"roleId","required":true}]},"delete":{"operationId":"deleteRole","tags":["roles"],"summary":"Delete a role","description":"Delete a role. Hierarchy applies: you can only delete roles strictly below your own highest role. Assignments are removed with it.","responses":{"200":{"description":"Role deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleMessage"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Role not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"roleId","required":true}]}},"/api/roles/{roleId}/users":{"post":{"operationId":"assignRole","tags":["roles"],"summary":"Assign a role to a user","description":"Assign a role to a user. Hierarchy applies: you can only assign roles strictly below your own highest role.","responses":{"200":{"description":"Role assigned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleMessage"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Role or user not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignRole"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"roleId","required":true}]}},"/api/roles/{roleId}/users/{userId}":{"delete":{"operationId":"unassignRole","tags":["roles"],"summary":"Remove a role from a user","description":"Remove a role from a user. Hierarchy applies: you can only remove roles strictly below your own highest role.","responses":{"200":{"description":"Role removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleMessage"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Role not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"roleId","required":true},{"schema":{"type":"string"},"in":"path","name":"userId","required":true}]}},"/api/jobs":{"post":{"operationId":"createJob","tags":["jobs"],"summary":"Create job posting","description":"Create a new job posting. Requires 'jobs:create' or 'jobs:manage', held globally or for any single group.","responses":{"201":{"description":"Job posting created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobDetail"}}}},"400":{"description":"Bad Request - Invalid input"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJob"}}}}},"get":{"operationId":"listJobs","tags":["jobs"],"summary":"List job postings","description":"Get a paginated list of job postings. Supports search, job type, year of study, and expired filtering. Public endpoint.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobList"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term to filter by title or company name"},{"in":"query","name":"expired","schema":{"type":"boolean"},"description":"Include expired job postings (default: false)"},{"in":"query","name":"jobType","schema":{"type":"string","enum":["full_time","part_time","summer_job","other"]},"description":"Filter by job type"},{"in":"query","name":"year","schema":{"type":"string","enum":["first","second","third","fourth","fifth","alumni"]},"description":"Filter by year of study (returns jobs targeting that class)"}]}},"/api/jobs/{id}":{"get":{"operationId":"getJob","tags":["jobs"],"summary":"Get job posting","description":"Get a single job posting by ID. Public endpoint.","responses":{"200":{"description":"Job posting details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobDetail"}}}},"404":{"description":"Not Found - Job posting not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"patch":{"operationId":"updateJob","tags":["jobs"],"summary":"Update job posting","description":"Update a job posting. Requires 'jobs:update' or 'jobs:manage', held globally or for any single group, or being the creator.","responses":{"200":{"description":"Job posting updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobDetail"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Job posting not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateJob"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"delete":{"operationId":"deleteJob","tags":["jobs"],"summary":"Delete job posting","description":"Delete a job posting. Requires 'jobs:delete' or 'jobs:manage', held globally or for any single group, or being the creator.","responses":{"200":{"description":"Job posting deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteJobResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - Job posting not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/user/me/calendar":{"get":{"operationId":"getCalendarSubscription","tags":["users"],"summary":"Get my calendar subscription URL","description":"Retrieve the personal iCalendar subscription URL for the authenticated user's event registrations. The URL is created on first request.","responses":{"200":{"description":"Calendar subscription URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarSubscription"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/user/me/calendar/regenerate":{"post":{"operationId":"regenerateCalendarSubscription","tags":["users"],"summary":"Regenerate my calendar subscription URL","description":"Issue a new personal iCalendar subscription URL. The previous URL stops working immediately.","responses":{"200":{"description":"New calendar subscription URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarSubscription"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/user/me/settings":{"get":{"operationId":"getUserSettings","tags":["users"],"summary":"Get current user settings","description":"Retrieve the authenticated user's settings including preferences and allergies.","responses":{"200":{"description":"User settings retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSettings"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User settings do not exist (user needs to complete onboarding)"}}},"post":{"operationId":"onboardUser","tags":["users"],"summary":"Complete user onboarding","description":"Create initial user settings and mark the user as onboarded. Can only be called once per user.","responses":{"201":{"description":"User onboarded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSettingsBase"}}}},"400":{"description":"Bad Request - User has already completed onboarding"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardUserInput"}}}}},"patch":{"operationId":"updateUserSettings","tags":["users"],"summary":"Update user settings","description":"Partially update the authenticated user's settings. Only provided fields will be updated. Settings are created on first update for users who have not onboarded, so a member can accept the event rules without filling in a full profile.","responses":{"200":{"description":"Settings updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserSettings"}}}},"400":{"description":"Bad Request - Invalid input"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserSettingsInput"}}}}}},"/api/user/me/password":{"post":{"operationId":"setPassword","tags":["users"],"summary":"Set a password for an account that has none","description":"Adds a username/password login to the authenticated user, for accounts created through Feide. Refuses if the account already has a password — use the password reset flow to change an existing one.","responses":{"200":{"description":"Password set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordResponse"}}}},"400":{"description":"Bad Request - Invalid password"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"409":{"description":"The account already has a password"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordInput"}}}}}},"/api/user/me/unanswered-evaluations":{"get":{"operationId":"listUnansweredEvaluations","tags":["users","forms"],"summary":"List the member's unanswered evaluations","description":"Evaluation forms for events the member attended but never answered. These block registration for new events.","responses":{"200":{"description":"Unanswered evaluations, newest event first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnansweredEvaluations"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}}}},"/api/user/allergy":{"get":{"operationId":"listAllergies","tags":["users"],"summary":"List all allergies","description":"Retrieve a list of all possible allergies that users can have.","responses":{"200":{"description":"List of allergies retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllergyList"}}}}}}},"/api/user/register":{"post":{"operationId":"registerUser","tags":["users"],"summary":"Register a member from another TIHLDE service","description":"Creates an account the same way the website's sign-up does — verification mail sent — and enrols the member in a study programme. The username is derived from the @stud.ntnu.no address, unless the caller names one, which also lifts the requirement that the address be a stud one. Requires an API key with 'users:create'.","responses":{"201":{"description":"Member created; a verification mail has been sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterUser"}}}},"400":{"description":"Bad Request - Address is not @stud.ntnu.no and no username was given, password too short, or the study programme does not exist"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Forbidden - Requires an API key with 'users:create'"},"409":{"description":"Conflict - a member already holds this NTNU username or address; they should log in rather than register"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterUserInput"}}}}}},"/api/user/search":{"get":{"operationId":"searchUsers","tags":["users"],"summary":"Search users","description":"Search users by name or username (case-insensitive substring). Used by admin UIs to assign roles and positions. Requires 'users:view' or 'roles:assign' — or, when 'groupSlug' is given, the right to manage that group's roster ('groups:manage' scoped to it, or being its leader).","responses":{"200":{"description":"Matching users","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSearchResult"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"q","schema":{"type":"string","minLength":2,"maxLength":100},"required":true,"description":"Search term (name or username)"},{"in":"query","name":"groupSlug","schema":{"type":"string"},"description":"Group the search is for. Lets that group's leader (or anyone with 'groups:manage' for it) search users in order to add members."}]}},"/api/user":{"get":{"operationId":"listUsers","tags":["users"],"summary":"List users","description":"Paginated list of every user, with optional name/username search and study filters. Used by the admin user overview. Requires 'users:view'.","responses":{"200":{"description":"Paginated users","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}}},"parameters":[{"in":"query","name":"pageSize","schema":{"default":50,"type":"number","minimum":1,"maximum":100},"description":"Number of items to return"},{"in":"query","name":"page","schema":{"default":0,"type":"number","minimum":0},"description":"Number of items to skip"},{"in":"query","name":"search","schema":{"type":"string","minLength":1,"maxLength":100},"description":"Filter by name or username (substring)"},{"in":"query","name":"study","schema":{"type":"string","maxLength":128},"description":"Slug of a STUDY group to filter by, or \"none\" for users without one"},{"in":"query","name":"studyStartYear","schema":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"Filter by cohort (STUDYYEAR group), e.g. 2023"},{"in":"query","name":"approvalStatus","schema":{"type":"string","enum":["pending","approved"]},"description":"Show only accounts with this approval status. 'pending' is the queue of self-registered accounts waiting for an admin."}]}},"/api/user/{id}/study-year":{"patch":{"operationId":"updateUserStudyYear","tags":["users"],"summary":"Correct a member's cohort","description":"Set a member's cohort start year by hand, overriding what Feide reports. Moves their STUDYYEAR group to match. Requires 'users:manage'.","responses":{"200":{"description":"Cohort updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStudyYear"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStudyYearInput"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/user/{id}/study":{"patch":{"operationId":"updateUserStudy","tags":["users"],"summary":"Correct a member's study programme","description":"Move a member to another STUDY group, replacing the one they have. Null removes it. Requires 'users:manage'.","responses":{"200":{"description":"Study programme updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStudy"}}}},"400":{"description":"Bad Request - No STUDY group with that slug"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStudyInput"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/user/{id}/allergies":{"get":{"operationId":"getUserAllergies","tags":["users"],"summary":"Get a member's allergies","description":"The allergies registered on one member. Requires 'users:manage'.","responses":{"200":{"description":"The member's allergies","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserAllergies"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"put":{"operationId":"updateUserAllergies","tags":["users"],"summary":"Set a member's allergies","description":"Replace the allergies registered on one member. Requires 'users:manage'.","responses":{"200":{"description":"The allergies now registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserAllergies"}}}},"400":{"description":"Bad Request - Unknown allergy slug"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserAllergiesInput"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/user/{id}/status":{"patch":{"operationId":"updateUserStatus","tags":["users"],"summary":"Activate or deactivate a member","description":"Deactivating blocks sign-in and ends the member's sessions; the account and its history are kept. Requires 'users:manage'.","responses":{"200":{"description":"Status updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserStatus"}}}},"400":{"description":"Bad Request - Cannot deactivate your own account"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User not found"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserStatusInput"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/user/{id}/approve":{"post":{"operationId":"approveUser","tags":["users"],"summary":"Approve a self-registered account","description":"Grants the 'member' role to an account that signed itself up on the website and is waiting for approval, and tells the person by e-mail. Requires 'users:manage'.","responses":{"200":{"description":"Account approved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApproveUser"}}}},"400":{"description":"Bad Request - The account is not waiting for approval"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/api/user/{id}":{"delete":{"operationId":"deleteUser","tags":["users"],"summary":"Delete a member permanently","description":"Irreversibly deletes the account along with its registrations, payments, fines, memberships and history. Prefer 'PATCH /user/{id}/status' to deactivate instead. Requires 'users:delete'.","responses":{"204":{"description":"User deleted"},"400":{"description":"Bad Request - Cannot delete your own account"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"get":{"operationId":"getUserProfile","tags":["users"],"summary":"Get user profile","description":"Public profile of a single user: name, bio, links, study programme and group memberships. Requires being signed in.","responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfile"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"403":{"description":"Kontoen din venter på godkjenning fra en administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPAppException"}}}},"404":{"description":"Not Found - User not found"}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}}},"components":{"schemas":{"CreateApiKeyResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$","description":"API key ID"},"name":{"type":"string","description":"Name of the API key"},"description":{"type":"string","description":"Description of the API key"},"keyPrefix":{"type":"string","description":"First 12 characters of the key for display"},"createdById":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User ID who created this key"},"lastUsedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"When the key was last used"},"permissions":{"type":"array","items":{"type":"string"},"description":"Array of permissions granted to this API key"},"metadata":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"Optional metadata as key-value pairs"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the key was created"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the key was last updated"},"key":{"type":"string","description":"The full API key - ONLY returned on creation and regeneration. Cannot be retrieved again."}},"required":["id","name","description","keyPrefix","createdById","lastUsedAt","permissions","metadata","createdAt","updatedAt","key"]},"HTTPAppException":{"type":"object","properties":{"status":{"type":"number","description":"The HTTP status code","example":401},"message":{"type":"string","description":"The error message"},"meta":{"description":"Additional metadata about the error"}},"required":["status","message"]},"CreateApiKey":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Name for the API key"},"description":{"type":"string","minLength":1,"maxLength":500,"description":"Detailed description of the API key's purpose"},"permissions":{"minItems":1,"type":"array","items":{"type":"string"},"description":"Array of permissions granted to this API key (e.g., ['email:send', 'news:create']). Service validates against allowed permissions."},"metadata":{"description":"Optional metadata as key-value pairs","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name","description","permissions"]},"ApiKey":{"description":"The API key details and permissions if valid. Undefined if invalid.","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$","description":"API key ID"},"name":{"type":"string","description":"Name of the API key"},"description":{"type":"string","description":"Description of the API key"},"keyPrefix":{"type":"string","description":"First 12 characters of the key for display"},"createdById":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User ID who created this key"},"lastUsedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"When the key was last used"},"permissions":{"type":"array","items":{"type":"string"},"description":"Array of permissions granted to this API key"},"metadata":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"Optional metadata as key-value pairs"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the key was created"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the key was last updated"}},"required":["id","name","description","keyPrefix","createdById","lastUsedAt","permissions","metadata","createdAt","updatedAt"]},"ApiKeyList":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}},"UpdateApiKey":{"type":"object","properties":{"name":{"description":"Name for the API key","type":"string","minLength":1,"maxLength":100},"description":{"description":"Detailed description of the API key's purpose","type":"string","minLength":1,"maxLength":500},"permissions":{"description":"Array of permissions granted to this API key","minItems":1,"type":"array","items":{"type":"string"}},"metadata":{"description":"Optional metadata as key-value pairs","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"DeleteApiKeyResponse":{"type":"object","properties":{"message":{"type":"string","description":"Success message"}},"required":["message"]},"ValidateApiKeyResponse":{"type":"object","properties":{"valid":{"type":"boolean","description":"Whether the API key is valid"},"apiKey":{"$ref":"#/components/schemas/ApiKey"}},"required":["valid"]},"ValidateApiKeyInput":{"type":"object","properties":{"key":{"type":"string","minLength":1,"description":"The API key to validate"}},"required":["key"]},"ApplicationOptions":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"}},"required":["slug","name"]},"description":"Groups selectable as the applying group"},"ccOptions":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["slug","name","email"]},"description":"Groups with an økonomiansvarlig address"},"budgetTypes":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","enum":["social_budget","group_budget","approved_hs_application","approved_idkom_application"]},"label":{"type":"string"}},"required":["value","label"]}},"caseTypes":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","enum":["discussion","decision","orientation"]},"label":{"type":"string"}},"required":["value","label"]}}},"required":["groups","ccOptions","budgetTypes","caseTypes"]},"ApplicationSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"type":{"type":"string","enum":["expense","support","sports_support","hs_case","company_contact"]},"typeLabel":{"type":"string","description":"Norwegian type label"},"status":{"type":"string","enum":["new","in_progress","approved","rejected"]},"statusLabel":{"type":"string","description":"Norwegian status label"},"title":{"type":"string","description":"Short human title — the case name, or the group and amount"},"contactName":{"type":"string"},"contactEmail":{"type":"string"},"submittedById":{"anyOf":[{"type":"string"},{"type":"null"}]},"submittedByName":{"anyOf":[{"type":"string"},{"type":"null"}]},"amountNok":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Amount for expense/support types, otherwise null"},"groupName":{"anyOf":[{"type":"string"},{"type":"null"}]},"hasPdf":{"type":"boolean"},"handledByName":{"anyOf":[{"type":"string"},{"type":"null"}]},"handledAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","type","typeLabel","status","statusLabel","title","contactName","contactEmail","submittedById","submittedByName","amountNok","groupName","hasPdf","handledByName","handledAt","createdAt","updatedAt"]},"ApplicationList":{"type":"array","items":{"$ref":"#/components/schemas/ApplicationSummary"}},"CreateApplicationResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"pdfGenerated":{"type":"boolean","description":"False when PDF rendering failed; the submission was still saved and an admin can regenerate it."}},"required":["id","pdfGenerated"]},"CreateExpenseApplication":{"type":"object","properties":{"contactName":{"type":"string","minLength":1,"maxLength":200,"description":"Full name of the person submitting"},"contactEmail":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Contact email address"},"ccEmail":{"description":"Optional copy to a group's økonomiansvarlig. Must match a group's registered finance address.","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"amountNok":{"type":"number","exclusiveMinimum":0,"maximum":1000000,"multipleOf":0.01,"description":"Amount in NOK, at most two decimals (øre)"},"expenseDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Date of the expense, YYYY-MM-DD"},"groupSlug":{"type":"string","minLength":1,"description":"Slug of the group the expense belongs to"},"budgetType":{"type":"string","enum":["social_budget","group_budget","approved_hs_application","approved_idkom_application"],"description":"Which budget the expense is drawn from"},"description":{"type":"string","minLength":1,"maxLength":5000,"description":"What the expense is for"},"accountNumber":{"type":"string","pattern":"^\\d{4}\\.\\d{2}\\.\\d{5}$","description":"Norwegian account number, xxxx.xx.xxxxx"},"attachmentKeys":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string","minLength":1},"description":"Receipt images, at least one"}},"required":["contactName","contactEmail","amountNok","expenseDate","groupSlug","budgetType","description","accountNumber","attachmentKeys"]},"CreateSupportApplication":{"type":"object","properties":{"contactName":{"type":"string","minLength":1,"maxLength":200,"description":"Full name of the person submitting"},"contactEmail":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Contact email address"},"groupSlug":{"type":"string","minLength":1,"description":"Slug of the group applying"},"purpose":{"type":"string","minLength":1,"maxLength":2000,"description":"Purpose of the application"},"eventDescription":{"type":"string","minLength":1,"maxLength":5000,"description":"Description of the event or product"},"justification":{"type":"string","minLength":1,"maxLength":5000,"description":"Why the support is needed"},"totalAmountNok":{"type":"integer","exclusiveMinimum":0,"maximum":10000000,"description":"Total amount applied for, in whole NOK"},"budgetLink":{"description":"Optional link to a budget document","type":"string","format":"uri"},"summary":{"description":"Optional closing summary","type":"string","maxLength":5000},"attachmentKeys":{"maxItems":10,"type":"array","items":{"type":"string","minLength":1},"description":"Budget documentation images"}},"required":["contactName","contactEmail","groupSlug","purpose","eventDescription","justification","totalAmountNok","attachmentKeys"]},"CreateHsCaseApplication":{"type":"object","properties":{"contactName":{"type":"string","minLength":1,"maxLength":200,"description":"Full name of the person submitting"},"contactEmail":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Contact email address"},"caseName":{"type":"string","minLength":1,"maxLength":300,"description":"Name of the case"},"caseType":{"type":"string","enum":["discussion","decision","orientation"],"description":"Kind of case"},"background":{"type":"string","minLength":1,"maxLength":10000,"description":"Background for the case"},"assessment":{"type":"string","minLength":1,"maxLength":10000,"description":"The case handler's assessment"},"recommendation":{"description":"The case handler's recommendation. Required for a Vedtakssak.","type":"string","maxLength":10000},"attachmentKeys":{"maxItems":10,"type":"array","items":{"type":"string","minLength":1},"description":"Optional attachments"}},"required":["contactName","contactEmail","caseName","caseType","background","assessment","attachmentKeys"]},"RegenerateApplicationPdfResponse":{"type":"object","properties":{"pdfAssetKey":{"type":"string"}},"required":["pdfAssetKey"]},"ApplicationDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"type":{"type":"string","enum":["expense","support","sports_support","hs_case","company_contact"]},"typeLabel":{"type":"string","description":"Norwegian type label"},"status":{"type":"string","enum":["new","in_progress","approved","rejected"]},"statusLabel":{"type":"string","description":"Norwegian status label"},"title":{"type":"string","description":"Short human title — the case name, or the group and amount"},"contactName":{"type":"string"},"contactEmail":{"type":"string"},"submittedById":{"anyOf":[{"type":"string"},{"type":"null"}]},"submittedByName":{"anyOf":[{"type":"string"},{"type":"null"}]},"amountNok":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Amount for expense/support types, otherwise null"},"groupName":{"anyOf":[{"type":"string"},{"type":"null"}]},"hasPdf":{"type":"boolean"},"handledByName":{"anyOf":[{"type":"string"},{"type":"null"}]},"handledAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"signature":{"type":"string"},"internalComment":{"anyOf":[{"type":"string"},{"type":"null"}]},"attachments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"assetKey":{"type":"string"},"kind":{"type":"string","enum":["receipt","budget","attachment"]},"sortOrder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","assetKey","kind","sortOrder"]}},"expense":{"anyOf":[{"type":"object","properties":{"ccEmail":{"anyOf":[{"type":"string"},{"type":"null"}]},"amountNok":{"type":"number"},"expenseDate":{"type":"string"},"budgetType":{"type":"string","enum":["social_budget","group_budget","approved_hs_application","approved_idkom_application"]},"budgetTypeLabel":{"type":"string"},"description":{"type":"string"},"accountNumber":{"type":"string"},"group":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"}},"required":["slug","name"]}},"required":["ccEmail","amountNok","expenseDate","budgetType","budgetTypeLabel","description","accountNumber","group"]},{"type":"null"}]},"support":{"anyOf":[{"type":"object","properties":{"purpose":{"type":"string"},"eventDescription":{"type":"string"},"justification":{"type":"string"},"totalAmountNok":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"budgetLink":{"anyOf":[{"type":"string"},{"type":"null"}]},"summary":{"anyOf":[{"type":"string"},{"type":"null"}]},"group":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"}},"required":["slug","name"]}},"required":["purpose","eventDescription","justification","totalAmountNok","budgetLink","summary","group"]},{"type":"null"}]},"hsCase":{"anyOf":[{"type":"object","properties":{"caseName":{"type":"string"},"caseType":{"type":"string","enum":["discussion","decision","orientation"]},"caseTypeLabel":{"type":"string"},"background":{"type":"string"},"assessment":{"type":"string"},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["caseName","caseType","caseTypeLabel","background","assessment","recommendation"]},{"type":"null"}]},"companyContact":{"anyOf":[{"type":"object","properties":{"company":{"type":"string"},"eventTypes":{"type":"array","items":{"type":"string"}},"semesters":{"type":"array","items":{"type":"string"}},"comment":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["company","eventTypes","semesters","comment"]},{"type":"null"}]}},"required":["id","type","typeLabel","status","statusLabel","title","contactName","contactEmail","submittedById","submittedByName","amountNok","groupName","hasPdf","handledByName","handledAt","createdAt","updatedAt","signature","attachments","expense","support","hsCase","companyContact"]},"UpdateApplicationStatus":{"type":"object","properties":{"status":{"type":"string","enum":["new","in_progress","approved","rejected"],"description":"New processing status"},"internalComment":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"description":"Handler-only note. Never shown to the submitter."},"messageToSubmitter":{"description":"Optional message included in the decision email sent to the submitter.","type":"string","maxLength":2000}},"required":["status"]},"UploadResponse":{"type":"object","properties":{"key":{"type":"string","description":"The unique key to reference this asset"},"originalFilename":{"type":"string","description":"The original filename"},"contentType":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The MIME type of the file"},"size":{"type":"number","description":"File size in bytes"},"status":{"type":"string","enum":["staged","ready"],"description":"Asset status"}},"required":["key","originalFilename","contentType","size","status"]},"AssetMetadata":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Asset ID"},"key":{"type":"string","description":"The unique key to reference this asset"},"originalFilename":{"type":"string","description":"The original filename"},"contentType":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The MIME type of the file"},"size":{"type":"number","description":"File size in bytes"},"status":{"type":"string","enum":["staged","ready"],"description":"Asset status"},"promotedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"When the asset was promoted"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the asset was created"}},"required":["id","key","originalFilename","contentType","size","status","promotedAt","createdAt"]},"VisibleBanner":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Banner ID"},"title":{"type":"string","description":"Banner headline"},"description":{"type":"string","description":"Short message shown in the banner"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional link for the banner"},"linkText":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional label for the banner link"},"visibleUntil":{"type":"string","description":"Visibility window end (ISO 8601)"}},"required":["id","title","description","url","linkText","visibleUntil"]},"VisibleBannerList":{"type":"array","items":{"$ref":"#/components/schemas/VisibleBanner"},"description":"Banners currently live, newest first"},"Banner":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Banner ID"},"title":{"type":"string","description":"Banner headline"},"description":{"type":"string","description":"Short message shown in the banner"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional link for the banner"},"linkText":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional label for the banner link"},"visibleFrom":{"type":"string","description":"Visibility window start (ISO 8601)"},"visibleUntil":{"type":"string","description":"Visibility window end (ISO 8601)"},"isVisible":{"type":"boolean","description":"Whether the banner is live right now"},"createdById":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Creator user ID"},"createdAt":{"type":"string","description":"Creation time (ISO 8601)"},"updatedAt":{"type":"string","description":"Last update time (ISO 8601)"}},"required":["id","title","description","url","linkText","visibleFrom","visibleUntil","isVisible","createdById","createdAt","updatedAt"]},"BannerList":{"type":"array","items":{"$ref":"#/components/schemas/Banner"},"description":"All banners, newest visibility first"},"CreateBanner":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"Banner headline"},"description":{"type":"string","minLength":1,"maxLength":500,"description":"Short message shown in the banner"},"url":{"description":"Optional link for the banner","type":"string","format":"uri"},"linkText":{"description":"Optional label for the banner link; an arrow is always shown","type":"string","maxLength":100},"visibleFrom":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the banner becomes visible"},"visibleUntil":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the banner stops being visible"}},"required":["title","description","visibleFrom","visibleUntil"]},"UpdateBanner":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","minLength":1,"maxLength":500},"url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"linkText":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}]},"visibleFrom":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"visibleUntil":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}}},"DeleteBannerResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"AccountLinkHelpResponse":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success","message"]},"AccountLinkHelp":{"type":"object","properties":{"feideName":{"type":"string","minLength":1,"maxLength":200,"description":"Name as Feide reported it"},"feideUsername":{"description":"NTNU username from the Feide login","type":"string","maxLength":100},"contactEmail":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Where to reach the member"},"note":{"description":"What they remember about the old account","type":"string","maxLength":1000}},"required":["feideName","contactEmail"]},"AccountLinkSyncResponse":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]},"CompanyContactResponse":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success","message"]},"CompanyContact":{"type":"object","properties":{"company":{"type":"string","minLength":1,"maxLength":200,"description":"Name of the company reaching out"},"contactName":{"type":"string","minLength":1,"maxLength":200,"description":"Name of the person to reply to"},"contactEmail":{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Email address to reply to"},"eventTypes":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":100},"description":"Event formats the company is interested in"},"semesters":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":100},"description":"Semesters the company is available in"},"comment":{"default":"","description":"Free-text message from the company","type":"string","maxLength":5000}},"required":["company","contactName","contactEmail","eventTypes","semesters"]},"SendEmailResponse":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"recipientCount":{"type":"number"}},"required":["success","message","recipientCount"]},"SendCustomEmail":{"type":"object","properties":{"to":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"minItems":1,"type":"array","items":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}],"description":"Recipient email address (string) or list of recipient email addresses (array)"},"subject":{"type":"string","minLength":1,"description":"Email subject line"},"content":{"minItems":1,"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"title","description":"Content block type"},"content":{"type":"string","minLength":1,"description":"The title text to display"}},"required":["type","content"],"title":"Title"},{"type":"object","properties":{"type":{"type":"string","const":"text","description":"Content block type"},"content":{"type":"string","minLength":1,"description":"The paragraph text to display"}},"required":["type","content"],"title":"Text"},{"type":"object","properties":{"type":{"type":"string","const":"button","description":"Content block type"},"text":{"type":"string","minLength":1,"description":"The button label text"},"url":{"type":"string","format":"uri","description":"The URL the button links to"}},"required":["type","text","url"],"title":"Button"}]},"description":"Array of content blocks (title, text, or button) to render in the email"}},"required":["to","subject","content"]},"Strike":{"type":"object","properties":{"id":{"type":"string","description":"Strike ID"},"userId":{"type":"string","description":"User ID who received the strike"},"eventId":{"type":"string","description":"Event the strike is connected to"},"count":{"type":"number","description":"Number of strikes"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Reason for the strike"},"createdAt":{"type":"string","description":"Creation timestamp"},"user":{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User display name"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User profile image URL"}},"required":["id","name","image"],"description":"Public user info for the struck user"},"event":{"type":"object","properties":{"id":{"type":"string","description":"Event ID"},"title":{"type":"string","description":"Event title"},"slug":{"type":"string","description":"Event slug"}},"required":["id","title","slug"],"description":"Event the strike belongs to"}},"required":["id","userId","eventId","count","reason","createdAt","user","event"]},"StrikeList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of strikes"},"pages":{"type":"number","description":"Total number of pages"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Next page number, or null if last page"},"strikes":{"type":"array","items":{"$ref":"#/components/schemas/Strike"}}},"required":["totalCount","pages","nextPage","strikes"]},"MyEventHistory":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of past registrations"},"pages":{"type":"number","description":"Total number of pages"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Next page number, or null if last page"},"events":{"type":"array","items":{"type":"object","properties":{"eventId":{"type":"string","description":"Event ID"},"title":{"type":"string","description":"Event title"},"slug":{"type":"string","description":"Event slug"},"startTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event started"},"status":{"type":"string","enum":["registered","attended","no_show"],"description":"Your registration status for the event"}},"required":["eventId","title","slug","startTime","status"]}}},"required":["totalCount","pages","nextPage","events"]},"MyUpcomingEvents":{"type":"array","items":{"type":"object","properties":{"eventId":{"type":"string","description":"Event ID"},"title":{"type":"string","description":"Event title"},"slug":{"type":"string","description":"Event slug"},"startTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event starts (ISO 8601)"},"endTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event ends (ISO 8601)"},"categorySlug":{"type":"string","description":"Slug of the event's category"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where the event takes place"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cover image URL"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alt text for the cover image"},"organizer":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name of the organizing group"},"status":{"type":"string","enum":["registered","waitlisted","pending"],"description":"Your registration status for the event"},"waitlistPosition":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Your place in the waitlist, or null if you have a spot"}},"required":["eventId","title","slug","startTime","endTime","categorySlug","location","image","imageAlt","organizer","status","waitlistPosition"]}},"CreateStrike":{"type":"object","properties":{"userId":{"type":"string","maxLength":255,"description":"User ID who receives the strike"},"eventId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Event the strike is connected to"},"count":{"default":1,"description":"Number of strikes (1-3)","type":"integer","minimum":1,"maximum":3},"reason":{"description":"Reason for the strike","type":"string","maxLength":256}},"required":["userId","eventId"]},"DeleteStrikeResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"UpdateFavoriteResponse":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]},"UpdateFavoriteEvent":{"type":"object","properties":{"isFavorite":{"type":"boolean","description":"Is favorite"}},"required":["isFavorite"]},"FavoriteEvents":{"type":"array","items":{"type":"object","properties":{"eventId":{"type":"string","description":"Event ID"},"title":{"type":"string","description":"Event title"},"slug":{"type":"string","description":"Event slug"},"startTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event starts (ISO 8601)"},"endTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event ends (ISO 8601)"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When you added this event to your favorites"}},"required":["eventId","title","slug","startTime","endTime","createdAt"]}},"CreateEventResponse":{"type":"object","properties":{"eventId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["eventId"]},"CreateEventSchema":{"type":"object","properties":{"title":{"type":"string","maxLength":256,"description":"Short title of the event"},"description":{"type":"string","description":"Description of the event"},"categorySlug":{"type":"string","description":"Category slug for the event"},"organizerGroupSlug":{"type":"string","description":"Slug of the group organizing the event"},"location":{"type":"string","maxLength":256,"description":"Location of the event (physical or online)"},"locationLat":{"description":"Latitude of the location, when picked from address search","anyOf":[{"type":"number","minimum":-90,"maximum":90},{"type":"null"}]},"locationLng":{"description":"Longitude of the location, when picked from address search","anyOf":[{"type":"number","minimum":-180,"maximum":180},{"type":"null"}]},"imageUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Main image to display for the event"},"imageAlt":{"description":"Alt text for the event image","anyOf":[{"type":"string","maxLength":255},{"type":"null"}]},"start":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event starts"},"end":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event ends"},"registrationStart":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"Timestamp for when registrations open. If null, open immediately."},"registrationEnd":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"When the registration for the event ends. After this time, users cannot sign up. Null for events without sign-up (requiresSigningUp false)."},"cancellationDeadline":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"Deadline timestamp for when users cannot cancel anymore. After this, no-shows may receive strikes."},"capacity":{"anyOf":[{"type":"integer","minimum":1,"maximum":9007199254740991},{"type":"null"}],"description":"Maximum number of participants allowed. If null, no capacity limit."},"isRegistrationClosed":{"type":"boolean","description":"Is the event closed for new registrations? This overrides registrationStart/End"},"requiresSigningUp":{"type":"boolean","description":"Do users need to sign up to attend the event?"},"allowWaitlist":{"type":"boolean","description":"Should users be allowed to join a waitlist if the event is full?"},"priorityPools":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"string"},"description":"Group slugs in this pool"}},"required":["groups"]}},{"type":"null"}],"description":"List of priority pools, with priority in descending order. Each pool contains a list of group slugs. Users in groups in the first pool have highest priority, then second pool, etc. Users not in any pool have lowest priority."},"onlyAllowPrioritized":{"type":"boolean","description":"Only allow users in at least one priority pool to sign up. Can only be true if at least one group is in priorityPools."},"restrictedToInstituteSlug":{"description":"Slug of the NTNU institute this event is reserved for (e.g. 'idi' or 'iik'). Only members of a study group belonging to that institute may sign up. Null — the default — leaves the event open to every institute.","anyOf":[{"type":"string"},{"type":"null"}]},"canCauseStrikes":{"type":"boolean","description":"Can this event give strike penalties to users?"},"enforcesPreviousStrikes":{"type":"boolean","description":"Should the system enforce strike rules for this event?"},"isPaidEvent":{"type":"boolean","description":"Is this a paid event?"},"price":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Price in NOK for attending the event. Can only be set if isPaidEvent is true."},"paymentGracePeriodMinutes":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The time (in minutes) between sign up and payment must be made. After this period, unpaid registrations are cancelled. Can only be set if isPaidEvent is true."},"contactPersonUserId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User ID of the primary contact person for the event"},"reactionsAllowed":{"type":"boolean","description":"Should users be able to react to this event with emojis?"},"visibility":{"default":"public","description":"Who may see the event. 'public' is visible to everyone including logged-out visitors; 'members' hides it from unauthenticated callers.","type":"string","enum":["public","members"]}},"required":["title","description","categorySlug","organizerGroupSlug","location","imageUrl","start","end","registrationStart","registrationEnd","cancellationDeadline","capacity","isRegistrationClosed","requiresSigningUp","allowWaitlist","priorityPools","onlyAllowPrioritized","canCauseStrikes","enforcesPreviousStrikes","isPaidEvent","price","paymentGracePeriodMinutes","contactPersonUserId","reactionsAllowed"]},"EventListItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$","description":"Event ID"},"slug":{"type":"string","description":"Event slug"},"title":{"type":"string","description":"Event title"},"location":{"description":"Event location (nullable)","anyOf":[{"type":"string"},{"type":"null"}]},"startTime":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Event start time (ISO 8601)"},"endTime":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Event end time (ISO 8601)"},"organizer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","description":"Organizer name"},"slug":{"type":"string","description":"Organizer slug"},"type":{"type":"string","description":"Organizer type"}},"required":["name","slug","type"]},{"type":"null"}],"description":"Event organizer (nullable)"},"closed":{"type":"boolean","description":"Is registration closed"},"image":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Event image URL (nullable)"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alt text for the event image (nullable)"},"createdAt":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Event creation time (ISO 8601)"},"updatedAt":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Event update time (ISO 8601)"},"category":{"type":"object","properties":{"slug":{"type":"string","description":"Category slug"},"label":{"type":"string","description":"Category label"}},"required":["slug","label"],"description":"Event category"},"visibility":{"type":"string","enum":["public","members"],"description":"Who may see the event ('public' or 'members'-only)"}},"required":["id","slug","title","startTime","endTime","organizer","closed","image","imageAlt","createdAt","updatedAt","category","visibility"]},"EventList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"items":{"type":"array","items":{"$ref":"#/components/schemas/EventListItem"},"description":"List of events"}},"required":["totalCount","pages","nextPage","items"]},"UpdateEventResponse":{"type":"object","properties":{"eventId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string","description":"The event slug after the update. Changing the title regenerates it, so clients must use this when linking to the event."}},"required":["eventId","slug"]},"UpdateEventSchema":{"type":"object","properties":{"title":{"type":"string","maxLength":256,"description":"Short title of the event"},"description":{"type":"string","description":"Description of the event"},"categorySlug":{"type":"string","description":"Category slug for the event"},"organizerGroupSlug":{"type":"string","description":"Slug of the group organizing the event"},"location":{"type":"string","maxLength":256,"description":"Location of the event (physical or online)"},"locationLat":{"description":"Latitude of the location, when picked from address search","anyOf":[{"type":"number","minimum":-90,"maximum":90},{"type":"null"}]},"locationLng":{"description":"Longitude of the location, when picked from address search","anyOf":[{"type":"number","minimum":-180,"maximum":180},{"type":"null"}]},"imageUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Main image to display for the event"},"imageAlt":{"description":"Alt text for the event image","anyOf":[{"type":"string","maxLength":255},{"type":"null"}]},"start":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event starts"},"end":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the event ends"},"registrationStart":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"Timestamp for when registrations open. If null, open immediately."},"registrationEnd":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"When the registration for the event ends. After this time, users cannot sign up. Null for events without sign-up (requiresSigningUp false)."},"cancellationDeadline":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"Deadline timestamp for when users cannot cancel anymore. After this, no-shows may receive strikes."},"capacity":{"anyOf":[{"type":"integer","minimum":1,"maximum":9007199254740991},{"type":"null"}],"description":"Maximum number of participants allowed. If null, no capacity limit."},"isRegistrationClosed":{"type":"boolean","description":"Is the event closed for new registrations? This overrides registrationStart/End"},"requiresSigningUp":{"type":"boolean","description":"Do users need to sign up to attend the event?"},"allowWaitlist":{"type":"boolean","description":"Should users be allowed to join a waitlist if the event is full?"},"priorityPools":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"string"},"description":"Group slugs in this pool"}},"required":["groups"]}},{"type":"null"}],"description":"List of priority pools, with priority in descending order. Each pool contains a list of group slugs. Users in groups in the first pool have highest priority, then second pool, etc. Users not in any pool have lowest priority."},"onlyAllowPrioritized":{"type":"boolean","description":"Only allow users in at least one priority pool to sign up. Can only be true if at least one group is in priorityPools."},"restrictedToInstituteSlug":{"description":"Slug of the NTNU institute this event is reserved for (e.g. 'idi' or 'iik'). Only members of a study group belonging to that institute may sign up. Null — the default — leaves the event open to every institute.","anyOf":[{"type":"string"},{"type":"null"}]},"canCauseStrikes":{"type":"boolean","description":"Can this event give strike penalties to users?"},"enforcesPreviousStrikes":{"type":"boolean","description":"Should the system enforce strike rules for this event?"},"isPaidEvent":{"type":"boolean","description":"Is this a paid event?"},"price":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Price in NOK for attending the event. Can only be set if isPaidEvent is true."},"paymentGracePeriodMinutes":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The time (in minutes) between sign up and payment must be made. After this period, unpaid registrations are cancelled. Can only be set if isPaidEvent is true."},"contactPersonUserId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User ID of the primary contact person for the event"},"reactionsAllowed":{"type":"boolean","description":"Should users be able to react to this event with emojis?"},"visibility":{"default":"public","description":"Who may see the event. 'public' is visible to everyone including logged-out visitors; 'members' hides it from unauthenticated callers.","type":"string","enum":["public","members"]}}},"DeleteEventResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"Event":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$","description":"Event ID"},"slug":{"type":"string","description":"Event slug"},"title":{"type":"string","description":"Event title"},"description":{"type":"string","description":"Event description, stored as markdown"},"location":{"description":"Event location (nullable)","anyOf":[{"type":"string"},{"type":"null"}]},"locationLat":{"description":"Latitude of the location (nullable)","anyOf":[{"type":"number"},{"type":"null"}]},"locationLng":{"description":"Longitude of the location (nullable)","anyOf":[{"type":"number"},{"type":"null"}]},"startTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"Event start time (ISO 8601)"},"endTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"Event end time (ISO 8601)"},"registrationStart":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"When registration opens (ISO 8601). Null means it is open immediately."},"registrationEnd":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"When registration closes (ISO 8601). Null when the event has no sign-up."},"cancellationDeadline":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"Last moment a registration can be cancelled without a strike (ISO 8601, nullable)"},"organizer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","description":"Organizer name"},"slug":{"type":"string","description":"Organizer slug"},"type":{"type":"string","description":"Organizer type"},"image":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Organizer logo URL"}},"required":["name","slug","type","image"]},{"type":"null"}],"description":"Event organizer (nullable)"},"contactPerson":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"Contact person user ID"},"name":{"type":"string","description":"Contact person name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contact person e-mail. Null for unauthenticated callers, so members' addresses are not exposed to the open internet."}},"required":["id","name","email"]},{"type":"null"}],"description":"Who to ask about the event (nullable). Set with contactPersonUserId."},"closed":{"type":"boolean","description":"Is registration closed"},"requiresSigningUp":{"type":"boolean","description":"Do users need to sign up to attend the event?"},"allowWaitlist":{"type":"boolean","description":"May users join a waitlist when the event is full?"},"capacity":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Maximum number of participants. Null means no capacity limit."},"canCauseStrikes":{"type":"boolean","description":"Can this event give strikes for late cancellation or no-show?"},"registeredCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Number of people signed up (registered, attended or no-show). Public, unlike the roster itself, so the open event page can show how many are going without naming them."},"waitlistCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Number of people on the waitlist. Public for the same reason as registeredCount: how long the queue is tells a member whether signing up is worth it, without naming anyone in it."},"image":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Event image URL (nullable)"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alt text for the event image (nullable)"},"createdById":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Creator user ID. The creator may update and delete their own event, so clients need it to decide whether to offer those actions."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"Event creation time (ISO 8601)"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"Event update time (ISO 8601)"},"category":{"type":"object","properties":{"slug":{"type":"string","description":"Category slug"},"label":{"type":"string","description":"Category label"}},"required":["slug","label"],"description":"Event category"},"reactions":{"type":"array","items":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User name"}},"required":["id","name"]},"emoji":{"type":"string","description":"Reaction emoji"}},"required":["user","emoji"]}},"isPaidEvent":{"type":"boolean","description":"Is this a paid event"},"payInfo":{"anyOf":[{"type":"object","properties":{"price":{"type":"number","description":"Event price in minor units (øre). Note the create/update endpoints take `price` in whole kroner instead."},"paymentGracePeriodMinutes":{"type":"number","description":"Payment grace period in minutes"}},"required":["price","paymentGracePeriodMinutes"]},{"type":"null"}],"description":"Payment info"},"priorityPools":{"type":"array","items":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Group name"},"slug":{"type":"string","description":"Group slug"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group logo URL (nullable)"}},"required":["name","slug","logoUrl"]}}},"required":["groups"]},"description":"Priority registration pools"},"enforcesPreviousStrikes":{"type":"boolean","description":"Does the event enforce previous strikes for registration"},"onlyAllowPrioritized":{"type":"boolean","description":"Only members covered by a priority pool may register"},"restrictedToInstitute":{"anyOf":[{"type":"object","properties":{"slug":{"type":"string","description":"Institute slug"},"shortName":{"type":"string","description":"Short institute name, e.g. 'IDI'"},"name":{"type":"string","description":"Full institute name"}},"required":["slug","shortName","name"]},{"type":"null"}],"description":"The institute this event is reserved for. Only members of a study group under this institute may register. Null means open to every institute."},"visibility":{"type":"string","enum":["public","members"],"description":"Who may see the event ('public' or 'members'-only)"},"registration":{"anyOf":[{"type":"object","properties":{"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the user signed up"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the registration was last updated by the system (moving waitlist position etc.)"},"status":{"type":"string","enum":["registered","waitlisted","cancelled","attended","no_show","pending"]},"waitlistPosition":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The user's position in the waitlist. Is null if not on the waitlist"},"attendedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}],"description":"When the user was registered as an attendee by TIHLDE for this event. Is null if not attended."}},"required":["createdAt","updatedAt","status","waitlistPosition","attendedAt"]},{"type":"null"}],"description":"The current user's registration information. This is null if not registered or if not logged in."}},"required":["id","slug","title","description","startTime","endTime","registrationStart","registrationEnd","cancellationDeadline","organizer","contactPerson","closed","requiresSigningUp","allowWaitlist","capacity","canCauseStrikes","registeredCount","waitlistCount","image","imageAlt","createdById","createdAt","updatedAt","category","reactions","isPaidEvent","payInfo","priorityPools","enforcesPreviousStrikes","onlyAllowPrioritized","restrictedToInstitute","visibility","registration"]},"EventRegistration":{"type":"object","properties":{"eventId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"userId":{"type":"string"},"status":{"type":"string","const":"pending"},"createdAt":{"type":"string"},"allowPhoto":{"type":"boolean","description":"Photo consent for this event"}},"required":["eventId","userId","status","createdAt","allowPhoto"]},"CreateEventRegistrationBody":{"type":"object","properties":{"allowPhoto":{"description":"Photo consent for this event. Omit it to use the account-level allowsPhotosByDefault, which is where members manage their consent.","type":"boolean"}}},"EventRegistrationPayment":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"type":"string","enum":["pending","paid","refunded","failed"]},"amountMinor":{"type":"number"},"currency":{"type":"string"},"expiresAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"receivedPaymentAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]}},"required":["id","status","amountMinor","currency","expiresAt","receivedPaymentAt"]},"EventRegisteredUser":{"type":"object","properties":{"id":{"type":"string","description":"User id"},"name":{"type":"string","description":"User name"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User image url (if any)"},"isAnonymous":{"type":"boolean","description":"True when the member has turned off public event registrations, in which case `id`, `name` and `image` carry no identity. Never true for event admins or for the caller's own registration."},"allowPhoto":{"description":"Photo consent for this event. Only included when the caller has event admin permissions.","type":"boolean"},"status":{"description":"Registration status (registered/attended/no_show). Only included for event admins.","type":"string"},"attendedAt":{"description":"When the user was checked in, if at all. Only included for event admins.","anyOf":[{"type":"string"},{"type":"null"}]},"email":{"description":"User email. Only included for event admins.","type":"string"},"registeredAt":{"description":"When the user registered. Only included for event admins.","type":"string"},"waitlistPosition":{"description":"Position on the waitlist, if waitlisted. Only included for event admins.","anyOf":[{"type":"number"},{"type":"null"}]},"payment":{"description":"The user's payment for this event, if any. Only included for event admins.","anyOf":[{"$ref":"#/components/schemas/EventRegistrationPayment"},{"type":"null"}]}},"required":["id","name","image","isAnonymous"]},"EventRegistrationList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"registeredUsers":{"type":"array","items":{"$ref":"#/components/schemas/EventRegisteredUser"},"description":"List of registered users (paginated)"}},"required":["totalCount","pages","nextPage","registeredUsers"]},"Attendance":{"type":"object","properties":{"userId":{"type":"string"},"eventId":{"type":"string"},"status":{"type":"string","description":"New registration status"},"attendedAt":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["userId","eventId","status","attendedAt"]},"SetAttendance":{"type":"object","properties":{"attended":{"type":"boolean","description":"True marks the user as attended (checked in); false reverts them to registered."}},"required":["attended"]},"CreatePaymentResponse":{"type":"object","properties":{"eventId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"userId":{"type":"string"},"checkoutUrl":{"type":"string","format":"uri"},"amount":{"type":"number"},"currency":{"type":"string"}},"required":["eventId","userId","checkoutUrl","amount","currency"]},"CreatePaymentBody":{"type":"object","properties":{"returnUrl":{"type":"string","format":"uri","description":"URL to redirect user after payment"},"userFlow":{"default":"WEB_REDIRECT","description":"User flow type for payment","type":"string","enum":["WEB_REDIRECT","NATIVE_REDIRECT"]}},"required":["returnUrl"]},"EventPaymentAdmin":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"userId":{"type":"string"},"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"type":"string"}},"required":["id","name","image","email"]},"amountMinor":{"type":"number","description":"Amount in minor units (øre)"},"currency":{"type":"string"},"provider":{"anyOf":[{"type":"string"},{"type":"null"}]},"providerPaymentId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Provider reference. Null means the obligation was never started with the provider."},"status":{"type":"string","enum":["pending","paid","refunded","failed"]},"receivedPaymentAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"expiresAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","userId","user","amountMinor","currency","provider","providerPaymentId","status","receivedPaymentAt","expiresAt","createdAt"]},"EventPaymentList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"payments":{"type":"array","items":{"$ref":"#/components/schemas/EventPaymentAdmin"},"description":"List of payments for the event (paginated)"},"summary":{"type":"object","properties":{"paidCount":{"type":"number"},"pendingCount":{"type":"number"},"refundedCount":{"type":"number"},"failedCount":{"type":"number"},"totalPaidMinor":{"type":"number","description":"Sum of all completed (paid) payments, in minor units"}},"required":["paidCount","pendingCount","refundedCount","failedCount","totalPaidMinor"],"description":"Totals across every payment for the event"}},"required":["totalCount","pages","nextPage","payments","summary"]},"RefundEventPayment":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"type":"string","enum":["pending","paid","refunded","failed"]},"refundedAmountMinor":{"type":"number","description":"The amount that was refunded, in minor units"},"currency":{"type":"string"}},"required":["id","status","refundedAmountMinor","currency"]},"CreateEventFormResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":{"type":"string","enum":["survey","evaluation"]},"resource_type":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"type":"boolean"},"order":{"type":"number"},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"order":{"type":"number"}},"required":["id","title","order"]}}},"required":["id","title","type","required","order","options"]}}},"required":["type","resource_type"]},"CreateEventForm":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"description":{"type":"string"},"template":{"default":false,"type":"boolean"},"fields":{"default":[],"type":"array","items":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"default":false,"type":"boolean"},"order":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"options":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"order":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["title"]}}},"required":["title","type"]}},"event":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"type":{"type":"string","enum":["survey","evaluation"]}},"required":["title","event","type"]},"EventFormList":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":{"type":"string","enum":["survey","evaluation"]},"resource_type":{"type":"string"},"viewer_has_answered":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","title","description","type","resource_type","viewer_has_answered","created_at","updated_at"]}},"EventFormDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":{"type":"string","enum":["survey","evaluation"]},"resource_type":{"type":"string"},"viewer_has_answered":{"type":"boolean"},"website_url":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"type":"boolean"},"order":{"type":"number"},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"order":{"type":"number"}},"required":["id","title","order"]}}},"required":["id","title","type","required","order","options"]}}},"required":["id","title","description","type","resource_type","viewer_has_answered","website_url","created_at","updated_at","fields"]},"FeedbackItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Feedback ID"},"type":{"type":"string","enum":["idea","bug"]},"status":{"type":"string","enum":["open","in_progress","closed","rejected"]},"title":{"type":"string"},"description":{"type":"string"},"author":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","image"]},{"type":"null"}],"description":"Author, null if the user has been deleted"},"upvotes":{"type":"number"},"downvotes":{"type":"number"},"myVote":{"anyOf":[{"type":"string","enum":["up","down"]},{"type":"null"}],"description":"The requesting user's own vote, if any"},"createdAt":{"type":"string","description":"Creation time (ISO 8601)"},"updatedAt":{"type":"string","description":"Last update time (ISO 8601)"}},"required":["id","type","status","title","description","author","upvotes","downvotes","myVote","createdAt","updatedAt"]},"FeedbackList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"items":{"type":"array","items":{"$ref":"#/components/schemas/FeedbackItem"},"description":"List of feedback"}},"required":["totalCount","pages","nextPage","items"]},"CreateFeedback":{"type":"object","properties":{"type":{"type":"string","enum":["idea","bug"],"description":"Idea or bug report"},"title":{"type":"string","minLength":2,"maxLength":100,"description":"Short title"},"description":{"type":"string","minLength":10,"maxLength":2000,"description":"What the idea or the bug is"}},"required":["type","title","description"]},"UpdateFeedback":{"type":"object","properties":{"title":{"type":"string","minLength":2,"maxLength":100},"description":{"type":"string","minLength":10,"maxLength":2000},"status":{"type":"string","enum":["open","in_progress","closed","rejected"]}}},"FeedbackMessageResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"FeedbackVoteCounts":{"type":"object","properties":{"upvotes":{"type":"number"},"downvotes":{"type":"number"},"myVote":{"anyOf":[{"type":"string","enum":["up","down"]},{"type":"null"}]}},"required":["upvotes","downvotes","myVote"]},"VoteFeedback":{"type":"object","properties":{"value":{"type":"string","enum":["up","down"],"description":"Thumbs up or thumbs down"}},"required":["value"]},"FormResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"template":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","title","description","template","created_at","updated_at"]},"CreateForm":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"description":{"type":"string"},"template":{"default":false,"type":"boolean"},"fields":{"default":[],"type":"array","items":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"default":false,"type":"boolean"},"order":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"options":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"order":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["title"]}}},"required":["title","type"]}}},"required":["title"]},"FormList":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"template":{"type":"boolean"},"resource_type":{"type":"string"},"viewer_has_answered":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","title","description","template","resource_type","viewer_has_answered","created_at","updated_at"]}},"FormDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"template":{"type":"boolean"},"resource_type":{"type":"string","enum":["Form","EventForm","GroupForm"]},"viewer_has_answered":{"type":"boolean"},"website_url":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"type":"boolean"},"order":{"type":"number"},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"order":{"type":"number"}},"required":["id","title","order"]}}},"required":["id","title","type","required","order","options"]}},"type":{"anyOf":[{"type":"string","enum":["survey","evaluation"]},{"type":"null"}]},"event":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}]},"start_date":{"type":"string"},"location":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","title","slug","start_date","location"]},{"type":"null"}]},"group":{"anyOf":[{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"}},"required":["slug","name"]},{"type":"null"}]},"can_submit_multiple":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"is_open_for_submissions":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"opens_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"only_for_group_members":{"anyOf":[{"type":"boolean"},{"type":"null"}]}},"required":["id","title","description","template","resource_type","viewer_has_answered","website_url","created_at","updated_at","fields","type","event","group","can_submit_multiple","is_open_for_submissions","opens_at","only_for_group_members"]},"UpdateFormResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"template":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"type":"boolean"},"order":{"type":"number"},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"order":{"type":"number"}},"required":["id","title","order"]}}},"required":["id","title","type","required","order","options"]}},"email_receiver_on_submit":{"anyOf":[{"type":"string"},{"type":"null"}]},"can_submit_multiple":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"is_open_for_submissions":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"opens_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"only_for_group_members":{"anyOf":[{"type":"boolean"},{"type":"null"}]}}},"UpdateForm":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"description":{"type":"string"},"template":{"type":"boolean"},"fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string","maxLength":400},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"default":false,"type":"boolean"},"order":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string","maxLength":400},"order":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["title"]}}},"required":["title","type"]}},"email_receiver_on_submit":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"can_submit_multiple":{"type":"boolean"},"is_open_for_submissions":{"type":"boolean"},"only_for_group_members":{"type":"boolean"},"opens_at":{"description":"When the form opens for submissions; until then it stays closed. Setting it turns is_open_for_submissions on, and turning that off clears this.","anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]}}},"DeleteFormResponse":{"type":"object","properties":{"detail":{"type":"string"}},"required":["detail"]},"FormStatistics":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"resource_type":{"type":"string"},"statistics":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"type":{"type":"string","enum":["multiple_select","single_select"]},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"answer_amount":{"type":"number"},"answer_percentage":{"type":"number"}},"required":["id","title","answer_amount","answer_percentage"]}}},"required":["id","title","type","required","options"]}}},"required":["id","title","resource_type","statistics"]},"CreateSubmissionResponse":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"]},"CreateSubmission":{"type":"object","properties":{"answers":{"type":"array","items":{"type":"object","properties":{"field":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id"]},"answer_text":{"type":"string"},"selected_options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id"]}}},"required":["field"]}}},"required":["answers"]},"SubmissionList":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["id","name","email"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"answers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"field_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"answer_text":{"anyOf":[{"type":"string"},{"type":"null"}]},"selected_options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"}},"required":["id","title"]}}},"required":["id","field_id","answer_text","selected_options"]}}},"required":["id","user","created_at","updated_at","answers"]}},"SubmissionDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["id","name","email"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"answers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"field_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"answer_text":{"anyOf":[{"type":"string"},{"type":"null"}]},"selected_options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"}},"required":["id","title"]}}},"required":["id","field_id","answer_text","selected_options"]}}},"required":["id","user","created_at","updated_at","answers"]},"DeleteSubmissionResponse":{"type":"object","properties":{"detail":{"type":"string"}},"required":["detail"]},"DeleteSubmissionWithReason":{"type":"object","properties":{"reason":{"type":"string","minLength":1}},"required":["reason"]},"Gallery":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Album ID"},"slug":{"type":"string","description":"Public URL slug"},"title":{"type":"string","description":"Album title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cover image URL"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}]},"pictureCount":{"type":"number","description":"Number of pictures in the album"},"event":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"slug":{"type":"string"},"start":{"type":"string","description":"Start time (ISO 8601)"}},"required":["id","title","slug","start"]},{"type":"null"}],"description":"Arrangement the album belongs to, if any"},"createdById":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","description":"Creation time (ISO 8601)"},"updatedAt":{"type":"string","description":"Last update time (ISO 8601)"}},"required":["id","slug","title","description","imageUrl","imageAlt","pictureCount","event","createdById","createdAt","updatedAt"]},"CreateGallery":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":100,"description":"Album title, e.g. 'Julebord 2025'"},"description":{"description":"Optional description shown on the album","type":"string","maxLength":5000},"imageUrl":{"description":"Cover image shown in the album list","type":"string","format":"uri"},"imageAlt":{"description":"Alt text for the cover image","type":"string","maxLength":200},"eventId":{"description":"Arrangement the pictures are from, if any","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["title"]},"GalleryList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Gallery"},"description":"List of albums"}},"required":["totalCount","pages","nextPage","items"]},"GalleryPicture":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Picture ID"},"albumId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Album the picture belongs to"},"imageUrl":{"type":"string","description":"Image URL"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}]},"title":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","description":"Creation time (ISO 8601)"},"updatedAt":{"type":"string","description":"Last update time (ISO 8601)"}},"required":["id","albumId","imageUrl","imageAlt","title","description","createdAt","updatedAt"]},"GalleryPictureList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"items":{"type":"array","items":{"$ref":"#/components/schemas/GalleryPicture"},"description":"List of pictures"}},"required":["totalCount","pages","nextPage","items"]},"CreateGalleryPicturesResponse":{"type":"object","properties":{"created":{"type":"number","description":"Number of pictures added"},"items":{"type":"array","items":{"$ref":"#/components/schemas/GalleryPicture"}}},"required":["created","items"]},"CreateGalleryPictures":{"type":"object","properties":{"pictures":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"imageUrl":{"type":"string","format":"uri","description":"URL of the uploaded image"},"imageAlt":{"type":"string","maxLength":200},"title":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":5000}},"required":["imageUrl"]},"description":"Pictures to add to the album"}},"required":["pictures"]},"UpdateGalleryPicture":{"type":"object","properties":{"imageAlt":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"title":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}]},"description":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}]}}},"DeleteGalleryPictureResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"UpdateGallery":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":100},"description":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}]},"imageUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"imageAlt":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"eventId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}}},"DeleteGalleryResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"NotificationDeviceResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the device registry was updated"}},"required":["success"]},"RegisterNotificationDevice":{"type":"object","properties":{"token":{"type":"string","minLength":1,"description":"Expo push token for the device, e.g. \"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]\""},"platform":{"type":"string","enum":["ios","android"],"description":"Platform the device runs"}},"required":["token","platform"]},"UnregisterNotificationDevice":{"type":"object","properties":{"token":{"type":"string","minLength":1,"description":"Expo push token to stop sending to"}},"required":["token"]},"Notification":{"type":"object","properties":{"id":{"type":"string","description":"Notification ID"},"userId":{"type":"string","description":"User ID"},"title":{"type":"string","description":"Notification title"},"description":{"type":"string","description":"Notification description"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional link URL (nullable)"},"isRead":{"type":"boolean","description":"Whether notification is read"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"Notification creation time (ISO 8601)"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"Notification update time (ISO 8601)"}},"required":["id","userId","title","description","link","isRead","createdAt","updatedAt"]},"NotificationList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Notification"},"description":"List of notifications"}},"required":["totalCount","pages","nextPage","items"]},"DeleteNotificationResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether deletion was successful"}},"required":["success"]},"MarkReadResponse":{"type":"object","properties":{"id":{"type":"string","description":"Notification ID"},"isRead":{"type":"boolean","description":"Updated read status"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"Notification update time (ISO 8601)"}},"required":["id","isRead","updatedAt"]},"MarkNotificationRead":{"type":"object","properties":{"isRead":{"type":"boolean","description":"Whether notification should be marked as read"}},"required":["isRead"]},"GroupWithMemberCount":{"type":"object","properties":{"slug":{"type":"string","description":"Group slug"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Gruppebilde URL, shown on the group's about page"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group logo URL, shown in avatars and cards"},"name":{"type":"string","description":"Group name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group description"},"contactEmail":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group contact email"},"type":{"type":"string","description":"Group type"},"subtype":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sub-category below type. GRUPPE or IDRETTSGRUPPE for interest groups, null otherwise."},"finesInfo":{"type":"string","description":"Group fines info"},"finesActivated":{"type":"boolean","description":"Group fines activated"},"finesAdminId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group fines admin ID"},"contractSigningRequired":{"type":"boolean","description":"Whether contract signing is required"},"createdAt":{"type":"string","description":"Creation timestamp"},"updatedAt":{"type":"string","description":"Last update timestamp"},"memberCount":{"type":"number","description":"Number of members in the group"}},"required":["slug","imageUrl","logoUrl","name","description","contactEmail","type","subtype","finesInfo","finesActivated","finesAdminId","contractSigningRequired","createdAt","updatedAt","memberCount"]},"GroupList":{"type":"array","items":{"$ref":"#/components/schemas/GroupWithMemberCount"}},"MyGroup":{"type":"object","properties":{"slug":{"type":"string","description":"Group slug"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Gruppebilde URL, shown on the group's about page"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group logo URL, shown in avatars and cards"},"name":{"type":"string","description":"Group name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group description"},"contactEmail":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group contact email"},"type":{"type":"string","description":"Group type"},"subtype":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sub-category below type. GRUPPE or IDRETTSGRUPPE for interest groups, null otherwise."},"finesInfo":{"type":"string","description":"Group fines info"},"finesActivated":{"type":"boolean","description":"Group fines activated"},"finesAdminId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group fines admin ID"},"contractSigningRequired":{"type":"boolean","description":"Whether contract signing is required"},"createdAt":{"type":"string","description":"Creation timestamp"},"updatedAt":{"type":"string","description":"Last update timestamp"},"membership":{"type":"object","properties":{"role":{"type":"string","enum":["member","leader"]},"joinedAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["role","joinedAt","updatedAt"]}},"required":["slug","imageUrl","logoUrl","name","description","contactEmail","type","subtype","finesInfo","finesActivated","finesAdminId","contractSigningRequired","createdAt","updatedAt","membership"]},"MyGroupList":{"type":"array","items":{"$ref":"#/components/schemas/MyGroup"}},"Group":{"type":"object","properties":{"slug":{"type":"string","description":"Group slug"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Gruppebilde URL, shown on the group's about page"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group logo URL, shown in avatars and cards"},"name":{"type":"string","description":"Group name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group description"},"contactEmail":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group contact email"},"type":{"type":"string","description":"Group type"},"subtype":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sub-category below type. GRUPPE or IDRETTSGRUPPE for interest groups, null otherwise."},"finesInfo":{"type":"string","description":"Group fines info"},"finesActivated":{"type":"boolean","description":"Group fines activated"},"finesAdminId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group fines admin ID"},"contractSigningRequired":{"type":"boolean","description":"Whether contract signing is required"},"createdAt":{"type":"string","description":"Creation timestamp"},"updatedAt":{"type":"string","description":"Last update timestamp"}},"required":["slug","imageUrl","logoUrl","name","description","contactEmail","type","subtype","finesInfo","finesActivated","finesAdminId","contractSigningRequired","createdAt","updatedAt"]},"CreateGroup":{"type":"object","properties":{"slug":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z0-9-]+$","description":"Unique group slug identifier"},"imageUrl":{"description":"Gruppebilde URL, shown on the group's about page","type":"string","maxLength":600,"format":"uri"},"logoUrl":{"description":"Group logo URL, shown in avatars and cards","type":"string","maxLength":600,"format":"uri"},"name":{"type":"string","minLength":1,"maxLength":128,"description":"Group name"},"description":{"description":"Group description","type":"string"},"contactEmail":{"description":"Group contact email","type":"string","maxLength":200,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"type":{"type":"string","maxLength":50,"description":"Group type (e.g., committee, study, interestgroup)"},"subtype":{"description":"Sub-category below type. Only used for interest groups today: GRUPPE or IDRETTSGRUPPE, which the org chart renders as separate sections.","type":"string","maxLength":50},"finesInfo":{"default":"","description":"Information about group fines policy","type":"string"},"finesActivated":{"default":false,"description":"Whether fines are activated for this group","type":"boolean"},"finesAdminId":{"description":"User ID of the fines administrator","type":"string","maxLength":255}},"required":["slug","name","type"]},"UpdateGroupResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"UpdateGroup":{"type":"object","properties":{"imageUrl":{"description":"Gruppebilde URL, shown on the group's about page","type":"string","maxLength":600,"format":"uri"},"logoUrl":{"description":"Group logo URL, shown in avatars and cards","type":"string","maxLength":600,"format":"uri"},"name":{"description":"Group name","type":"string","minLength":1,"maxLength":128},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group description"},"contactEmail":{"anyOf":[{"type":"string","maxLength":200,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}],"description":"Group contact email"},"type":{"description":"Group type","type":"string","maxLength":50},"subtype":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"description":"Sub-category below type (GRUPPE or IDRETTSGRUPPE for interest groups). Null clears it."},"finesInfo":{"description":"Information about group fines policy","type":"string"},"finesActivated":{"description":"Whether fines are activated for this group","type":"boolean"},"finesAdminId":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"description":"User ID of the fines administrator"},"contractSigningRequired":{"description":"Whether contract signing is required for group members","type":"boolean"}}},"GroupDetail":{"type":"object","properties":{"slug":{"type":"string","description":"Group slug"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Gruppebilde URL, shown on the group's about page"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group logo URL, shown in avatars and cards"},"name":{"type":"string","description":"Group name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group description"},"contactEmail":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group contact email"},"type":{"type":"string","description":"Group type"},"subtype":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sub-category below type. GRUPPE or IDRETTSGRUPPE for interest groups, null otherwise."},"finesInfo":{"type":"string","description":"Group fines info"},"finesActivated":{"type":"boolean","description":"Group fines activated"},"finesAdminId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Group fines admin ID"},"contractSigningRequired":{"type":"boolean","description":"Whether contract signing is required"},"createdAt":{"type":"string","description":"Creation timestamp"},"updatedAt":{"type":"string","description":"Last update timestamp"},"viewerCanUseFines":{"type":"boolean","description":"Whether the authenticated caller may see and give fines in this group. False when fines are off, when signed out, or when the caller is a study-group member who is no longer an enrolled student."}},"required":["slug","imageUrl","logoUrl","name","description","contactEmail","type","subtype","finesInfo","finesActivated","finesAdminId","contractSigningRequired","createdAt","updatedAt","viewerCanUseFines"]},"Fine":{"type":"object","properties":{"id":{"type":"string","description":"Fine ID"},"userId":{"type":"string","description":"User ID who received the fine"},"groupSlug":{"type":"string","description":"Group slug that issued the fine"},"reason":{"type":"string","description":"Reason for the fine"},"amount":{"type":"number","description":"Fine amount in NOK"},"defense":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User's defense text"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Evidence image URL"},"status":{"type":"string","description":"Fine status (pending, approved, paid, rejected)"},"createdByUserId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User who created the fine"},"approvedByUserId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User who approved the fine"},"approvedAt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Approval timestamp"},"paidAt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Payment timestamp"},"createdAt":{"type":"string","description":"Creation timestamp"},"updatedAt":{"type":"string","description":"Last update timestamp"},"user":{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User display name"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User profile image URL"}},"required":["id","name","image"],"description":"Public user info for the fined user"},"createdByUser":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User display name"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User profile image URL"}},"required":["id","name","image"]},{"type":"null"}],"description":"Public user info for the fine creator"},"lawId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the paragraph the fine was given under"},"law":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"Law ID"},"paragraph":{"type":"string","description":"Paragraph number, e.g. 3.10"},"title":{"type":"string","description":"Paragraph title"}},"required":["id","paragraph","title"]},{"type":"null"}],"description":"The paragraph in the group's lovverk. Null for fines migrated from Lepton and for fines given without one."}},"required":["id","userId","groupSlug","reason","amount","defense","image","status","createdByUserId","approvedByUserId","approvedAt","paidAt","createdAt","updatedAt","user","createdByUser","lawId","law"]},"FineList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of fines"},"pages":{"type":"number","description":"Total number of pages"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Next page number, or null if last page"},"fines":{"type":"array","items":{"$ref":"#/components/schemas/Fine"}}},"required":["totalCount","pages","nextPage","fines"]},"CreateFine":{"type":"object","properties":{"userId":{"type":"string","maxLength":255,"description":"User ID who receives the fine"},"groupSlug":{"type":"string","maxLength":128,"description":"Group slug that issues the fine"},"reason":{"type":"string","minLength":1,"description":"Reason for the fine"},"amount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of fines given (0 or more)"},"defense":{"description":"User's defense text","type":"string"},"image":{"description":"Evidence image URL","type":"string","maxLength":600,"format":"uri"},"lawId":{"description":"Paragraph in the group's lovverk the fine is given under. Optional: a group whose lovverk is empty can still hand out fines.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["userId","groupSlug","reason","amount"]},"FineStatistics":{"type":"object","properties":{"notApproved":{"type":"number","description":"Sum of amounts for fines awaiting approval"},"approvedNotPaid":{"type":"number","description":"Sum of amounts for approved, unpaid fines"},"paid":{"type":"number","description":"Sum of amounts for paid fines"}},"required":["notApproved","approvedNotPaid","paid"]},"FineUserList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of members"},"pages":{"type":"number","description":"Total number of pages"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Next page number, or null if last page"},"users":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User display name"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User profile image URL"},"finesAmount":{"type":"number","description":"Sum of the member's fine amounts in this group, 0 when they have none"},"finesCount":{"type":"number","description":"Number of fines the member has"}},"required":["id","name","image","finesAmount","finesCount"]}}},"required":["totalCount","pages","nextPage","users"]},"BatchUpdateFinesResponse":{"type":"object","properties":{"message":{"type":"string"},"updated":{"type":"number","description":"Number of fines that were updated"}},"required":["message","updated"]},"BatchUpdateFines":{"type":"object","properties":{"fineIds":{"minItems":1,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"The fines to update"},"status":{"type":"string","enum":["pending","approved","paid","rejected"],"description":"Fine status"}},"required":["fineIds","status"]},"BatchUpdateUserFines":{"type":"object","properties":{"status":{"type":"string","enum":["pending","approved","paid","rejected"],"description":"Fine status"}},"required":["status"]},"UpdateFineResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"UpdateFine":{"type":"object","properties":{"defense":{"description":"User's defense text","type":"string"},"status":{"type":"string","enum":["pending","approved","paid","rejected"],"description":"Fine status"}}},"Law":{"type":"object","properties":{"id":{"type":"string","description":"Law ID"},"groupSlug":{"type":"string","description":"Group the law belongs to"},"paragraph":{"type":"string","description":"Paragraph number as a decimal string"},"title":{"type":"string","description":"Law title"},"description":{"type":"string","description":"Longer description of the law"},"amount":{"type":"number","description":"Default number of fine units"},"createdAt":{"type":"string","description":"Creation timestamp"},"updatedAt":{"type":"string","description":"Last update timestamp"}},"required":["id","groupSlug","paragraph","title","description","amount","createdAt","updatedAt"]},"LawList":{"type":"array","items":{"$ref":"#/components/schemas/Law"}},"CreateLaw":{"type":"object","properties":{"paragraph":{"type":"number","minimum":0,"maximum":99.99,"description":"Paragraph number, e.g. 1 or 3.12"},"title":{"type":"string","minLength":1,"maxLength":100,"description":"Law title"},"description":{"description":"Longer description of the law","type":"string"},"amount":{"description":"Default number of fine units","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["paragraph","title"]},"UpdateLaw":{"type":"object","properties":{"paragraph":{"description":"Paragraph number, e.g. 1 or 3.12","type":"number","minimum":0,"maximum":99.99},"title":{"description":"Law title","type":"string","minLength":1,"maxLength":100},"description":{"description":"Longer description of the law","type":"string"},"amount":{"description":"Default number of fine units","type":"integer","minimum":0,"maximum":9007199254740991}}},"GroupFormerMember":{"type":"object","properties":{"id":{"type":"string","description":"Membership history entry ID"},"userId":{"type":"string","description":"User ID"},"groupSlug":{"type":"string","description":"Group slug"},"role":{"type":"string","description":"Role held when the membership ended"},"startedAt":{"type":"string","description":"When the membership started"},"endedAt":{"type":"string","description":"When the membership ended"},"user":{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User display name"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Username used in profile URLs"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User profile image URL"}},"required":["id","name","username","image"],"description":"Public user info for the former member"}},"required":["id","userId","groupSlug","role","startedAt","endedAt","user"]},"GroupFormerMemberList":{"type":"array","items":{"$ref":"#/components/schemas/GroupFormerMember"}},"GroupMember":{"type":"object","properties":{"userId":{"type":"string","description":"User ID"},"groupSlug":{"type":"string","description":"Group slug"},"role":{"type":"string","description":"Membership role"},"createdAt":{"type":"string","description":"Membership creation timestamp"},"updatedAt":{"type":"string","description":"Membership update timestamp"},"user":{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User display name"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Username used in profile URLs"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"User profile image URL"},"studyProgram":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name of the member's study programme, derived from their STUDY group membership. Null when the member has none (alumni, honorary members)."},"studyStartYear":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The year the member started studying (kull), derived from their STUDYYEAR group membership. Null when unknown."}},"required":["id","name","username","image","studyProgram","studyStartYear"],"description":"Public user info for the member"}},"required":["userId","groupSlug","role","createdAt","updatedAt","user"]},"GroupMemberList":{"type":"array","items":{"$ref":"#/components/schemas/GroupMember"}},"GroupMembership":{"type":"object","properties":{"userId":{"type":"string","description":"User ID"},"groupSlug":{"type":"string","description":"Group slug"},"role":{"type":"string","enum":["member","leader"],"description":"Membership role"},"createdAt":{"type":"string","description":"Membership creation timestamp"},"updatedAt":{"type":"string","description":"Membership update timestamp"}},"required":["userId","groupSlug","role","createdAt","updatedAt"]},"AddGroupMember":{"type":"object","properties":{"userId":{"type":"string","maxLength":255,"description":"User ID to add as member"},"role":{"default":"member","description":"Membership role","type":"string","enum":["member","leader"]}},"required":["userId"]},"UpdateMemberRoleResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"UpdateGroupMemberRole":{"type":"object","properties":{"role":{"type":"string","enum":["member","leader"],"description":"Membership role"}},"required":["role"]},"GroupPositionHolder":{"type":"object","properties":{"userId":{"type":"string","description":"Holder's user ID"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Holder's name"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Holder's profile image"}},"required":["userId","name","image"]},"GroupPosition":{"type":"object","properties":{"id":{"type":"string","description":"Position ID"},"groupSlug":{"type":"string","description":"Group slug"},"name":{"type":"string","description":"Position name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"permissions":{"type":"array","items":{"type":"string"}},"scope":{"type":"string","enum":["group","global"]},"linkedGroupSlug":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"If set, this position is held automatically by the leader of the given subgroup and cannot be assigned manually."},"holder":{"anyOf":[{"$ref":"#/components/schemas/GroupPositionHolder"},{"type":"null"}],"description":"The single holder of this position, if assigned"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","groupSlug","name","description","permissions","scope","linkedGroupSlug","holder","createdAt","updatedAt"]},"GroupPositionList":{"type":"array","items":{"$ref":"#/components/schemas/GroupPosition"}},"CreateGroupPosition":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":128,"description":"Position name, e.g. 'Økonomiansvarlig'"},"description":{"description":"Optional description of the position","type":"string","maxLength":1000},"permissions":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":64},"description":"Permissions granted to holders of this position. Must be valid permission names from the registry."},"scope":{"default":"group","description":"Whether permissions apply only within this group, or globally (global requires roles:create)","type":"string","enum":["group","global"]}},"required":["name","permissions"]},"UpdateGroupPosition":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":128},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}]},"permissions":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":64},"description":"Permissions granted to holders of this position. Must be valid permission names from the registry."},"scope":{"type":"string","enum":["group","global"]}}},"GroupPositionMessage":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"AssignGroupPosition":{"type":"object","properties":{"userId":{"type":"string","maxLength":255,"description":"User to assign the position to"}},"required":["userId"]},"GroupLeaderPermissions":{"type":"object","properties":{"permissions":{"type":"array","items":{"type":"string"},"description":"Permissions held by whoever currently leads this group, scoped to this group."},"globalPermissions":{"type":"array","items":{"type":"string"},"description":"Permissions held by whoever currently leads this group across all of TIHLDE, unscoped."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What this group calls its leader, e.g. 'President'. Null when the group has no custom title and the leader is simply «Leder»."}},"required":["permissions","globalPermissions","title"]},"UpdateGroupLeaderPermissions":{"type":"object","properties":{"permissions":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":64},"description":"Permissions the group's leader holds, scoped to this group. Replaces the existing list."},"globalPermissions":{"description":"Permissions the group's leader holds across all of TIHLDE. Replaces the existing list. Requires holding each permission globally yourself. Omit to leave unchanged.","maxItems":100,"type":"array","items":{"type":"string","maxLength":64}},"title":{"description":"What this group calls its leader, e.g. 'President'. Null clears it back to 'Leder'. Omit to leave unchanged.","anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}]}},"required":["permissions"]},"GroupMemberPermissions":{"type":"object","properties":{"permissions":{"type":"array","items":{"type":"string"},"description":"Permissions held by every member of this group, scoped to this group."},"globalPermissions":{"type":"array","items":{"type":"string"},"description":"Permissions held by every member of this group across all of TIHLDE, unscoped."}},"required":["permissions","globalPermissions"]},"UpdateGroupMemberPermissions":{"type":"object","properties":{"permissions":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":64},"description":"Permissions every member of this group holds, scoped to this group. Replaces the existing list."},"globalPermissions":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":64},"description":"Permissions every member of this group holds across all of TIHLDE. Replaces the existing list. Requires holding each permission globally yourself."}},"required":["permissions","globalPermissions"]},"CreateGroupFormResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"group":{"type":"string"},"email_receiver_on_submit":{"anyOf":[{"type":"string"},{"type":"null"}]},"can_submit_multiple":{"type":"boolean"},"is_open_for_submissions":{"type":"boolean"},"opens_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"only_for_group_members":{"type":"boolean"},"resource_type":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"type":"boolean"},"order":{"type":"number"},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"order":{"type":"number"}},"required":["id","title","order"]}}},"required":["id","title","type","required","order","options"]}}},"required":["group","resource_type"]},"CreateGroupForm":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"description":{"type":"string"},"template":{"default":false,"type":"boolean"},"fields":{"default":[],"type":"array","items":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"type":{"type":"string","enum":["text_answer","multiple_select","single_select"]},"required":{"default":false,"type":"boolean"},"order":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"options":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","maxLength":400},"order":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["title"]}}},"required":["title","type"]}},"group":{"type":"string","maxLength":128},"email_receiver_on_submit":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"can_submit_multiple":{"default":true,"type":"boolean"},"is_open_for_submissions":{"default":false,"type":"boolean"},"only_for_group_members":{"default":false,"type":"boolean"},"opens_at":{"description":"When the form opens for submissions; until then it stays closed. Setting it turns is_open_for_submissions on, and turning that off clears this.","anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]}},"required":["title","group"]},"GroupFormList":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"group":{"type":"string"},"email_receiver_on_submit":{"anyOf":[{"type":"string"},{"type":"null"}]},"can_submit_multiple":{"type":"boolean"},"is_open_for_submissions":{"type":"boolean"},"opens_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"is_open_now":{"type":"boolean"},"only_for_group_members":{"type":"boolean"},"resource_type":{"type":"string"},"viewer_has_answered":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","title","description","group","email_receiver_on_submit","can_submit_multiple","is_open_for_submissions","opens_at","is_open_now","only_for_group_members","resource_type","viewer_has_answered","created_at","updated_at"]}},"Institute":{"type":"object","properties":{"slug":{"type":"string","description":"Institute slug, e.g. 'idi'"},"shortName":{"type":"string","description":"Short institute name, e.g. 'IDI'"},"name":{"type":"string","description":"Full institute name"}},"required":["slug","shortName","name"]},"InstituteList":{"type":"array","items":{"$ref":"#/components/schemas/Institute"}},"SignaturePlacement":{"type":"object","properties":{"page":{"type":"integer","minimum":0,"maximum":9007199254740991},"xPct":{"type":"number","minimum":0,"maximum":1},"yPct":{"type":"number","minimum":0,"maximum":1},"wPct":{"type":"number","exclusiveMinimum":0,"maximum":1},"hPct":{"type":"number","exclusiveMinimum":0,"maximum":1}},"required":["page","xPct","yPct","wPct","hPct"]},"ActiveContract":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"version":{"type":"string"},"fileKey":{"type":"string"},"isActive":{"type":"boolean"},"signaturePlacement":{"anyOf":[{"$ref":"#/components/schemas/SignaturePlacement"},{"type":"null"}]},"namePlacement":{"anyOf":[{"$ref":"#/components/schemas/SignaturePlacement"},{"type":"null"}]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"downloadUrl":{"type":"string","description":"Direct URL to stream the PDF"}},"required":["id","title","version","fileKey","isActive","signaturePlacement","namePlacement","createdAt","updatedAt","downloadUrl"]},"SignatureStatus":{"type":"object","properties":{"hasSigned":{"type":"boolean"},"signedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"signedName":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["hasSigned","signedAt","signedName"]},"SignContractResponse":{"type":"object","properties":{"message":{"type":"string"},"signedAt":{"type":"string"}},"required":["message","signedAt"]},"SignContract":{"type":"object","properties":{"signedName":{"type":"string","minLength":1,"maxLength":256,"description":"Full name the member signed with"},"signatureDataUrl":{"type":"string","pattern":"^data:image\\/png;base64,","description":"Signature as a PNG data URL, produced by the signature pad"}},"required":["signedName","signatureDataUrl"]},"Contract":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"version":{"type":"string"},"fileKey":{"type":"string"},"isActive":{"type":"boolean"},"signaturePlacement":{"anyOf":[{"$ref":"#/components/schemas/SignaturePlacement"},{"type":"null"}]},"namePlacement":{"anyOf":[{"$ref":"#/components/schemas/SignaturePlacement"},{"type":"null"}]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","title","version","fileKey","isActive","signaturePlacement","namePlacement","createdAt","updatedAt"]},"ContractList":{"type":"array","items":{"$ref":"#/components/schemas/Contract"}},"CreateContract":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":256,"description":"Contract title"},"version":{"type":"string","minLength":1,"maxLength":64,"description":"Version identifier e.g. '2026-01'"},"fileKey":{"type":"string","minLength":1,"maxLength":600,"description":"Asset key from POST /api/assets"},"signaturePlacement":{"description":"Where the member's signature is stamped on the PDF","anyOf":[{"$ref":"#/components/schemas/SignaturePlacement"},{"type":"null"}]},"namePlacement":{"description":"Where the signer's name and date are written","anyOf":[{"$ref":"#/components/schemas/SignaturePlacement"},{"type":"null"}]}},"required":["title","version","fileKey"]},"ActivateContractResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"GroupSignatureMember":{"type":"object","properties":{"userId":{"type":"string"},"userName":{"type":"string","description":"User's display name"},"userEmail":{"type":"string","description":"User's email address"},"hasSigned":{"type":"boolean"},"signedAt":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["userId","userName","userEmail","hasSigned","signedAt"]},"GroupSignatureList":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/GroupSignatureMember"}},"totalMembers":{"type":"number"},"signedCount":{"type":"number"}},"required":["members","totalMembers","signedCount"]},"RevokeSignatureResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"MotetidEventListItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$"},"slug":{"type":"string"},"title":{"type":"string"},"dateMode":{"type":"string","enum":["DATES","WEEKDAYS"],"description":"Whether the board's columns are calendar dates (\"DATES\", a one-off activity) or recurring weekdays (\"WEEKDAYS\", a fixed weekly slot)"},"dates":{"type":"array","items":{"type":"string","pattern":"^(\\d{4}-\\d{2}-\\d{2}|MON|TUE|WED|THU|FRI|SAT|SUN)$","description":"Board column key: ISO \"YYYY-MM-DD\" date, or \"MON\"..\"SUN\" on a weekday board"}},"startTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"\"HH:mm\" time string"},"endTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"\"HH:mm\" time string"},"deadline":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"isOwner":{"type":"boolean","description":"Whether the caller created the event"},"participantCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","slug","title","dateMode","dates","startTime","endTime","deadline","createdAt","isOwner","participantCount"]},"MotetidEventList":{"type":"array","items":{"$ref":"#/components/schemas/MotetidEventListItem"},"description":"Events the caller created or participates in, newest first"},"CreateMotetidEventResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$"},"slug":{"type":"string","description":"Share-link slug for the new event"}},"required":["id","slug"]},"CreateMotetidEvent":{"type":"object","properties":{"title":{"type":"string","minLength":2,"maxLength":200,"description":"Event title"},"dateMode":{"type":"string","enum":["DATES","WEEKDAYS"],"description":"Whether the board's columns are calendar dates (\"DATES\", a one-off activity) or recurring weekdays (\"WEEKDAYS\", a fixed weekly slot)"},"dates":{"minItems":1,"maxItems":62,"type":"array","items":{"type":"string","pattern":"^(\\d{4}-\\d{2}-\\d{2}|MON|TUE|WED|THU|FRI|SAT|SUN)$","description":"Board column key: ISO \"YYYY-MM-DD\" date, or \"MON\"..\"SUN\" on a weekday board"},"description":"Candidate columns: ISO dates, or weekday keys when dateMode is WEEKDAYS"},"startTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"\"HH:mm\" time string"},"endTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"\"HH:mm\" time string"},"deadline":{"description":"Optional deadline; the board becomes read-only after this instant","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["title","dates","startTime","endTime"]},"MotetidEvent":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$","description":"Event ID"},"slug":{"type":"string","description":"Share-link slug"},"title":{"type":"string"},"dateMode":{"type":"string","enum":["DATES","WEEKDAYS"],"description":"Whether the board's columns are calendar dates (\"DATES\", a one-off activity) or recurring weekdays (\"WEEKDAYS\", a fixed weekly slot)"},"dates":{"type":"array","items":{"type":"string","pattern":"^(\\d{4}-\\d{2}-\\d{2}|MON|TUE|WED|THU|FRI|SAT|SUN)$","description":"Board column key: ISO \"YYYY-MM-DD\" date, or \"MON\"..\"SUN\" on a weekday board"},"description":"Candidate columns, sorted chronologically (DATES) or Monday-first (WEEKDAYS)"},"startTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"\"HH:mm\" time string"},"endTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"\"HH:mm\" time string"},"slotDuration":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Slot length in minutes"},"deadline":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"participants":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$","description":"Participant ID"},"name":{"type":"string","description":"Display name"},"userId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Linked user id, null for anonymous participants"},"slots":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","pattern":"^(\\d{4}-\\d{2}-\\d{2}|MON|TUE|WED|THU|FRI|SAT|SUN)$","description":"Board column key: ISO \"YYYY-MM-DD\" date, or \"MON\"..\"SUN\" on a weekday board"},"time":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"\"HH:mm\" time string"},"status":{"type":"string","enum":["AVAILABLE","IF_NEEDED"],"description":"Availability status for a slot"}},"required":["date","time","status"]}}},"required":["id","name","userId","slots"]}},"viewer":{"type":"object","properties":{"isAuthenticated":{"type":"boolean"},"isOwner":{"type":"boolean"},"participantId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$"},{"type":"null"}],"description":"The viewer's participant row, if any"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Suggested display name for the viewer"}},"required":["isAuthenticated","isOwner","participantId","name"]}},"required":["id","slug","title","dateMode","dates","startTime","endTime","slotDuration","deadline","createdAt","participants","viewer"]},"SaveMotetidAvailabilityResponse":{"type":"object","properties":{"participantId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$"}},"required":["participantId"]},"SaveMotetidAvailability":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Display name for the participant"},"slots":{"maxItems":5000,"type":"array","items":{"type":"object","properties":{"date":{"type":"string","pattern":"^(\\d{4}-\\d{2}-\\d{2}|MON|TUE|WED|THU|FRI|SAT|SUN)$","description":"Board column key: ISO \"YYYY-MM-DD\" date, or \"MON\"..\"SUN\" on a weekday board"},"time":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"\"HH:mm\" time string"},"status":{"type":"string","enum":["AVAILABLE","IF_NEEDED"],"description":"Availability status for a slot"}},"required":["date","time","status"]},"description":"The participant's full slot selection"}},"required":["name","slots"]},"DeleteMotetidEventResponse":{"type":"object","properties":{"message":{"type":"string","description":"Success message"}},"required":["message"]},"MotetidGoogleConnectResponse":{"type":"object","properties":{"url":{"type":"string","description":"Google OAuth consent URL to navigate the browser to"}},"required":["url"]},"MotetidSyncCalendarResponse":{"type":"object","properties":{"blocked":{"type":"array","items":{"type":"string"},"description":"Busy grid slots as \"date|time\" keys"},"events":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"start":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"end":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["title","start","end"]}}},"required":["blocked","events"]},"MotetidSyncCalendarError":{"type":"object","properties":{"error":{"type":"string"},"requiresReconnect":{"description":"The user must redo the Google OAuth flow","type":"boolean"}},"required":["error"]},"NewsArticle":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"News article ID"},"title":{"type":"string","description":"News article title"},"header":{"type":"string","description":"News article subtitle/ingress"},"body":{"type":"string","description":"Main content"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Image URL"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Image alt text"},"emojisAllowed":{"type":"boolean","description":"Whether reactions are enabled"},"createdById":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Creator user ID"},"createdAt":{"type":"string","description":"Creation time (ISO 8601)"},"updatedAt":{"type":"string","description":"Last update time (ISO 8601)"},"creator":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["id","name","email"]},{"type":"null"}],"description":"Creator user info"},"reactions":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"string"},"newsId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"emoji":{"type":"string"},"createdAt":{"type":"string"},"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}},"required":["userId","newsId","emoji","createdAt","user"]}}},"required":["id","title","header","body","imageUrl","imageAlt","emojisAllowed","createdById","createdAt","updatedAt","creator","reactions"]},"CreateNews":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"News article title"},"header":{"type":"string","minLength":1,"maxLength":200,"description":"News article subtitle/ingress"},"body":{"type":"string","minLength":1,"description":"Main content of the news"},"imageUrl":{"description":"Optional image URL","type":"string","format":"uri"},"imageAlt":{"description":"Alt text for the image","type":"string","maxLength":255},"emojisAllowed":{"default":false,"description":"Whether reactions are enabled","type":"boolean"}},"required":["title","header","body"]},"NewsListItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$","description":"News ID"},"title":{"type":"string","description":"News title"},"header":{"type":"string","description":"News header"},"body":{"type":"string","description":"News body"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Image URL"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Image alt text"},"emojisAllowed":{"type":"boolean","description":"Whether reactions are allowed"},"createdAt":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Creation time (ISO 8601)"},"updatedAt":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Last update time (ISO 8601)"}},"required":["id","title","header","body","imageUrl","imageAlt","emojisAllowed","createdAt","updatedAt"]},"NewsList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"items":{"type":"array","items":{"$ref":"#/components/schemas/NewsListItem"},"description":"List of news articles"}},"required":["totalCount","pages","nextPage","items"]},"UpdateNews":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"header":{"type":"string","minLength":1,"maxLength":200},"body":{"type":"string","minLength":1},"imageUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"imageAlt":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}]},"emojisAllowed":{"type":"boolean"}}},"DeleteNewsResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"NewsReaction":{"type":"object","properties":{"userId":{"type":"string","description":"User ID"},"newsId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"News article ID"},"emoji":{"type":"string","description":"Emoji reaction"},"createdAt":{"type":"string","description":"Reaction creation time (ISO 8601)"}},"required":["userId","newsId","emoji","createdAt"]},"CreateNewsReaction":{"type":"object","properties":{"emoji":{"type":"string","minLength":1,"maxLength":32,"description":"Emoji reaction (e.g., 👍, ❤️, 😂)"}},"required":["emoji"]},"DeleteNewsReactionResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"ToddelIssue":{"type":"object","properties":{"edition":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Lepton's running count, not the number on the cover"},"title":{"type":"string","description":"Title of the issue"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cover image, if one was published"},"pdfUrl":{"type":"string","description":"The issue itself, as a PDF"},"publishedAt":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Publication date (ISO 8601)"}},"required":["edition","title","imageUrl","pdfUrl","publishedAt"]},"ToddelList":{"type":"array","items":{"$ref":"#/components/schemas/ToddelIssue"},"description":"Every issue, newest first"},"CreateToddel":{"type":"object","properties":{"edition":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"Running count, continuing where the archive left off — not the number printed on the cover"},"title":{"type":"string","minLength":1,"maxLength":200,"description":"Title of the issue, e.g. “Töddel”"},"publishedAt":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Publication date (ISO 8601)"},"pdfKey":{"type":"string","minLength":1,"description":"Asset key of the PDF, from POST /api/assets"},"imageKey":{"description":"Asset key of the cover image, if there is one","anyOf":[{"type":"string","minLength":1},{"type":"null"}]}},"required":["edition","title","publishedAt","pdfKey"]},"UpdateToddel":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"Title of the issue, e.g. “Töddel”"},"publishedAt":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Publication date (ISO 8601)"},"pdfKey":{"type":"string","minLength":1,"description":"Asset key of the PDF, from POST /api/assets"},"imageKey":{"description":"Asset key of the cover image, if there is one","anyOf":[{"type":"string","minLength":1},{"type":"null"}]}},"description":"Only the given fields are changed"},"DeleteToddelResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"QRCode":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$","description":"QR code ID"},"name":{"type":"string","description":"Name shown above the code"},"content":{"type":"string","description":"The payload the code encodes"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the code was created"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"When the code was last updated"}},"required":["id","name","content","createdAt","updatedAt"]},"QRCodeList":{"type":"array","items":{"$ref":"#/components/schemas/QRCode"},"description":"The caller's QR codes, newest first"},"CreateQRCode":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Name shown above the code"},"content":{"type":"string","minLength":1,"maxLength":2000,"description":"The payload the code encodes — a URL or plain text"}},"required":["name","content"]},"DeleteQRCodeResponse":{"type":"object","properties":{"message":{"type":"string","description":"Success message"}},"required":["message"]},"RoleUser":{"type":"object","properties":{"userId":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["userId","name","image"]},"Role":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"position":{"type":"number"},"permissions":{"type":"array","items":{"type":"string"}},"users":{"type":"array","items":{"$ref":"#/components/schemas/RoleUser"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","description","position","permissions","users","createdAt","updatedAt"]},"RoleList":{"type":"array","items":{"$ref":"#/components/schemas/Role"}},"CreateRole":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64,"description":"Unique role name"},"description":{"type":"string","maxLength":256},"permissions":{"default":[],"maxItems":100,"type":"array","items":{"type":"string","maxLength":64},"description":"Permissions granted by this role"}},"required":["name"]},"UpdateRole":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64},"description":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}]},"permissions":{"maxItems":100,"type":"array","items":{"type":"string","maxLength":64},"description":"Permissions granted by this role"}}},"RoleMessage":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"AssignRole":{"type":"object","properties":{"userId":{"type":"string","maxLength":255,"description":"User to assign"}},"required":["userId"]},"JobDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Job posting ID"},"title":{"type":"string","description":"Job title"},"ingress":{"type":"string","description":"Short description"},"body":{"type":"string","description":"Full job description"},"company":{"type":"string","description":"Company name"},"location":{"type":"string","description":"Job location"},"deadline":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Application deadline (ISO 8601)"},"isContinuouslyHiring":{"type":"boolean","description":"Is continuously hiring"},"jobType":{"type":"string","enum":["full_time","part_time","summer_job","other"],"description":"Job type"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contact email"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Application link"},"classStart":{"type":"string","enum":["first","second","third","fourth","fifth","alumni"],"description":"Minimum year of study"},"classEnd":{"type":"string","enum":["first","second","third","fourth","fifth","alumni"],"description":"Maximum year of study"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Image URL"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Image alt text"},"createdById":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Creator user ID"},"createdAt":{"type":"string","description":"Creation time (ISO 8601)"},"updatedAt":{"type":"string","description":"Last update time (ISO 8601)"},"creator":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["id","name","email"]},{"type":"null"}],"description":"Creator user info"},"expired":{"type":"boolean","description":"Whether the job posting has expired"}},"required":["id","title","ingress","body","company","location","deadline","isContinuouslyHiring","jobType","email","link","classStart","classEnd","imageUrl","imageAlt","createdById","createdAt","updatedAt","creator","expired"]},"CreateJob":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"Job posting title"},"ingress":{"default":"","description":"Short description/summary","type":"string","maxLength":800},"body":{"default":"","description":"Full job description","type":"string"},"company":{"type":"string","minLength":1,"maxLength":200,"description":"Company name"},"location":{"type":"string","minLength":1,"maxLength":200,"description":"Job location"},"deadline":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$","description":"Application deadline"},"isContinuouslyHiring":{"default":false,"description":"Whether hiring is ongoing","type":"boolean"},"jobType":{"default":"other","description":"Type of employment","type":"string","enum":["full_time","part_time","summer_job","other"]},"email":{"description":"Contact email","type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"link":{"description":"Application or company URL","type":"string","format":"uri"},"classStart":{"default":"first","description":"Target class start","type":"string","enum":["first","second","third","fourth","fifth","alumni"]},"classEnd":{"default":"fifth","description":"Target class end","type":"string","enum":["first","second","third","fourth","fifth","alumni"]},"imageUrl":{"description":"Company logo","type":"string","format":"uri"},"imageAlt":{"description":"Logo alt text","type":"string","maxLength":255}},"required":["title","company","location","deadline"]},"JobListItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Job posting ID"},"title":{"type":"string","description":"Job title"},"ingress":{"type":"string","description":"Short description"},"body":{"type":"string","description":"Full job description"},"company":{"type":"string","description":"Company name"},"location":{"type":"string","description":"Job location"},"deadline":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Application deadline (ISO 8601)"},"isContinuouslyHiring":{"type":"boolean","description":"Is continuously hiring"},"jobType":{"type":"string","enum":["full_time","part_time","summer_job","other"],"description":"Job type"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contact email"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Application link"},"classStart":{"type":"string","enum":["first","second","third","fourth","fifth","alumni"],"description":"Minimum year of study"},"classEnd":{"type":"string","enum":["first","second","third","fourth","fifth","alumni"],"description":"Maximum year of study"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Image URL"},"imageAlt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Image alt text"},"createdById":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Creator user ID"},"createdAt":{"type":"string","description":"Creation time (ISO 8601)"},"updatedAt":{"type":"string","description":"Last update time (ISO 8601)"},"expired":{"type":"boolean","description":"Whether the job posting has expired"}},"required":["id","title","ingress","body","company","location","deadline","isContinuouslyHiring","jobType","email","link","classStart","classEnd","imageUrl","imageAlt","createdById","createdAt","updatedAt","expired"]},"JobList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"items":{"type":"array","items":{"$ref":"#/components/schemas/JobListItem"},"description":"List of job postings"}},"required":["totalCount","pages","nextPage","items"]},"UpdateJob":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"ingress":{"type":"string","maxLength":800},"body":{"type":"string"},"company":{"type":"string","minLength":1,"maxLength":200},"location":{"type":"string","minLength":1,"maxLength":200},"deadline":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"isContinuouslyHiring":{"type":"boolean"},"jobType":{"type":"string","enum":["full_time","part_time","summer_job","other"]},"email":{"anyOf":[{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"link":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"classStart":{"type":"string","enum":["first","second","third","fourth","fifth","alumni"]},"classEnd":{"type":"string","enum":["first","second","third","fourth","fifth","alumni"]},"imageUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"imageAlt":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}]}}},"DeleteJobResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"CalendarSubscription":{"type":"object","properties":{"url":{"type":"string","description":"Personal iCalendar (.ics) URL the user subscribes to in their calendar app. Contains a secret token — treat it like a password."}},"required":["url"]},"UserSettings":{"type":"object","properties":{"gender":{"type":"string","enum":["male","female","other"]},"allowsPhotosByDefault":{"type":"boolean"},"acceptsEventRules":{"type":"boolean"},"imageUrl":{"type":"string","format":"uri"},"bioDescription":{"type":"string"},"githubUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"linkedinUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"receiveMailCommunication":{"type":"boolean"},"publicEventRegistrations":{"default":true,"type":"boolean"},"allergies":{"default":[],"type":"array","items":{"type":"string"}},"isOnboarded":{"type":"boolean","description":"Whether the user has completed onboarding"}},"required":["gender","allowsPhotosByDefault","acceptsEventRules","receiveMailCommunication","isOnboarded"]},"UserSettingsBase":{"type":"object","properties":{"gender":{"type":"string","enum":["male","female","other"]},"allowsPhotosByDefault":{"type":"boolean"},"acceptsEventRules":{"type":"boolean"},"imageUrl":{"type":"string","format":"uri"},"bioDescription":{"type":"string"},"githubUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"linkedinUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"receiveMailCommunication":{"type":"boolean"},"publicEventRegistrations":{"default":true,"type":"boolean"},"allergies":{"default":[],"type":"array","items":{"type":"string"}}},"required":["gender","allowsPhotosByDefault","acceptsEventRules","receiveMailCommunication"]},"OnboardUserInput":{"type":"object","properties":{"gender":{"type":"string","enum":["male","female","other"]},"allowsPhotosByDefault":{"type":"boolean"},"acceptsEventRules":{"type":"boolean"},"imageUrl":{"type":"string","format":"uri"},"bioDescription":{"type":"string"},"githubUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"linkedinUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"receiveMailCommunication":{"type":"boolean"},"publicEventRegistrations":{"default":true,"type":"boolean"},"allergies":{"default":[],"type":"array","items":{"type":"string"}}},"required":["gender","allowsPhotosByDefault","acceptsEventRules","receiveMailCommunication"]},"UpdateUserSettings":{"type":"object","properties":{"gender":{"type":"string","enum":["male","female","other"]},"allowsPhotosByDefault":{"type":"boolean"},"acceptsEventRules":{"type":"boolean"},"imageUrl":{"type":"string","format":"uri"},"bioDescription":{"type":"string"},"githubUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"linkedinUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"receiveMailCommunication":{"type":"boolean"},"publicEventRegistrations":{"type":"boolean"},"allergies":{"type":"array","items":{"type":"string"}}}},"UpdateUserSettingsInput":{"type":"object","properties":{"gender":{"type":"string","enum":["male","female","other"]},"allowsPhotosByDefault":{"type":"boolean"},"acceptsEventRules":{"type":"boolean"},"imageUrl":{"type":"string","format":"uri"},"bioDescription":{"type":"string"},"githubUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"linkedinUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","const":""}]},"receiveMailCommunication":{"type":"boolean"},"publicEventRegistrations":{"type":"boolean"},"allergies":{"type":"array","items":{"type":"string"}}}},"SetPasswordResponse":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]},"SetPasswordInput":{"type":"object","properties":{"newPassword":{"type":"string","description":"The password to set. Length is checked against Better Auth's own bounds, since those are what sign-in enforces."}},"required":["newPassword"]},"UnansweredEvaluations":{"type":"array","items":{"type":"object","properties":{"formId":{"type":"string","description":"Form to answer"},"formTitle":{"type":"string"},"eventId":{"type":"string"},"eventTitle":{"type":"string"},"eventEndTime":{"type":"string","description":"When the event ended"}},"required":["formId","formTitle","eventId","eventTitle","eventEndTime"]}},"Allergy":{"type":"object","properties":{"slug":{"type":"string","description":"Unique identifier for the allergy"},"label":{"type":"string","description":"Display name of the allergy"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Detailed description of the allergy"}},"required":["slug","label","description"]},"AllergyList":{"type":"array","items":{"$ref":"#/components/schemas/Allergy"}},"RegisterUser":{"type":"object","properties":{"id":{"type":"string","description":"The new user's id"},"username":{"type":"string","description":"Derived from the e-mail's local part"},"email":{"type":"string"},"emailVerificationRequired":{"type":"boolean","const":true,"description":"Always true. A verification mail has been sent, and the account cannot sign in on tihlde.org until it is used."}},"required":["id","username","email","emailVerificationRequired"]},"RegisterUserInput":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"The member's full name"},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"The member's address. Must be @stud.ntnu.no unless 'username' is given, since the username is otherwise derived from it"},"password":{"type":"string","minLength":8,"maxLength":128,"description":"The password the member chose"},"studyProgramSlug":{"type":"string","minLength":1,"description":"Slug of the STUDY group to enrol the member in, e.g. 'dataingenir'"},"username":{"description":"The NTNU username to give the member, for callers registering students who have not been given their @stud.ntnu.no address yet. Should be the member's Feide username, so a later Feide login lands on this same account. Omit to derive it from the address.","type":"string","pattern":"^[a-z0-9]{1,15}$"}},"required":["name","email","password","studyProgramSlug"]},"UserSearchResult":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"username":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","username","image"]}},"UserListItem":{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User display name"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Username"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Profile image URL"},"studyProgram":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name of the user's study programme, derived from their STUDY group membership. Null when they have none."},"studyStartYear":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The year the user started studying (kull), derived from their STUDYYEAR group membership. Null when unknown."},"isActive":{"type":"boolean","description":"False when the account is deactivated — the member cannot sign in."},"approvalStatus":{"anyOf":[{"type":"string","enum":["pending","approved"]},{"type":"null"}],"description":"'pending' for a self-registered account still waiting for an admin, 'approved' once one said yes. Null for accounts that never needed approving — Feide logins and members migrated from Lepton."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The account's e-mail address. Shown to the admins who hold 'users:view', who need it both to judge a stranger waiting for approval and to get hold of an ordinary member."},"createdAt":{"type":"string","description":"Account creation timestamp"}},"required":["id","name","username","image","studyProgram","studyStartYear","isActive","approvalStatus","email","createdAt"]},"UserList":{"type":"object","properties":{"totalCount":{"type":"number","description":"Total number of items available"},"pages":{"type":"number","description":"Total number of pages available"},"nextPage":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The next page number that can be fetched"},"items":{"type":"array","items":{"$ref":"#/components/schemas/UserListItem"}}},"required":["totalCount","pages","nextPage","items"]},"UpdateStudyYear":{"type":"object","properties":{"message":{"type":"string"},"startYear":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The cohort year now stored"}},"required":["message","startYear"]},"UpdateStudyYearInput":{"type":"object","properties":{"startYear":{"anyOf":[{"type":"integer","minimum":2000,"maximum":3000},{"type":"null"}],"description":"Cohort start year, e.g. 2026. Null clears the cohort and removes the member's STUDYYEAR group."}},"required":["startYear"]},"UpdateStudy":{"type":"object","properties":{"message":{"type":"string"},"studyProgram":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name of the programme now stored"},"studyProgramSlug":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Slug of the programme now stored"}},"required":["message","studyProgram","studyProgramSlug"]},"UpdateStudyInput":{"type":"object","properties":{"studyProgramSlug":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Slug of the STUDY group the member should be on, e.g. 'dataingenir'. Replaces the one they have; null removes it."}},"required":["studyProgramSlug"]},"UserAllergies":{"type":"object","properties":{"allergies":{"type":"array","items":{"$ref":"#/components/schemas/Allergy"}}},"required":["allergies"]},"UpdateUserAllergiesInput":{"type":"object","properties":{"allergies":{"maxItems":50,"type":"array","items":{"type":"string","maxLength":64},"description":"Allergy slugs the member should have. Replaces the current set."}},"required":["allergies"]},"UpdateUserStatus":{"type":"object","properties":{"message":{"type":"string"},"isActive":{"type":"boolean"}},"required":["message","isActive"]},"UpdateUserStatusInput":{"type":"object","properties":{"isActive":{"type":"boolean","description":"False deactivates the account: the member can no longer sign in, and their sessions are ended."},"reason":{"description":"Why the account was deactivated. Ignored when activating.","type":"string","maxLength":500}},"required":["isActive"]},"ApproveUser":{"type":"object","properties":{"message":{"type":"string"},"approvalStatus":{"type":"string","const":"approved"}},"required":["message","approvalStatus"]},"UserProfile":{"type":"object","properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User display name"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's e-mail address. Only filled in for viewers holding 'users:view', and for the user themselves. Null for everyone else — it is not part of the public profile."},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Profile image URL — the uploaded avatar when there is one, otherwise the one from Feide"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text bio"},"githubUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Link to the user's GitHub profile"},"linkedinUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Link to the user's LinkedIn profile"},"studyProgram":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name of the user's study programme, derived from their STUDY group membership. Null when they have none."},"studyStartYear":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The year the user started studying (kull), derived from their STUDYYEAR group membership. Null when unknown."},"groups":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"role":{"type":"string"}},"required":["slug","name","type","logoUrl","role"]},"description":"Every group the user belongs to, including the derived STUDY/STUDYYEAR groups"},"formerGroups":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"role":{"type":"string"},"startedAt":{"type":"string"},"endedAt":{"type":"string"}},"required":["slug","name","type","logoUrl","role","startedAt","endedAt"]},"description":"Groups the user used to belong to, most recent first. Groups they rejoined appear under `groups` instead, and a group left more than once is listed once, by the latest stint. As public as the active memberships above."},"createdAt":{"type":"string","description":"Account creation timestamp"}},"required":["id","name","username","email","image","bio","githubUrl","linkedinUrl","studyProgram","studyStartYear","groups","formerGroups","createdAt"]}},"parameters":{"ListApplicationsQuery":{"in":"query","name":"type","schema":{"type":"string","enum":["expense","support","sports_support","hs_case","company_contact"]}}}}}