feat: implement automation key management with middleware and routes for organization API keys, invite improvements

This commit is contained in:
Rotimi Best
2026-03-13 18:40:07 +01:00
parent 77a1188573
commit 492f8dc5ed
71 changed files with 3655 additions and 456 deletions
@@ -9,7 +9,6 @@
"seconds": "seconds",
"snackbar_error": "error while sending code"
},
"email_verification": {
"errors": {
"invalid_token": {
@@ -52,21 +51,18 @@
},
"security_footer": "Your account security is our top priority. All verification attempts are logged for security monitoring."
},
"delete_modal": {
"label": "Delete",
"content": "Are you sure?",
"no": "No",
"yes": "Yes"
},
"new_course_modal": {
"live_class_label": "Live Class",
"live_class_subtitle": "Ideal for bootcamps where lessons are time based",
"self_paced_label": "Self Paced",
"self_paced_subtitle": "Best for courses where students can take lessons at their own pace"
},
"onboarding": {
"sell": "Sell courses online",
"teach": "Teach my current students online",
@@ -86,20 +82,17 @@
"back": "Back",
"continue": "Continue"
},
"welcome_modal": {
"we_at": "We at",
"small_team": "are a small team with a large mission to empower educators with great software to help them make an impact in people's lives. Education is the most powerful gift you can give anyone, and it makes perfect sense to provide you with all the tools you need to give this GIFT to more people.",
"thank_you": "Thank you for choosing us",
"deeply_appreciate": "we deeply appreciate it and are at your service."
},
"app": {
"search": {
"placeholder": "Search"
}
},
"login": {
"welcome": "Welcome back",
"email": "Your email",
@@ -142,7 +135,6 @@
"timer": "Activity timer",
"tic_tac": "Tictac toe",
"see_more": "See more",
"whats_new": "What's new",
"launch_week": "Launch week"
},
@@ -365,7 +357,6 @@
"video": "Video",
"image": "Image"
},
"show_banner": "Show banner",
"hide_banner": "Hide banner"
},
@@ -422,7 +413,6 @@
"twitter": "Twitter",
"linkedin": "LinkedIn"
},
"custom_links": {
"heading": "Custom Links",
"hide_links": "Hide Links",
@@ -458,7 +448,6 @@
"error": "An error occurred while opening billing"
}
},
"media_manager": {
"page_title": "Media Manager - ClassroomIO",
"heading": "Media Manager",
@@ -567,7 +556,6 @@
}
}
},
"tags_admin": {
"page_title": "Tags - ClassroomIO",
"heading": "Tags",
@@ -617,7 +605,6 @@
"no_groups": "Create a tag group before creating tags"
}
},
"public_courses": {
"page_title": "Courses - ClassroomIO",
"heading": "Courses",
@@ -633,7 +620,6 @@
"description": "Try clearing your filters or check back later."
}
},
"org_navigation": {
"platform": "Platform",
"dashboard": "Dashboard",
@@ -650,7 +636,6 @@
"unlock": "Unlock unlimited features and invest in our future",
"upgrade": "Upgrade Now"
},
"pricing": {
"plan_names": {
"free": "Free",
@@ -671,7 +656,6 @@
"per_org": "Per Organization"
}
},
"setup": {
"your_checklist": "Your Checklist",
"get_started": "Get Started",
@@ -711,7 +695,6 @@
"button_label": "Publish Course"
}
},
"add_org": {
"create_org": "Create Organization",
"name": "Name of Organization",
@@ -720,7 +703,6 @@
"sitename": "Sitename already exists",
"error_organization": "Something went wrong while creating this organization. Please reload and try again"
},
"navigation": {
"goto": "Go to",
"courses": "Courses",
@@ -739,12 +721,10 @@
"org_loading_state": "Taking you to your organization...",
"add_organization": "+ Add Organization"
},
"markdown_editor": {
"write": "Write",
"preview": "Preview"
},
"components": {
"quiz": {
"title": "Quizzes",
@@ -825,7 +805,6 @@
}
}
},
"schedule": {
"title": "Your Schedule",
"essence": "The essence of design collaboration",
@@ -833,7 +812,6 @@
"join": "Join Call",
"no": "No Lesson on this day"
},
"dashboard": {
"morning_heading": "Good Morning",
"afternoon_heading": "Good Afternoon",
@@ -868,7 +846,6 @@
"publish_first_course_description": "To track enrollments, publish a course and invite students",
"publish_course": "Publish Course"
},
"ai": {
"help_me": "Help me write",
"placeholder": "Help me write a short description",
@@ -878,7 +855,6 @@
"insert": "Insert",
"can_you": "Can you please rephrase this"
},
"courses": {
"heading": "Courses",
"heading_button": "Create Course",
@@ -947,7 +923,6 @@
}
}
},
"course": {
"navItems": {
"nav_news_feed": "News Feed",
@@ -972,7 +947,6 @@
"placeholder": "Search lessons or exercises",
"empty": "No results found"
},
"navItem": {
"news_feed": {
"heading": "News Feed",
@@ -1015,7 +989,6 @@
"fetching_error": "An error occured while fetching feed"
}
},
"lessons": {
"heading": "Lessons",
"heading_v2": "Content",
@@ -1078,7 +1051,6 @@
"no_tutor": "No tutor added",
"body_header": "No lessons yet",
"body_content": "Share your knowledge with the world by creating engaging lessons. Start by clicking on the Add button.",
"lesson_nav": {
"materials": "Materials",
"exercises": "Exercises"
@@ -1179,7 +1151,6 @@
"upload_error_try_again": "Please try again with a valid document file",
"try_again": "Try Again",
"upgrade": "Upgrade your plan to upload documents",
"cancel_upload": "Cancel Upload",
"upload_cancelled": "Upload cancelled by user",
"downloading": "Downloading...",
@@ -1197,7 +1168,6 @@
},
"button_done": "Done"
},
"exercises": {
"heading": "All Exercises",
"add_button": "Add",
@@ -1554,7 +1524,6 @@
"load_more_btn": "Load more",
"comment_error": "Failed to post comment. Please try again."
},
"polls": {
"title": "Poll",
"active_polls": "Active Polls",
@@ -1562,7 +1531,6 @@
"loading": "Loading...",
"no_polls": "No polls to display"
},
"poll": {
"voted": "Voted",
"created_by": "Created by",
@@ -1591,7 +1559,6 @@
"restore_version": "Restore this version"
}
},
"attendance": {
"title": "Attendance",
"present": "Present",
@@ -1601,7 +1568,6 @@
"lesson": "Lesson",
"no_student": "No Student Added"
},
"submissions": {
"title": "Submitted Exercises",
"submission_status": {
@@ -1640,7 +1606,6 @@
"delete_success": "Successfully deleted submission"
}
},
"marks": {
"title": "Marks",
"student": "Student",
@@ -1652,7 +1617,6 @@
},
"no_student": "No Student Added"
},
"people": {
"title": "People",
"add": "Add",
@@ -1761,7 +1725,6 @@
"remove": "Remove"
}
},
"certificates": {
"title": "Certificates",
"certificate_settings": "Certificate Settings",
@@ -1789,7 +1752,6 @@
"characters": "characters",
"unexpected_error": "An unexpected error occurred"
},
"landing_page": {
"editor": {
"title": {
@@ -1802,12 +1764,10 @@
"pricing": "Pricing",
"certificate": "Certificate"
},
"save": "Save",
"page_builder": "Page Builder",
"section": "section",
"display_section": "Display section",
"header_form": {
"title": "Title",
"description": "Description",
@@ -1816,7 +1776,6 @@
"replace": "Replace",
"helper": "Enter a link to a youtube video"
},
"reviews_form": {
"add_reviews": "Add Reviews",
"fullname": "Full Name",
@@ -1838,11 +1797,9 @@
}
}
},
"certificate_form": {
"upload_template": "Certificate template"
},
"instructor_form": {
"upload": "Instructor Logo",
"name": "Instructor name",
@@ -1852,7 +1809,6 @@
"about_placeholder": "A short background about the instructure",
"total": "Total number of courses"
},
"pricing_form": {
"currency": "Currency",
"cost": "Cost",
@@ -1864,7 +1820,6 @@
"gift": "About Gift"
}
},
"upload_widget": {
"title": "Banner Widget",
"width": "Images wider than 1500 pixels work best.",
@@ -1873,7 +1828,6 @@
"submit": "Submit",
"no_images": "No images available."
},
"pricing_section": {
"free": "Free",
"not_accepting": "This course is currently not accepting new students.",
@@ -1887,7 +1841,6 @@
"back_to_course": "Back to course",
"join_course": "Join Course"
},
"course_content": "Course content",
"modules": "modules",
"lessons": "lessons",
@@ -1923,7 +1876,6 @@
"explore": "Explore courses",
"find": "Find courses you will love from best teachers all over the world",
"find_course": "Find a course...",
"reviews_modal": {
"title": "Reviews",
"rating": "Rating",
@@ -1932,11 +1884,9 @@
"instructor": "Instructor",
"courses": "courses"
},
"analytics": {
"title": "Analytics"
},
"settings": {
"heading": "Settings",
"cover_image": "Cover Image",
@@ -1987,14 +1937,12 @@
"save": "Save Changes"
}
},
"not_permitted": {
"header": "No Permission",
"body": "Unfortunately you don't have access to this course.",
"button": "Go home"
}
},
"community": {
"title": "Community",
"ask_button": "Ask Community",
@@ -2027,7 +1975,6 @@
},
"all": "All"
},
"audience": {
"title": "Audience",
"export": "Export",
@@ -2038,7 +1985,6 @@
"date_joined": "Date Joined",
"upgrade": "Upgrade your plan to invite more students"
},
"analytics": {
"title": "Analytics",
"courses": "Courses",
@@ -2098,7 +2044,6 @@
"no_data_default": "No data available",
"no_data_default_description": "There is no data to display at this time."
},
"upgrade": {
"certificate": "Upgrade your plan to generate certificates",
"team": "Upgrade your plan to add team members",
@@ -2110,15 +2055,10 @@
"required": "Upgrade Required",
"enterprise_required": "This feature requires an Enterprise plan."
},
"automation": {
"title": "Automation",
"subtitle": "Connect MCP clients now, with Zapier and the public API following on the same org-scoped key system.",
"coming_soon": "Coming soon",
"overview": {
"title": "Automation access",
"description": "Create org-scoped automation keys, connect supported clients, and keep all machine access tied back to this organization."
},
"permissions": {
"disabled_title": "Read-only access",
"disabled_description": "Only organization admins can create, rotate, or revoke automation keys. Teachers can review the setup instructions here."
@@ -2150,35 +2090,19 @@
"mcp": {
"keys": {
"title": "MCP keys",
"description": "These keys authenticate the current organization when an MCP client calls ClassroomIO through `@cio/mcp`.",
"description": "These keys authenticate the current organization when an MCP client calls ClassroomIO through `@classroomio/mcp`.",
"generate": "Generate MCP key",
"scope_badge": "Draft create/read/update/publish",
"secret_once": "This secret is shown once. Copy it into your MCP client now."
},
"setup": {
"title": "Client setup",
"description": "Use one org-scoped key per integration. If a key is rotated or revoked, the client stops working until the new secret is configured.",
"claude_code_description": "Registers `@cio/mcp` with Claude Code using the generated org-scoped key.",
"claude_code_description": "Registers `@classroomio/mcp` with Claude Code using the generated org-scoped key.",
"codex_description": "Adds the ClassroomIO MCP server to Codex with the same org-scoped key.",
"cursor_description": "Paste this into `~/.cursor/mcp.json` or your workspace MCP config."
},
"flows": {
"title": "Typical flows",
"description": "The agent owns reasoning and structure. ClassroomIO only accepts normalized course payloads, saves drafts, and publishes on request.",
"prompt_title": "Prompt to draft",
"prompt_description": "The user asks for a course, the agent structures it, then `create_course_draft` saves it into ClassroomIO before any publish step.",
"pdf_title": "PDF to draft",
"pdf_description": "The agent reads the PDF externally, restructures it into sections and lessons, then sends the normalized draft into ClassroomIO.",
"review_title": "Review to publish",
"review_description": "The user asks for changes, the agent updates the draft, and publish only happens after explicit approval."
},
"architecture": {
"title": "Architecture",
"description": "The MCP client and ClassroomIO API are separate trust boundaries. The org-scoped automation key authenticates the client, and the API remains the source of truth for validation and persistence."
}
}
},
"lms_navigation": {
"home": "Home",
"my_learning": "My Learning",
@@ -2188,7 +2112,6 @@
"help": "Help",
"settings": "Settings"
},
"my_learning": {
"heading": "My Learning",
"progress": "In progress",
@@ -2199,7 +2122,6 @@
"not_completed": "No Course Completed",
"any_course": "Any course that you complete will be displayed here"
},
"exercises": {
"heading": "Exercises",
"not_submitted": "Not Submitted",
@@ -2208,13 +2130,11 @@
"graded": "Graded",
"lesson": "Lesson"
},
"explore": {
"heading": "Explore our courses",
"empty_heading": "No course available",
"empty_description": "There are currently no published course available"
},
"snackbar": {
"success": "Success",
"success_update": "Updated successfully",
@@ -2251,7 +2171,6 @@
"thumbnail_too_large": "Thumbnail image is too large",
"thumbnail_invalid": "Invalid thumbnail file"
},
"poll": {
"error": {
"creating_poll": "An error occurred while creating poll",
@@ -2263,19 +2182,16 @@
"vote_once": "You can only vote once"
}
},
"exercise": {
"success": "Saved Successfully",
"submission_updated": "Submission updated to",
"error_fetching": "Fetching exercises failed, please reload the page"
},
"materials": {
"apology": "We apologise, there was an error saving your lesson.",
"update_translations": "Updating translations failed",
"creating_new": "Creating new translations failed"
},
"newsfeed": {
"success": {
"edit": "Feed edited successfully",
@@ -2287,7 +2203,6 @@
"creating": "creating feed"
}
},
"people": {
"error": {
"missing_data": "Org data missing, please reload the page",
@@ -2297,7 +2212,6 @@
"copied": "Copied to clipboard"
}
},
"course_settings": {
"success": {
"download": "Download Complete",
@@ -2315,7 +2229,6 @@
"failed_deletion": "Deletion Failed, please try again later"
}
},
"lms": {
"error": {
"username_exists": "username already exists",
@@ -2323,7 +2236,6 @@
"try_again": "Please try again"
}
},
"team_members": {
"invite_sent": "Invite Sent!",
"user_exists": "User Already Exists",
@@ -2331,7 +2243,6 @@
"remove_fail": "Failed to remove user, please try again",
"remove_success": "Member removed successfully"
},
"tags": {
"group_created": "Tag group created",
"group_updated": "Tag group updated",
@@ -2341,7 +2252,6 @@
"fetch_failed": "Failed to load tags",
"save_failed": "Failed to save tag changes"
},
"landing_page_settings": {
"error": {
"file_size": "File Size must not be greater than 500kb",
@@ -2353,7 +2263,6 @@
"complete": "Complete :)"
}
},
"course": {
"success": {
"comment_deleted": "Comment Deleted",
@@ -2371,13 +2280,11 @@
"feed_fetch_error": "An error occured while fetching feed"
}
},
"attendance": {
"error": {
"reload": "Something isn't right. Please reload and take attendance again"
}
},
"lessons": {
"lesson_created": "Lesson created successfully",
"lesson_create_failed": "Failed to create lesson",
@@ -2410,28 +2317,23 @@
"exercise_not_complete": "Complete all exercises to mark as complete"
}
},
"marks": {
"error": "Error fetching marks"
},
"submissions": {
"success": {
"grading": "Grading saved and student notified"
}
},
"invite": {
"failed_join": "Joining failed, please contact your admin"
},
"community": {
"error": {
"try_again": "Error - Please try again later",
"deleting_comments": "Error deleting comments",
"deleting_question": "Error deleting question"
},
"success": {
"question_updated": "Question Updated!",
"comment_submitted": "Comment Submitted!",
@@ -2439,31 +2341,25 @@
"question_submitted": "Question Submitted!"
}
},
"signup": {
"disposable": "The email you provided is disposable"
},
"upgrade": {
"generate_fail": "Failed to generate success",
"failed": "Failed, please try again later"
},
"setup": {
"info_lesson": "You Need to Create a lesson",
"info_course": "You need to create a course"
},
"generic": {
"success_delete": "Deleted successfully",
"try_again": "Error - Please try again later"
}
},
"specialization": {
"course_tag": "Specialization"
},
"invite": {
"organization": {
"page_title": "Join {orgName} on ClassroomIO",
@@ -2481,18 +2377,15 @@
}
}
},
"content": {
"heading": "Internationalization in SvelteKit",
"toggle_label": "Select Preferred Language"
},
"generic": {
"locked": "Locked",
"unlocked": "Unlocked",
"loading": "Loading"
},
"validations": {
"user_profile": {
"email": "Invalid email address",
@@ -2619,7 +2512,6 @@
"invalid_field": "Invalid value"
}
},
"common": {
"unsaved_changes": {
"message": "You have unsaved changes. Are you sure you want to leave?"