# Create API key Source: https://docs.lyceum.technology/api-reference/api-keys/create-api-key /api-reference/openapi.json post /api/v2/external/orgs/{slug}/api-keys Create an org-scoped API key. The plaintext is returned once — never again. # List API keys Source: https://docs.lyceum.technology/api-reference/api-keys/list-api-keys /api-reference/openapi.json get /api/v2/external/orgs/{slug}/api-keys # Regenerate API key Source: https://docs.lyceum.technology/api-reference/api-keys/regenerate-api-key /api-reference/openapi.json post /api/v2/external/orgs/{slug}/api-keys/{key_id}/regenerate Issue a new secret for an existing key, keeping its name. The plaintext is returned once. # Revoke API key Source: https://docs.lyceum.technology/api-reference/api-keys/revoke-api-key /api-reference/openapi.json delete /api/v2/external/orgs/{slug}/api-keys/{key_id} # Toggle Api Key Source: https://docs.lyceum.technology/api-reference/api-keys/toggle-api-key /api-reference/openapi.json patch /api/v2/external/auth/api-keys/{api_key_id}/toggle Toggle API key active status. # Validate Api Key Source: https://docs.lyceum.technology/api-reference/api-keys/validate-api-key /api-reference/openapi.json post /api/v2/external/auth/api-keys/validate Validate an API key and return information about it. This endpoint allows you to check if an API key is valid without actually using it for authentication. Returns user information and key metadata if valid. # Login Source: https://docs.lyceum.technology/api-reference/authentication/login /api-reference/openapi.json post /api/v2/external/auth/login Login with email and password, return JWT tokens. This JWT can be used directly with MinIO STS. # Refresh Token Source: https://docs.lyceum.technology/api-reference/authentication/refresh-token /api-reference/openapi.json post /api/v2/external/auth/refresh Refresh an access token using a refresh token. # Aggregate transactions Source: https://docs.lyceum.technology/api-reference/billing/aggregate-transactions /api-reference/openapi.json get /api/v2/external/orgs/{slug}/transactions/aggregate Aggregate the org's debits for ``category``, grouped by ``group_by``. Example: ``?category=vms&group_by=hardware_profile`` returns one row per hardware profile with total spend + count. The set of valid ``group_by`` values depends on the category — invalid combinations return 400. # Create Auto Top Up Setup Intent Source: https://docs.lyceum.technology/api-reference/billing/create-auto-top-up-setup-intent /api-reference/openapi.json post /api/v2/external/billing/auto-top-up/setup-intent Open a SetupIntent so the org can save a card for off-session auto top-ups. Croesus owns the org's Stripe billing identity: it resolves (or creates) the customer and opens the SetupIntent. This endpoint just proxies the client secret to the dashboard. On confirmation Stripe fires ``setup_intent.succeeded`` which the webhook turns into a Croesus billing-profile update (the saved card). # Create Checkout Session Source: https://docs.lyceum.technology/api-reference/billing/create-checkout-session /api-reference/openapi.json post /api/v2/external/billing/checkout Create a Stripe checkout session for purchasing credits. The active org is captured in ``session.metadata.org_id`` so the webhook credits the org the buyer was in when they clicked "Buy" — not whichever org they happen to be in when Stripe fires the event. # Create Signup Grant Setup Intent Source: https://docs.lyceum.technology/api-reference/billing/create-signup-grant-setup-intent /api-reference/openapi.json post /api/v2/external/billing/signup-grant/setup-intent Open a Stripe SetupIntent so the user can verify a card and claim the $20 signup grant. Stripe runs Radar / CVC / AVS / 3DS checks during SetupIntent confirmation; on success Stripe fires ``setup_intent.succeeded`` which the webhook handler turns into an atomic ``claim_signup_grant`` call + Croesus credit (idempotency_key ``signup:``, matching the retired auth-hook path so double-claims via either path are impossible). # Get auto top-up Source: https://docs.lyceum.technology/api-reference/billing/get-auto-top-up /api-reference/openapi.json get /api/v2/external/orgs/{slug}/auto-top-up This org's auto top-up configuration (from Croesus); 404 if not configured yet. # Get balance Source: https://docs.lyceum.technology/api-reference/billing/get-balance /api-reference/openapi.json get /api/v2/external/orgs/{slug}/balance Current billing balance for this org (read from Croesus). # Get Billing Activities Source: https://docs.lyceum.technology/api-reference/billing/get-billing-activities /api-reference/openapi.json get /api/v2/external/billing/activities Get billing activities for the active org. The org-scoped RPC only emits execution rows. Credit purchases stay personal (top-ups + invoices are unchanged for now) and appear via ``/invoices`` instead. # Get Execution History Source: https://docs.lyceum.technology/api-reference/billing/get-execution-history /api-reference/openapi.json get /api/v2/external/billing/history Get execution history for the active org. # Get Invoices Source: https://docs.lyceum.technology/api-reference/billing/get-invoices /api-reference/openapi.json get /api/v2/external/billing/invoices Get user's credit purchase invoices/receipts. # Get payment method Source: https://docs.lyceum.technology/api-reference/billing/get-payment-method /api-reference/openapi.json get /api/v2/external/orgs/{slug}/payment-method The card saved for this org's auto top-up (display info only). has_card is false until a card has been saved via a SetupIntent. # Get usage Source: https://docs.lyceum.technology/api-reference/billing/get-usage /api-reference/openapi.json get /api/v2/external/orgs/{slug}/usage Per-user, per-model token usage for an inference ``kind``. Owners and admins see every member; a plain member sees only their own rows. Prompt and completion tokens are folded into one row per (user, model). Reads raw metered events from Croesus, so it covers all history and is independent of the billing ledger; ``deployment_id`` identifies the model. # Get User Credits Source: https://docs.lyceum.technology/api-reference/billing/get-user-credits /api-reference/openapi.json get /api/v2/external/billing/credits Get current user's credit balance. # Get User Credits Details Source: https://docs.lyceum.technology/api-reference/billing/get-user-credits-details /api-reference/openapi.json get /api/v2/external/billing/credits/details Get detailed user credits information including Stripe data. # List transactions Source: https://docs.lyceum.technology/api-reference/billing/list-transactions /api-reference/openapi.json get /api/v2/external/orgs/{slug}/transactions Org's billing-ledger transactions (read from Croesus, newest first). Filters: kind=credit|debit, source=, before= + before_id= (cursor; must be passed together), limit=<1..1000>. # Set auto top-up Source: https://docs.lyceum.technology/api-reference/billing/set-auto-top-up /api-reference/openapi.json put /api/v2/external/orgs/{slug}/auto-top-up Set this org's auto top-up configuration (writes to Croesus). Owner/admin only. # Chat Completions Source: https://docs.lyceum.technology/api-reference/chat-&-embeddings/chat-completions /api-reference/openapi.json post /openai/v1/chat/completions # Embeddings Source: https://docs.lyceum.technology/api-reference/chat-&-embeddings/embeddings /api-reference/openapi.json post /openai/v1/embeddings # List Models Source: https://docs.lyceum.technology/api-reference/chat-&-embeddings/list-models /api-reference/openapi.json get /openai/v1/models # Complete Compose execution Source: https://docs.lyceum.technology/api-reference/compose/complete-compose-execution /api-reference/openapi.json post /api/v2/external/execution/compose/complete/{execution_id} Complete Docker Compose execution endpoint called after streaming finishes. Updates the Supabase record with final output and status. # Start Compose execution Source: https://docs.lyceum.technology/api-reference/compose/start-compose-execution /api-reference/openapi.json post /api/v2/external/execution/compose/start Start Docker Compose execution and return direct streaming URL. The client can then connect directly to the streaming_url to receive output. # Complete Container execution Source: https://docs.lyceum.technology/api-reference/container/complete-container-execution /api-reference/openapi.json post /api/v2/external/execution/image/complete/{execution_id} Complete Docker execution endpoint called after streaming finishes. Updates the Supabase record with final output and status. # Start Container execution Source: https://docs.lyceum.technology/api-reference/container/start-container-execution /api-reference/openapi.json post /api/v2/external/execution/image/start Start Docker container execution and return direct streaming URL. The client can then connect directly to the streaming_url to receive output. # Create Dedicated Deployment Source: https://docs.lyceum.technology/api-reference/dedicated-inference/create-dedicated-deployment /api-reference/openapi.json post /api/v2/external/inference/create # Delete Dedicated Deployment Source: https://docs.lyceum.technology/api-reference/dedicated-inference/delete-dedicated-deployment /api-reference/openapi.json delete /api/v2/external/inference/stop # Get Dedicated Deployment Source: https://docs.lyceum.technology/api-reference/dedicated-inference/get-dedicated-deployment /api-reference/openapi.json get /api/v2/external/inference/get Get deployment details and all replicas. Accepts either: - User Bearer token — JWT is validated; user_id is taken from the token. - INFERENCE_PROXY_SERVICE_TOKEN — service token; caller must supply X-User-Id header. In both cases ownership is enforced: the deployment must belong to the resolved user_id. If include_terminated is False (default), returns 404 for deployments whose status is stopped. Set include_terminated=true to retrieve any deployment regardless of status. # Get deployment metrics Source: https://docs.lyceum.technology/api-reference/dedicated-inference/get-deployment-metrics /api-reference/openapi.json get /api/v2/external/inference/{deployment_id}/metrics # Get Replica Logs Source: https://docs.lyceum.technology/api-reference/dedicated-inference/get-replica-logs /api-reference/openapi.json get /api/v2/external/inference/replica/{replica_id}/logs Return the last container logs for a failed replica (owned by the requesting user). # Get replica machine metrics Source: https://docs.lyceum.technology/api-reference/dedicated-inference/get-replica-machine-metrics /api-reference/openapi.json get /api/v2/external/inference/replica/{replica_id}/machine-metrics Get GPU and system metrics from Prometheus for a specific inference replica. Mirrors GET /execution/{execution_id}/metrics but keyed on an inference deployment's replica. Uses replica.node_id as the Prometheus `instance` label. # List Dedicated Deployments Source: https://docs.lyceum.technology/api-reference/dedicated-inference/list-dedicated-deployments /api-reference/openapi.json get /api/v2/external/inference/list List dedicated deployments. Accepts either: - User Bearer token — JWT is validated; only deployments belonging to that user are returned. The user_id query param is ignored. - DEPLOYMENT_SCALER_SERVICE_TOKEN — service token; returns all deployments, or only those belonging to user_id if the query param is provided. If include_terminated is False (default), only non-stopped deployments are returned. Set include_terminated=true to include stopped/failed deployments. # Delete All Environment Variables Source: https://docs.lyceum.technology/api-reference/environment-variables/delete-all-environment-variables /api-reference/openapi.json delete /api/v2/external/environment-variables Delete all environment variables for the user. # Delete Environment Variable Source: https://docs.lyceum.technology/api-reference/environment-variables/delete-environment-variable /api-reference/openapi.json delete /api/v2/external/environment-variables/{env_var_id} Delete a single environment variable. # List Environment Variables Source: https://docs.lyceum.technology/api-reference/environment-variables/list-environment-variables /api-reference/openapi.json get /api/v2/external/environment-variables List all environment variables for the authenticated user. # Upsert Environment Variables Source: https://docs.lyceum.technology/api-reference/environment-variables/upsert-environment-variables /api-reference/openapi.json post /api/v2/external/environment-variables Create or update one or more environment variables. # Abort execution Source: https://docs.lyceum.technology/api-reference/executions/abort-execution /api-reference/openapi.json post /api/v2/external/workloads/abort/{execution_id} Abort a specific execution by setting cancel=true in the database. # Delete execution Source: https://docs.lyceum.technology/api-reference/executions/delete-execution /api-reference/openapi.json delete /api/v2/external/execution/{execution_id} Delete an execution record. # Get execution Source: https://docs.lyceum.technology/api-reference/executions/get-execution /api-reference/openapi.json get /api/v2/external/execution/{execution_id} Get execution details. # Get execution metrics Source: https://docs.lyceum.technology/api-reference/executions/get-execution-metrics /api-reference/openapi.json get /api/v2/external/execution/{execution_id}/metrics Get GPU and system metrics from Prometheus for a specific execution. This endpoint queries Grafana Cloud's Prometheus for GPU and system metrics associated with the execution's hostname (execution_owner). GPU Metrics (via DCGM): - gpuUtilizationPercent: GPU utilization percentage (0-100) - gpuMemoryUtilizationPercent: GPU memory utilization percentage (0-100) - gpuTemperatureCelsius: GPU temperature in Celsius - gpuPowerWatt: GPU power draw in watts - gpuPowerLimitWatt: GPU power limit in watts - gpuClockSmMhz: GPU SM clock speed in MHz - gpuClockMemMhz: GPU memory clock speed in MHz - gpuPcieThroughputRxBytesPerSec: PCIe receive throughput in bytes/sec - gpuPcieThroughputTxBytesPerSec: PCIe transmit throughput in bytes/sec System Metrics: - systemRamTotalBytes: Total system RAM in bytes - systemRamUsedBytes: Used system RAM in bytes - systemCpuUsagePercent: CPU usage percentage (0-100) # Get execution timing Source: https://docs.lyceum.technology/api-reference/executions/get-execution-timing /api-reference/openapi.json get /api/v2/external/execution/{execution_id}/timing Get execution timing details. # List executions Source: https://docs.lyceum.technology/api-reference/executions/list-executions /api-reference/openapi.json get /api/v2/external/workloads/list List all non-completed executions for the active org. # Stop execution Source: https://docs.lyceum.technology/api-reference/executions/stop-execution /api-reference/openapi.json post /api/v2/external/workloads/stop/{execution_id} Gracefully stop a running execution and mark it as completed. Unlike abort, this marks the execution as 'completed' rather than 'aborted'. Use this for interactive workloads like notebooks where stopping is a normal end-of-session action, not a cancellation. # Get Gpu Selection Status Source: https://docs.lyceum.technology/api-reference/gpu-selection/get-gpu-selection-status /api-reference/openapi.json get /api/v2/external/execution/gpu_selection/{execution_id}/status Get the current status of a GPU selection execution. # Start Gpu Selection Source: https://docs.lyceum.technology/api-reference/gpu-selection/start-gpu-selection /api-reference/openapi.json post /api/v2/external/execution/gpu_selection/start Start GPU selection execution. Creates a parent execution record and publishes the job to RabbitMQ for Pythia to process. Pythia will create sub-jobs on different GPUs and aggregate the results. # Ping Source: https://docs.lyceum.technology/api-reference/health/ping /api-reference/openapi.json get /api/v2/external/health/ping Health check endpoint for the Lyceum FastAPI Server. # Version Source: https://docs.lyceum.technology/api-reference/health/version /api-reference/openapi.json get /api/v2/external/health/version Get FastAPI and system version information. # Create Generation Source: https://docs.lyceum.technology/api-reference/image-generation/create-generation /api-reference/openapi.json post /api/v2/external/images/generations # List Models Source: https://docs.lyceum.technology/api-reference/image-generation/list-models /api-reference/openapi.json get /api/v2/external/images/models # Serve Image Source: https://docs.lyceum.technology/api-reference/image-generation/serve-image /api-reference/openapi.json get /api/v2/external/images/serve/{image_id} # Create Hardware Reminder Source: https://docs.lyceum.technology/api-reference/infra/create-hardware-reminder /api-reference/openapi.json post /api/v2/external/infra/hardware-reminders Save or update an opt-in to be notified when a hardware profile becomes available. # API Reference Source: https://docs.lyceum.technology/api-reference/introduction REST API for Lyceum Cloud The Lyceum Cloud REST API exposes every dashboard feature: launching runs and VMs, deploying inference, managing storage and secrets, billing, and account settings. Every endpoint is listed in the **Endpoints** group in the sidebar, generated directly from the OpenAPI spec. ## Base URL ``` https://api.lyceum.technology/api/v2/external ``` ## Authentication Every request takes a bearer token in the `Authorization` header. Two token types are accepted: | Token | Format | Lifetime | When to use | | ----------- | ------------ | -------------------------------------------- | ------------------------------------------------------ | | **API key** | `lk_...` | Long-lived, until revoked or expired | CLI, CI, scripts, integrations | | **JWT** | Standard JWT | Short-lived, refreshable via `/auth/refresh` | Interactive sessions, dashboard, browser-based testing | Generate API keys from the [API Keys](/docs/configuration/api-keys) page in the dashboard. The full key value is shown exactly once at creation, store it in a secret manager immediately. ```bash theme={null} curl https://api.lyceum.technology/api/v2/external/billing/credits \ -H "Authorization: Bearer lk_your_api_key" ``` ## Organization context Every billable resource (runs, VMs, deployments, balance, history) belongs to an [organization](/docs/account/organizations). The server resolves the org for each request in this order: 1. **API key org**, Keys created under `/orgs/{slug}/api-keys` carry the org. No header needed. 2. **`X-Org-Slug` header**, On JWT requests, pin the org by slug. The server verifies your membership. 3. **Default org**, If neither is present, the request falls back to the membership flagged as your default (auto-created at signup). ```bash theme={null} # JWT + explicit org curl https://api.lyceum.technology/api/v2/external/billing/credits \ -H "Authorization: Bearer eyJhbGciOi..." \ -H "X-Org-Slug: acme" ``` ## Login (JWT flow) ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/auth/login \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com", "password": "your-password"}' ``` The response includes `access_token` and `refresh_token`. Pass the access token as `Authorization: Bearer `. When it expires, call `POST /auth/refresh` with the refresh token to get a new pair. ## Validation errors Endpoints return HTTP `422` with a structured `HTTPValidationError` body when the request payload is malformed or missing required fields. Other failures return standard HTTP status codes (`400`, `401`, `403`, `404`, `5xx`) with a `detail` field describing the error. ## Endpoint groups The full endpoint list is in the sidebar under **Endpoints**, grouped by tag. Highlights: | Group | Purpose | Doc page | | ------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- | | **Authentication** | `/auth/login`, `/auth/refresh` | [Quickstart](/docs/quickstart) | | **Organizations** | `/orgs`, `/orgs/{slug}`, members, balance, transactions | [Organizations](/docs/account/organizations) | | **Org API Keys** | `/orgs/{slug}/api-keys/...` | [API Keys](/docs/configuration/api-keys) | | **Org Invites** | `/orgs/{slug}/invites/...`, `/invites/{token}`, `/invites/mine` | [Organizations](/docs/account/organizations#inviting-members) | | **Streaming Execution** | Submit Python runs, fetch status, abort | [Launch a Run](/docs/serverless/launch-run) | | **Docker Execution** | Submit Docker image runs | [Launch a Run](/docs/serverless/launch-run) | | **Docker Compose Execution** | Submit Compose stacks | [Launch a Run](/docs/serverless/launch-run) | | **GPU Selection Execution** | Fan out across GPU types | [Runs](/docs/serverless/runs) | | **Workload Management** | List, abort, stop runs | [Runs](/docs/serverless/runs) | | **Execution Management** | Get/delete a run, fetch timing | [Runs](/docs/serverless/runs) | | **Observability - Logs** | Loki-backed log queries | [Logs](/docs/observability/logs) | | **Observability - GPU Metrics** | DCGM and system metrics per execution | [GPU & System Metrics](/docs/observability/metrics) | | **Machine Types** | Hardware catalogue and pricing | [Launch a Run](/docs/serverless/launch-run) | | **Pricing** | Full price book across all meters (`/pricing`) | [Launch a Run](/docs/serverless/launch-run) | | **User Quotas** | Hardware profiles your account can use | [Settings](/docs/account/settings) | | **Storage Files** / **Storage Credentials** | Per-user S3 bucket | [Storage](/docs/configuration/storage) | | **Environment Variables** | Secrets injected into runs | [Secrets](/docs/serverless/secrets) | | **Dedicated Deployment External** | Create, get, list, stop dedicated deployments | [Dedicated Inference](/docs/inference/dedicated) | | **Billing** | Credits, history, invoices | [Billing](/docs/account/billing) | | **VMs** | Provision and manage GPU virtual machines | [Your VMs](/docs/instances/vms) | For end-to-end worked examples, submit a run, poll status, fetch logs and metrics, see [End-to-End API Workflow](/examples/api-integration). # Get Execution Logs Source: https://docs.lyceum.technology/api-reference/logs/get-execution-logs /api-reference/openapi.json get /api/v2/external/logs/execution/{execution_id} Get all logs for a specific execution. This endpoint queries Loki for all logs with the specified execution_id label. Access is gated on the execution belonging to the caller's active org, since Loki labels carry no org/user context to filter on. # Get User Logs Source: https://docs.lyceum.technology/api-reference/logs/get-user-logs /api-reference/openapi.json get /api/v2/external/logs/user Get all logs for the current user. This endpoint queries Loki for all logs with the user's user_id label. # Get Machine Types Source: https://docs.lyceum.technology/api-reference/machine-types/get-machine-types /api-reference/openapi.json get /api/v2/external/machine-types Get all machine types with their per-hour price, sourced from Croesus. # Accept Invite Source: https://docs.lyceum.technology/api-reference/org-invites/accept-invite /api-reference/openapi.json post /api/v2/external/invites/{token}/accept Accept an invite and become a member of the org. The caller must be signed in and their email must match the invite's email. # Accept My Invite Source: https://docs.lyceum.technology/api-reference/org-invites/accept-my-invite /api-reference/openapi.json post /api/v2/external/invites/mine/{invite_id}/accept Accept a pending invite by id. Caller's email must match the invite's. # List My Pending Invites Source: https://docs.lyceum.technology/api-reference/org-invites/list-my-pending-invites /api-reference/openapi.json get /api/v2/external/invites/mine Pending invites for the caller's email. Tokens not returned — accept via POST /invites/mine/{invite_id}/accept. # Preview Invite Source: https://docs.lyceum.technology/api-reference/org-invites/preview-invite /api-reference/openapi.json get /api/v2/external/invites/{token} Public preview of a pending invite for the accept page. No auth — the token itself is the capability. # Add Org Member Source: https://docs.lyceum.technology/api-reference/organizations/add-org-member /api-reference/openapi.json post /api/v2/external/orgs/{slug}/members Add a user to the org by email. If the email matches a Lyceum account, they're added directly and get a "you've been added" email. If not, a pending invite is created and an invite email is sent; signup auto-accepts via the user-created auth hook. # Create Org Source: https://docs.lyceum.technology/api-reference/organizations/create-org /api-reference/openapi.json post /api/v2/external/orgs Create a new org. The caller becomes its owner. # Delete Org Source: https://docs.lyceum.technology/api-reference/organizations/delete-org /api-reference/openapi.json delete /api/v2/external/orgs/{slug} # Get Org Source: https://docs.lyceum.technology/api-reference/organizations/get-org /api-reference/openapi.json get /api/v2/external/orgs/{slug} # List My Orgs Source: https://docs.lyceum.technology/api-reference/organizations/list-my-orgs /api-reference/openapi.json get /api/v2/external/orgs List every org the authenticated user belongs to, with their role. # List Org Invites Source: https://docs.lyceum.technology/api-reference/organizations/list-org-invites /api-reference/openapi.json get /api/v2/external/orgs/{slug}/invites List pending invites (not yet accepted) for this org. # List Org Members Source: https://docs.lyceum.technology/api-reference/organizations/list-org-members /api-reference/openapi.json get /api/v2/external/orgs/{slug}/members # Remove Org Member Source: https://docs.lyceum.technology/api-reference/organizations/remove-org-member /api-reference/openapi.json delete /api/v2/external/orgs/{slug}/members/{user_id} # Revoke Org Invite Source: https://docs.lyceum.technology/api-reference/organizations/revoke-org-invite /api-reference/openapi.json delete /api/v2/external/orgs/{slug}/invites/{invite_id} Revoke a pending invite. 404 if already accepted or missing. # Update Org Source: https://docs.lyceum.technology/api-reference/organizations/update-org /api-reference/openapi.json patch /api/v2/external/orgs/{slug} Patch the org's display fields and company billing details. The `id` never changes. # Update Org Member Role Source: https://docs.lyceum.technology/api-reference/organizations/update-org-member-role /api-reference/openapi.json patch /api/v2/external/orgs/{slug}/members/{user_id} # Get Pricing Source: https://docs.lyceum.technology/api-reference/pricing/get-pricing /api-reference/openapi.json get /api/v2/external/pricing List unit prices from Croesus, optionally filtered to one resource. # Complete Python execution Source: https://docs.lyceum.technology/api-reference/python/complete-python-execution /api-reference/openapi.json post /api/v2/external/execution/streaming/complete/{execution_id} Complete execution endpoint called by the extension after streaming finishes. Updates the Supabase record with final output and status. # Get Python execution status Source: https://docs.lyceum.technology/api-reference/python/get-python-execution-status /api-reference/openapi.json get /api/v2/external/execution/streaming/{execution_id}/status Get the current status of an execution. # Start Python execution Source: https://docs.lyceum.technology/api-reference/python/start-python-execution /api-reference/openapi.json post /api/v2/external/execution/streaming/start Start execution and return direct streaming URL. The client can then connect directly to the streaming_url to receive output. # Get Available Resources Source: https://docs.lyceum.technology/api-reference/resources/get-available-resources /api-reference/openapi.json get /api/v2/external/resources/available-resources List all available hardware resources including prices. # Get Storage Credentials Source: https://docs.lyceum.technology/api-reference/storage-credentials/get-storage-credentials /api-reference/openapi.json post /api/v2/external/storage/credentials Generate temporary MinIO/S3 storage credentials using STS. # Delete File Source: https://docs.lyceum.technology/api-reference/storage-files/delete-file /api-reference/openapi.json delete /api/v2/external/storage/delete/{file_key} Delete a file from user's S3 bucket. # Delete Folder Source: https://docs.lyceum.technology/api-reference/storage-files/delete-folder /api-reference/openapi.json delete /api/v2/external/storage/delete-folder/{folder_prefix} Delete all files in a folder (by prefix). # Download File Source: https://docs.lyceum.technology/api-reference/storage-files/download-file /api-reference/openapi.json get /api/v2/external/storage/download/{file_key} Download a file from user's S3 bucket. # List Files Source: https://docs.lyceum.technology/api-reference/storage-files/list-files /api-reference/openapi.json get /api/v2/external/storage/list-files List files in user's S3 bucket. # Upload Bulk Files Source: https://docs.lyceum.technology/api-reference/storage-files/upload-bulk-files /api-reference/openapi.json post /api/v2/external/storage/upload-bulk Upload multiple files at once, optionally preserving folder structure. # Upload File Source: https://docs.lyceum.technology/api-reference/storage-files/upload-file /api-reference/openapi.json post /api/v2/external/storage/upload Upload a file to user's S3 bucket. # Delete User Source: https://docs.lyceum.technology/api-reference/user-management/delete-user /api-reference/openapi.json delete /api/v2/external/user/delete Delete the current authenticated user's account. # Get User Status Source: https://docs.lyceum.technology/api-reference/user-management/get-user-status /api-reference/openapi.json get /api/v2/external/user/status Check user's authentication status and profile information. ``credit_balance`` reflects the **active org's** Croesus balance, not the caller's personal user_credits row. Pass ``X-Org-Slug`` to scope it. # Get Available Hardware Profiles Source: https://docs.lyceum.technology/api-reference/user-quotas/get-available-hardware-profiles /api-reference/openapi.json get /api/v2/external/user/quotas/available-hardware Get list of hardware profiles available to the user based on their quotas and pricing table. # Get User Quotas Source: https://docs.lyceum.technology/api-reference/user-quotas/get-user-quotas /api-reference/openapi.json get /api/v2/external/user/quotas Get user's machine type quotas and permissions. # Create Video Generation Source: https://docs.lyceum.technology/api-reference/video-generation/create-video-generation /api-reference/openapi.json post /api/v2/external/videos/generations # List Video Models Source: https://docs.lyceum.technology/api-reference/video-generation/list-video-models /api-reference/openapi.json get /api/v2/external/videos/models # Serve Video Source: https://docs.lyceum.technology/api-reference/video-generation/serve-video /api-reference/openapi.json get /api/v2/external/videos/serve/{video_id} # Create Vm Source: https://docs.lyceum.technology/api-reference/vms/create-vm /api-reference/openapi.json post /api/v2/external/vms/create Create a new VM with specified hardware requirements. Requires sufficient credits and calls the VM provisioning service. # Get Vm Availability Source: https://docs.lyceum.technology/api-reference/vms/get-vm-availability /api-reference/openapi.json get /api/v2/external/vms/availability Get hardware profiles from the VM provisioning service which the user is allowed to provision. Availability does not automatically imply that the requested hardware is actually available right now. # Get Vm Status Source: https://docs.lyceum.technology/api-reference/vms/get-vm-status /api-reference/openapi.json get /api/v2/external/vms/{vm_id}/status Get detailed status of a specific VM from VM provisioning service. # List Vms Source: https://docs.lyceum.technology/api-reference/vms/list-vms /api-reference/openapi.json get /api/v2/external/vms/list List VMs for the active org with optional status filtering. # Terminate Vm Source: https://docs.lyceum.technology/api-reference/vms/terminate-vm /api-reference/openapi.json delete /api/v2/external/vms/{vm_id} Terminate and delete a VM permanently. # Update Vm Source: https://docs.lyceum.technology/api-reference/vms/update-vm /api-reference/openapi.json patch /api/v2/external/vms/{vm_id} Update mutable fields on a VM. Currently only ``display_name``. # CLI Reference Source: https://docs.lyceum.technology/cli-reference/introduction Complete reference for the lyceum command-line tool The Lyceum CLI is a Python-based command-line tool that wraps the REST API. Every dashboard feature except inference deployment management is accessible from it. ## Install ```bash theme={null} pip install lyceum-cli ``` Requires Python 3.8 or newer. The package installs a single binary, `lyceum`. ## Authenticate ```bash theme={null} lyceum auth login ``` Logs you in via the dashboard browser flow and stores the token locally. For non-interactive environments, pass `--manual` to be prompted for tokens directly, or supply an API key with `--api-key lk_...`. ## Command groups | Group | Purpose | | --------------------------------- | ------------------------------------------------- | | [`auth`](#auth) | Sign in, sign out, check status | | [`python`](#python) | Submit Python runs | | [`docker`](#docker) | Submit Docker image runs | | [`compose`](#compose) | Submit Docker Compose runs | | [`gpu-selection`](#gpu-selection) | Fan out runs across GPU types | | [`notebook`](#notebook) | Launch and manage Jupyter notebook sessions | | [`workloads`](#workloads) | List, abort, and view history of runs | | [`infer`](#infer) | Deploy models, chat with deployments, manage them | | [`storage`](#storage) | Manage files in your storage bucket | | [`vm`](#vm) | Provision and manage GPU virtual machines | ## Global flags | Flag | Description | | ----------------- | --------------------- | | `--version`, `-v` | Print the CLI version | *** ## auth ### `lyceum auth login` Sign in to Lyceum Cloud. | Flag | Description | | ----------------------- | -------------------------------------------------- | | `--url ` | Override the API base URL (for development) | | `--dashboard-url ` | Override the dashboard URL | | `--manual` | Use manual token entry instead of the browser flow | | `--api-key ` | Sign in directly with an API key | ### `lyceum auth logout` Clear stored credentials. ### `lyceum auth status` Show the current authentication status. *** ## python ### `lyceum python run ` Execute a Python snippet or file on Lyceum Cloud. | Flag | Default | Description | | ------------------------------ | ------- | -------------------------------------------------------------- | | `-m`, `--machine ` | `cpu` | Machine type (`cpu`, `a100`, `h100`, ...) | | `-f`, `--file-name ` | — | Name for the execution | | `-r`, `--requirements ` | — | Requirements file path or pip requirements string | | `--import ` | — | Pre-import a module (repeatable) | | `--use-config` / `--no-config` | use | Read `.lyceum/config.json` workspace config | | `-d`, `--debug` | off | Show debug information about config, requirements, and payload | ```bash theme={null} lyceum python run script.py -m gpu.a100 -r requirements.txt ``` ### `lyceum python config init|show|refresh` Manage the workspace config at `.lyceum/config.json`. `init` creates one, `show` prints the current config, `refresh` regenerates it. *** ## docker ### `lyceum docker run ` Run a Docker container on Lyceum Cloud. | Flag | Default | Description | | -------------------------- | --------- | -------------------------------------------------- | | `-m`, `--machine ` | `cpu` | Machine type | | `-t`, `--timeout ` | `300` | Execution timeout in seconds | | `-f`, `--file-name ` | — | Name for the execution | | `-c`, `--command ` | — | Command to run in the container | | `-e`, `--env ` | — | Environment variable (repeatable) | | `-d`, `--detach` | off | Run container in background and print execution ID | | `--callback ` | — | Webhook URL for completion notification | | `--registry-creds ` | — | Registry credentials as a JSON string | | `--registry-type ` | — | Registry credential type (`basic`, `aws`) | | `--s3` / `--no-s3` | on | Mount your storage bucket inside the container | | `--s3-mount-path ` | `/mnt/s3` | Where to mount the bucket inside the container | | `--graceful-timeout ` | `10` | Seconds to wait for graceful shutdown on cancel | ```bash theme={null} lyceum docker run python:3.11-slim -c "python -c 'print(1+1)'" -m cpu ``` ### `lyceum docker logs ` Stream logs from a Docker run. ### `lyceum docker registry-examples` Print example registry credential payloads. *** ## compose ### `lyceum compose run ` Run a Docker Compose stack on Lyceum Cloud. | Flag | Default | Description | | -------------------------- | ------- | ----------------------------------------------- | | `-m`, `--machine ` | `cpu` | Hardware profile | | `-t`, `--timeout ` | `300` | Execution timeout in seconds | | `-f`, `--file-name ` | — | Display name for the run | | `-d`, `--detach` | off | Submit and return immediately | | `--callback ` | — | Webhook URL for completion notification | | `--registry-creds ` | — | Registry credentials as a JSON string | | `--registry-type ` | — | Registry credential type (`basic`, `aws`) | | `--graceful-timeout ` | `10` | Seconds to wait for graceful shutdown on cancel | ### `lyceum compose logs ` Stream logs from a Compose run. ### `lyceum compose registry-examples` Print example registry credential payloads. *** ## gpu-selection ### `lyceum gpu-selection run ` Submit Python code that fans out across GPU types so you can compare them. | Flag | Default | Description | | ------------------------------ | ------- | ------------------------------------ | | `-f`, `--file-name ` | — | Display name for the parent run | | `-t`, `--timeout ` | `60` | Per-sub-job timeout (1–600) | | `-r`, `--requirements ` | — | Path to a `requirements.txt` | | `--import ` | — | Pre-import a module (repeatable) | | `--use-config` / `--no-config` | use | Read `.lyceum/config.json` | | `--optimize ` | — | Optimisation objective for selection | | `-d`, `--debug` | off | Show debug information | ### `lyceum gpu-selection status ` Get the parent run status and per-sub-job results. *** ## notebook ### `lyceum notebook launch` Launch a Jupyter notebook server on Lyceum Cloud. Returns a URL you can open in a browser. | Flag | Default | Description | | ------------------------ | ------------------------------ | --------------------------- | | `-m`, `--machine ` | `cpu` | Hardware profile | | `-t`, `--timeout ` | `600` | Session timeout (max `600`) | | `-i`, `--image ` | `jupyter/base-notebook:latest` | Custom Jupyter image | | `--token ` | `lyceum` | Jupyter notebook token | | `-p`, `--port ` | `8888` | Port for the Jupyter server | ### `lyceum notebook list` List notebook sessions. ### `lyceum notebook stop ` Stop a running notebook session. *** ## workloads ### `lyceum workloads list` List your runs. | Flag | Default | Description | | ------------------- | ------- | ---------------------------- | | `-n`, `--limit ` | `10` | Number of executions to show | ### `lyceum workloads abort ` Hard-stop a run. The run is marked `aborted`. ### `lyceum workloads history` Show recent execution history. | Flag | Default | Description | | ------------------- | ------- | ---------------------------- | | `-n`, `--limit ` | `10` | Number of executions to show | *** ## infer ### `lyceum infer deploy ` Deploy a Hugging Face model as a dedicated inference endpoint. | Flag | Default | Description | | -------------------------- | ---------- | ----------------------------------------------- | | `-g`, `--gpu ` | `gpu.a100` | Hardware profile | | `-t`, `--hf-token ` | — | Hugging Face token (for gated models) | | `--min-replicas ` | `1` | Minimum replicas to keep running | | `--max-replicas ` | `1` | Maximum replicas allowed | | `--target-rps ` | `10.0` | Target requests/sec per replica for scale-up | | `--target-latency ` | `5000.0` | Target p95 latency in milliseconds for scale-up | | `--stabilisation ` | `300` | Scale-down stabilisation window | | `-w`, `--wait` | off | Block until the deployment has healthy replicas | ```bash theme={null} lyceum infer deploy meta-llama/Llama-3.1-8B-Instruct -g gpu.a100 --min-replicas 1 --max-replicas 3 ``` ### `lyceum infer status ` Get the status of a deployment. | Flag | Default | Description | | ------------- | ------- | --------------------------- | | `-a`, `--all` | off | Include stopped deployments | ### `lyceum infer stop ` Stop a deployment. ### `lyceum infer models` List available models. | Flag | Default | Description | | ------------- | ------- | --------------------------- | | `-a`, `--all` | off | Include stopped deployments | ### `lyceum infer chat` Send a chat completion to a deployed model. | Flag | Default | Description | | ------------------------- | ------- | ---------------------------------------------------- | | `-d`, `--deployment ` | — | Deployment ID to target | | `-m`, `--model ` | — | Alias for `--deployment` | | `-p`, `--prompt ` | — | Message text or path to a `.txt`/`.yaml`/`.xml` file | | `-i`, `--image ` | — | Image file path (for multimodal models) | | `--image-url ` | — | Image URL (for multimodal models) | | `-s`, `--system ` | — | System message | | `-t`, `--tokens ` | `1000` | Max output tokens | | `--temperature ` | `0.7` | Sampling temperature | | `-a`, `--async` | off | Submit async, return request ID immediately | | `--timeout ` | `60` | Request timeout (10–60) | ### `lyceum infer result ` Fetch the result of an async chat request. *** ## storage ### `lyceum storage ls [prefix]` List files in your bucket. | Flag | Default | Description | | ------------------- | ------- | -------------------------------- | | `-n`, `--max ` | `1000` | Maximum number of files to fetch | | `-r`, `--recursive` | off | List all files recursively | ### `lyceum storage load ` Upload a file or directory to your bucket. | Flag | Default | Description | | -------------------- | -------------------------- | --------------------------------------- | | `-k`, `--key ` | filename or directory name | Remote path/key inside the bucket | | `-r`, `--recursive` | off | Upload a directory recursively | | `-f`, `--force` | off | Skip confirmation for directory uploads | ### `lyceum storage download ` Download a file from your bucket. | Flag | Default | Description | | ----------------------- | -------- | ----------------- | | `-o`, `--output ` | filename | Local output path | ### `lyceum storage rm ` Delete a single file. | Flag | Default | Description | | --------------- | ------- | ----------------- | | `-f`, `--force` | off | Skip confirmation | ### `lyceum storage rmdir ` Delete every file under a prefix. | Flag | Default | Description | | --------------- | ------- | ----------------- | | `-f`, `--force` | off | Skip confirmation | *** ## vm ### `lyceum vm start` Provision a new VM instance. | Flag | Default | Description | | ------------------------------------ | ------------------ | --------------------------------------------------------- | | `-h`, `--hardware-profile ` | `a100` | Hardware profile (`cpu`, `a100`, `h100`, ...) | | `-k`, `--key ` | required | SSH public key for VM access | | `-g`, `--gpu-count ` | server default (1) | Number of GPUs in the VM | | `-a`, `--async` | off | Return immediately without waiting for the VM to be ready | ```bash theme={null} lyceum vm start -h h100 -k "$(cat ~/.ssh/id_ed25519.pub)" -g 1 ``` ### `lyceum vm list` List your VMs. By default, includes provisioning, ready, failed, and terminated VMs; toggle each with the corresponding flag. | Flag | Default | Description | | ------------------------------------------- | ------- | --------------------------------------------------------- | | `-p/-P`, `--provisioning/--no-provisioning` | on | Include VMs being set up (pending, provisioning, running) | | `-r/-R`, `--ready/--no-ready` | on | Include fully operational VMs | | `-f/-F`, `--failed/--no-failed` | on | Include failed VMs | | `-t/-T`, `--terminated/--no-terminated` | on | Include terminated VMs | ### `lyceum vm status ` Get detailed status for a VM, including IP and connection info. ### `lyceum vm availability` Check what hardware profiles are currently available to provision. ### `lyceum vm terminate ` Terminate a VM. | Flag | Default | Description | | --------------- | ------- | ----------------- | | `-f`, `--force` | off | Skip confirmation | # Billing Source: https://docs.lyceum.technology/docs/account/billing Credits, invoices, and execution history Lyceum Cloud is credit-based. You top up your balance through Stripe and the platform deducts credits as you run jobs, host VMs, or serve models. Credits don't expire and there's no monthly minimum, what you put in is what you have to spend. ## Credits live on the organization Every billable resource, a serverless run, a VM hour, an inference replica hour, is billed against an [organization](/docs/account/organizations). New accounts get an org created automatically at signup; credits granted to your account live on that org. If you belong to several orgs, each one has its own balance and history. The org a request bills against is resolved by: 1. The `org_id` carried by an API key created under `/orgs/{slug}/api-keys`. 2. The `X-Org-Slug` header on a JWT request. 3. Otherwise, your default org. The dashboard sets `X-Org-Slug` automatically based on the org switcher. ## How charging works Every billable action generates an entry in the org's history with the credit cost attached. Charges are pulled against the org's balance in real time, so you can watch your spend during long jobs and abort if needed. Two convenience views layer on top of the org ledger: * **Activities**, Combined timeline of debits and credit top-ups for the active org. * **History**, Per-execution breakdown for runs that have been billed. ## Topping up `POST /billing/checkout` creates a Stripe Checkout session for the active org. The session metadata captures the org id at creation time, so the webhook routes the credits to the right org even if you switch the active org before the payment completes. In the dashboard, the **Billing** page has both preset packages and a custom-amount input. ## REST API Top-level billing endpoints (active-org aware): | Method | Endpoint | Purpose | | ------ | -------------------------- | ----------------------------------------------------------- | | `GET` | `/billing/credits` | Current credit balance for the active org | | `GET` | `/billing/credits/details` | Detailed breakdown (free vs purchased, total used) | | `POST` | `/billing/checkout` | Create a Stripe Checkout session for the active org | | `GET` | `/billing/history` | Per-execution billing history for the active org | | `GET` | `/billing/activities` | Combined timeline of charges and top-ups for the active org | | `GET` | `/billing/invoices` | Stripe invoices (still scoped to your user, not the org) | Org-scoped reads, the underlying ledger pulled straight from the credit system: | Method | Endpoint | Purpose | | ------ | ------------------------------------- | ---------------------------------------------------------------------------------------------- | | `GET` | `/orgs/{slug}/balance` | Org balance and suspension status | | `GET` | `/orgs/{slug}/transactions` | Newest-first ledger with `kind`, `source`, cursor, and `limit` filters | | `GET` | `/orgs/{slug}/transactions/aggregate` | Group debits by `vm_id`, `hardware_profile`, `execution_id`, or `deployment_id` for a category | See [Organizations, Billing](/docs/account/organizations#billing) for the category and `group_by` reference. # Organizations Source: https://docs.lyceum.technology/docs/account/organizations Group members, share credits, and scope API keys to an org An organization (org) is a shared workspace. Members of an org share its credit balance and its API keys, and every billable resource, runs, VMs, deployments, is tagged with the org that ran it. Every Lyceum account has one org created automatically at signup; you can stay on that single org or create additional ones (for a team, a client project, a separate billing pool). ## Concepts * **Default org**, Every user has exactly one membership flagged as their default. Requests that don't pin an org fall back to it. The default is created at signup and you must always have one. * **Active org**, The org the dashboard (or CLI) is currently acting on. The org switcher in the sidebar changes the active org; under the hood the dashboard sends `X-Org-Slug: ` on every request. * **Roles**, Each member of an org has one of three roles: | Role | Can do | | -------- | -------------------------------------------------------------------------- | | `owner` | Everything: change roles, edit org name/slug/billing email, delete the org | | `admin` | Manage members, invites, and API keys | | `member` | Read org details, see balance, use the org's API keys and credits | * **Slug**, A short URL-safe identifier (`a-z`, `0-9`, `-`). Slugs are unique platform-wide and stable URLs in the dashboard (`/orgs/{slug}`). They can be renamed by an owner. ## Using the dashboard The org switcher lives in the sidebar. Click it to: * See every org you belong to and your role in each * Switch the active org, every page in the dashboard refetches against the new org * Open **Organizations** to create a new org, see pending invites you've received, or jump into an org's settings The **Organization** settings page (`/orgs/{slug}`) shows the members table, pending invites, and the org's current balance. Owners get extra controls for renaming the org, changing the billing email, and deleting it. ## Inviting members Add someone by email from the org settings page (or `POST /orgs/{slug}/members`). The response shape depends on whether the invitee already has a Lyceum account: * **Existing user**, They're added directly. They receive a "you've been added to *Org Name*" email and the invite shows `status: "added"`. * **New user**, A pending invite is created and an invite email is sent. The response is `status: "invited"`. The link in that email goes to `https://dashboard.lyceum.technology/invite/{token}`. When the user signs up or signs in with the same email, the invite is auto-accepted by the auth hook. Pending invites can be revoked at any time (`DELETE /orgs/{slug}/invites/{invite_id}`). Once a user accepts, the row is kept for audit and a new pending invite can be issued. Only one pending invite per (org, email) can exist at a time. Revoke the old one first if you need to re-send. ## Org context on API requests Every request that touches a billable resource (runs, VMs, deployments, balance, history) is resolved against an org using this order: 1. **API key with an embedded `org_id`**, API keys created under `/orgs/{slug}/api-keys` are pinned to that org. Authentication and org context come from the key in a single step. 2. **JWT + `X-Org-Slug` header**, If you authenticated with a JWT, pass the slug of the org you want to act on as `X-Org-Slug: `. The server verifies that you're a member of that org. 3. **Fallback**, If neither is provided, the request falls back to your default org. ```bash theme={null} # Explicit org via header curl https://api.lyceum.technology/api/v2/external/billing/credits \ -H "Authorization: Bearer eyJhbGciOi..." \ -H "X-Org-Slug: acme" # Org pinned by the API key (no header needed) curl https://api.lyceum.technology/api/v2/external/billing/credits \ -H "Authorization: Bearer lk_..." ``` ## CLI Org management commands live under `lyceum org`. The CLI tracks the active org in `~/.lyceum/config.json`; once set, it sends `X-Org-Slug` on every subsequent request. ```bash theme={null} # Switch active org lyceum org list lyceum org use acme # Pin acme as active lyceum org current # Show what's currently active lyceum org unset # Fall back to your default org # Create / inspect / update / delete lyceum org create "Acme Corp" --slug acme --billing-email billing@acme.com lyceum org show # Defaults to active org lyceum org update --name "Acme Inc" lyceum org delete acme # Balance and ledger lyceum org balance lyceum org transactions list --kind debit --limit 50 lyceum org transactions aggregate --category vms --group-by hardware_profile # Members lyceum org members list lyceum org members add user@example.com --role member lyceum org members role --role admin lyceum org members remove # Invites lyceum org invites list # Pending invites for this org lyceum org invites revoke lyceum org invites mine # Invites sent to your email lyceum org invites accept # Org-scoped API keys lyceum org keys list lyceum org keys create "ci-pipeline" --expires-at 2027-01-01T00:00:00Z lyceum org keys revoke ``` You can also override the active org for one command without changing the pin: ```bash theme={null} lyceum --org other-team workloads list # or set LYCEUM_ORG=other-team ``` ## Billing Credits are tracked per org. The org's billing balance, transaction ledger, and category aggregates are available at: | Method | Endpoint | Description | | ------ | ------------------------------------- | -------------------------------------------------------------------------------------- | | `GET` | `/orgs/{slug}/balance` | Current credit balance and suspension status | | `GET` | `/orgs/{slug}/transactions` | Newest-first ledger; filters: `kind`, `source`, cursor (`before`+`before_id`), `limit` | | `GET` | `/orgs/{slug}/transactions/aggregate` | Group debits by a metadata key for a category | Aggregate categories and the metadata keys you can group by: | `category` | What it covers | Valid `group_by` | | ---------------------------------------- | -------------------------------------- | ---------------------------------- | | `vms` | VM running time | `vm_id`, `hardware_profile` | | `executions` | Serverless run execution | `execution_id`, `hardware_profile` | | `inference_prompt_tokens` | Dedicated inference prompt tokens | `deployment_id` | | `inference_completion_tokens` | Dedicated inference completion tokens | `deployment_id` | | `serverless_inference_prompt_tokens` | Serverless inference prompt tokens | `deployment_id` | | `serverless_inference_completion_tokens` | Serverless inference completion tokens | `deployment_id` | Stripe top-ups credit the org that was active when the checkout session was created, the org id is captured in the Stripe session metadata so the webhook routes credits back to the right org even if the user switches active org before the payment completes. See [Billing](/docs/account/billing) for top-ups, history, and invoices. ## REST API ### Orgs | Method | Endpoint | Role required | | -------- | -------------- | ------------------------------------ | | `GET` | `/orgs` | Authenticated | | `POST` | `/orgs` | Authenticated (caller becomes owner) | | `GET` | `/orgs/{slug}` | owner / admin / member | | `PATCH` | `/orgs/{slug}` | owner | | `DELETE` | `/orgs/{slug}` | owner | `DELETE /orgs/{slug}` returns `409` if any member has the org as their default, or if the org still has live VMs, runs, or deployments. ### Members | Method | Endpoint | Role required | | -------- | -------------------------------- | ------------- | | `GET` | `/orgs/{slug}/members` | owner / admin | | `POST` | `/orgs/{slug}/members` | owner / admin | | `PATCH` | `/orgs/{slug}/members/{user_id}` | owner | | `DELETE` | `/orgs/{slug}/members/{user_id}` | owner / admin | `DELETE` is rejected with `400` if the target is the last owner, and `409` if the org is the target's default org. ### Invites | Method | Endpoint | Auth | | -------- | ---------------------------------- | -------------------------------- | | `GET` | `/orgs/{slug}/invites` | owner / admin | | `DELETE` | `/orgs/{slug}/invites/{invite_id}` | owner / admin | | `GET` | `/invites/mine` | Authenticated | | `POST` | `/invites/mine/{invite_id}/accept` | Authenticated (email must match) | | `GET` | `/invites/{token}` | Public (token is the capability) | | `POST` | `/invites/{token}/accept` | Authenticated (email must match) | ### Org-scoped API keys | Method | Endpoint | Role required | | -------- | ------------------------------------------- | ------------- | | `GET` | `/orgs/{slug}/api-keys` | owner / admin | | `POST` | `/orgs/{slug}/api-keys` | owner / admin | | `POST` | `/orgs/{slug}/api-keys/{key_id}/regenerate` | owner / admin | | `DELETE` | `/orgs/{slug}/api-keys/{key_id}` | owner / admin | The plaintext key is returned exactly once at creation or regeneration, store it immediately. See [API Keys](/docs/configuration/api-keys) for details. # Settings Source: https://docs.lyceum.technology/docs/account/settings Profile, security, quotas, and account management Settings groups account-level configuration: your profile, security (password), the hardware quotas your account is authorised to use, and account deletion. Workspace-level settings, members, billing email, org-scoped API keys, live on each organization's settings page. See [Organizations](/docs/account/organizations). ## Quotas Your account has a quota for each hardware profile. Access to each profile is granted by Lyceum based on your usage and any agreements in place. Quotas exist to prevent accidental over-spend and to keep capacity available for everyone. | Method | Endpoint | Returns | | ------ | --------------------------------- | ------------------------------------------ | | `GET` | `/user/quotas` | Boolean per hardware profile | | `GET` | `/user/quotas/available-hardware` | Just the profiles you can launch right now | If you need a profile you don't have access to, contact [support@lyceum.technology](mailto:support@lyceum.technology). ## Account lifecycle | Method | Endpoint | Purpose | | -------- | -------------- | ------------------------------- | | `GET` | `/user/status` | Current user profile | | `DELETE` | `/user/delete` | Permanently delete your account | Account deletion is irreversible. It removes your runs, VMs, deployments, storage, secrets, API keys, and billing history. Export anything you want to keep first. # Single Sign-On (SSO) Source: https://docs.lyceum.technology/docs/account/sso Let everyone at your company sign in to Lyceum through your own identity provider ## Overview SSO lets your team sign in to Lyceum through your company's own identity provider (IdP), for example Okta, instead of a Lyceum-specific password. It's set up once per email domain by an org owner, and from then on anyone signing in from your IdP with an email at that domain is automatically added to your organization on their first login, no invite required. SSO is configured under **Organization → SSO** in the dashboard, and is only available to the org's **owner**. Read the warnings below before you start. A couple of the choices here (removing a domain, linking an old account) are permanent and can't be undone from the dashboard once made. ## Before you start **Keep the org owner's password account.** Don't convert your own (the owner's) account to SSO-only. If your identity provider is ever misconfigured, or an SSO domain gets accidentally removed, an owner with no password fallback can be locked out of their own organization with no self-service way back in. Everyone else on the team can safely be SSO-only; the owner is the one account worth keeping a password on as a safety net. **An SSO account can never sign in with a password, even if one is set for it later.** This is a deliberate security choice. It's what makes disabling someone's account at your identity provider actually disable their Lyceum access too, rather than leaving a password-based backdoor open. If someone already had a Lyceum password account before your company turned on SSO, see [Linking an existing account](#linking-an-existing-account) below, they're a different, separate account by default. **Linking an old account is permanent, and safest right after your very first SSO sign-in.** If a teammate already had a Lyceum account (password-based) from before SSO was enabled, they can link it to their new SSO identity to bring over its VMs, API keys, billing history, and org role. The old account is deleted once the link completes, this can't be undone. Do it immediately after your first SSO sign-in and before using the new account for anything else: some of what carries over favors whichever account is older, so mixing real usage across both accounts before linking can leave things in a state you didn't intend. **Removing a domain's SSO can permanently lock people out.** Anyone who signed in via SSO loses their only way to sign in the moment the domain's SSO is removed. Their data isn't deleted, but recovering access at that point needs Lyceum support, not something they (or you) can do from the dashboard. ## Setting up SSO for your domain ### 1. Claim your domain From **Organization → SSO**, enter the email domain you want to enable (e.g. `acme.com`) and choose the **default role** new SSO logins are granted: `admin` or `member`. Owner isn't an option here on purpose, granting it automatically to anyone who happens to sign in from your domain would be a serious privilege-escalation risk, so if someone needs to be an owner, promote them manually afterward from the members list. This reserves the domain and gives you a DNS TXT record to publish. ### 2. Verify domain ownership Publish the TXT record shown (record name and value) at your DNS provider, then click **Verify**. DNS changes can take a few minutes to propagate; if verification fails immediately after publishing, wait a bit and retry. ### 3. Create a SAML app with your identity provider Once verified, the dashboard shows the two values your IdP needs: * **ACS URL** (Single sign-on URL) * **SP Entity ID** (Audience URI) Create a new SAML 2.0 app in your IdP (in Okta: **Applications → Create App Integration → SAML 2.0**) and enter both. Also set **Name ID format** to **EmailAddress** in your IdP's app settings. Most IdPs default new apps to "Unspecified," which looks like it should work (the value is still your email) but won't. Assign the users (or groups) who should have access to this app in your IdP. ### 4. Activate Your IdP will give you SAML metadata, either a URL or an XML document, once the app is set up. Paste it back into the dashboard and click **Activate**. The domain is now `active`, and anyone signing in from that IdP with a matching email will be provisioned automatically. ## How your team signs in Once active, there are two ways to reach SSO sign-in: * **From your IdP directly.** Set up a bookmark/link tile in your IdP pointing at `https://dashboard.lyceum.technology/sso-login?domain=`. Clicking it, already authenticated with your IdP, lands the user signed into Lyceum with no further prompts. * **From the Lyceum login page.** Click **SSO**, enter your work email, and you'll be redirected to your company's IdP to complete sign-in. The first time someone signs in this way, Lyceum creates their account and adds them to your org with the default role you configured. Every sign-in after that just logs them back into the same account. ## Linking an existing account If someone already has a password-based Lyceum account at the same email as their new SSO identity, they can bring its data over themselves, from **Account → Security → Link an old account**, once signed in with SSO: 1. Sign in with SSO (creates the new account, if this is the first time). 2. Go to **Account → Security → Link an old account**. 3. Enter the old account's password. 4. Confirm. VMs, API keys, billing history, and org role move onto the new SSO account. The old account is deleted. We require the old account's actual password as proof of ownership, specifically so that nobody can claim someone else's account just because their IdP happens to assert the same email address. Do this right after the first SSO sign-in, per the warning above, before the new account accumulates any usage of its own. ## Turning off SSO for a domain Remove the domain from **Organization → SSO**. This immediately stops new and existing SSO sign-ins for that domain. Anyone who never had a password on their account loses access entirely at that point, see the warning above before doing this. ## REST API Base URL: `https://api.lyceum.technology/api/v2/external`. All domain endpoints require the caller to be the org's **owner**. ### SSO domains | Method | Endpoint | Description | | -------- | ----------------------------------------------- | -------------------------------------------------- | | `GET` | `/orgs/{slug}/sso/domains` | List the org's SSO domains | | `POST` | `/orgs/{slug}/sso/domains` | Claim a domain; returns a DNS TXT challenge | | `POST` | `/orgs/{slug}/sso/domains/{domain_id}/verify` | Check the TXT record; `422` if not found yet | | `PUT` | `/orgs/{slug}/sso/domains/{domain_id}/provider` | Register the IdP's SAML metadata and activate | | `DELETE` | `/orgs/{slug}/sso/domains/{domain_id}` | Remove the domain and deregister its SAML provider | ```bash theme={null} # Claim a domain curl -X POST https://api.lyceum.technology/api/v2/external/orgs/acme/sso/domains \ -H "Authorization: Bearer eyJhbGciOi..." \ -H "Content-Type: application/json" \ -d '{"domain": "acme.com", "default_role": "member"}' # Activate once verified, with your IdP's metadata curl -X PUT https://api.lyceum.technology/api/v2/external/orgs/acme/sso/domains/{domain_id}/provider \ -H "Authorization: Bearer eyJhbGciOi..." \ -H "Content-Type: application/json" \ -d '{"metadata_url": "https://your-okta-tenant.okta.com/app/.../sso/saml/metadata"}' ``` A claimed domain's response includes the DNS challenge (while `pending`) and the ACS URL / SP Entity ID your IdP needs (from `verified` onward): ```json theme={null} { "id": "...", "domain": "acme.com", "status": "verified", "default_role": "member", "acs_url": "https://auth.lyceum.technology/auth/v1/sso/saml/acs", "sp_entity_id": "https://auth.lyceum.technology/auth/v1/sso/saml/metadata", "verification_token": null, "verification_record_name": null } ``` ### Linking an account | Method | Endpoint | Auth | | ------ | --------------------- | -------------------------------------------------------- | | `POST` | `/auth/merge-account` | Authenticated (must be signed in as the new SSO account) | ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/auth/merge-account \ -H "Authorization: Bearer eyJhbGciOi..." \ -H "Content-Type: application/json" \ -d '{ "old_email": "you@acme.com", "old_password": "your-old-password", "captcha_token": "...", "old_mfa_code": "123456" }' ``` `old_email` must match the caller's own current email. `old_mfa_code` is only required if the old account has two-factor authentication enabled. # aider Source: https://docs.lyceum.technology/docs/agents/aider Configure aider to pair program with Lyceum inference models [aider](https://aider.chat) is a terminal pair programmer that edits files in your git repo. It reaches Lyceum through its OpenAI-compatible settings. ## Install aider does not yet run on Python 3.13. The install fails with `ModuleNotFoundError: No module named 'pyaudioop'`. Pin 3.12: ```bash theme={null} uv tool install --python 3.12 aider-chat ``` ## Configure Or make it permanent in `~/.aider.conf.yml`: The model needs an `openai/` prefix. aider routes through LiteLLM, which uses that prefix to pick the OpenAI-compatible transport. Write `openai/z-ai/glm-5.2`, not `z-ai/glm-5.2`. ## One-shot edits Useful in scripts and CI: ## Notes * **Editor vs. main model.** `--editor-model` can point at a cheaper model for applying diffs while the main model plans: `--model openai/z-ai/glm-5.2 --editor-model openai/deepseek/deepseek-v4-flash-0731`. * **Token counts.** aider prints its own token accounting; billed usage is in the [dashboard](https://dashboard.lyceum.technology). # Cline Source: https://docs.lyceum.technology/docs/agents/cline Configure the Cline VS Code extension to use Lyceum inference models [Cline](https://cline.bot) is an autonomous coding agent for VS Code. It connects to Lyceum through its OpenAI Compatible provider. ## Install 1. Open the VS Code Extensions panel. 2. Search for **Cline** and click **Install**. 3. Restart VS Code. ## Configure Click the Cline icon in the VS Code sidebar. On first run choose **Use your own API key**; otherwise click the gear icon. Set **API Provider** to **OpenAI Compatible**. Expand **Model Configuration** and set **Context Window** and **Max Output Tokens** for your chosen model. See [active models](/docs/inference/active-models). Leave **Image Support** off unless you picked a vision model. Click **Let's go!**, then **Done**. ## Recommended models Cline runs long tool-calling loops, so pick a model that is strong at agentic work: | Model | Why | | --------------------------- | --------------------------- | | `moonshotai/kimi-k2.7-code` | Built for agentic coding | | `z-ai/glm-5.2` | Strongest general reasoning | | `deepseek/deepseek-v4-pro` | Cheaper reasoning | ## Notes * **Tool calling is required.** Cline drives edits through function calls; every model listed above supports them. * **Context window is not auto-detected.** Our `/models` endpoint returns IDs only, so Cline cannot infer limits. If you leave the default, Cline may truncate context earlier than necessary or overrun the model. * **Costs.** Cline's cost estimate uses the pricing you enter in its settings, not Lyceum's billing. Your real usage is in the [dashboard](https://dashboard.lyceum.technology). # GitHub Copilot Source: https://docs.lyceum.technology/docs/agents/github-copilot Use Lyceum inference models in GitHub Copilot Chat with a custom endpoint GitHub Copilot Chat in VS Code can bring your own key. Lyceum connects through its **Custom Endpoint** provider. ## Configure Run **Chat: Manage Language Models** from the command palette, or click the gear icon in the chat model picker. Choose **Add Models → Custom Endpoint**, and set the API type to **Chat Completions**. Select it from the model picker in the Copilot Chat panel. ## Two Lyceum-specific details Copilot is stricter than most clients, so two things differ from the other pages here. **Use slash-free model IDs.** Copilot rejects `/` in custom model names. Every Lyceum model has a slash-free alias. Replace each `/` with `-`: | Canonical | Use in Copilot | | --------------------------- | --------------------------- | | `z-ai/glm-5.2` | `z-ai-glm-5.2` | | `moonshotai/kimi-k2.7-code` | `moonshotai-kimi-k2.7-code` | | `deepseek/deepseek-v4-pro` | `deepseek-deepseek-v4-pro` | Both forms resolve to the same model; the alias is resolved before any model lookup. **Prefer `z-ai-glm-5.2-instant` over `z-ai-glm-5.2`.** GLM 5.2 is a reasoning model and returns its trace in the non-standard `reasoning_content` field. Copilot cannot pass request parameters to turn that off, and it caps `max_tokens`, so the reasoning can consume the whole output budget and the reply arrives empty. The `-instant` variant is the same model with thinking disabled, which is what you want here. ## Anthropic mode Copilot's Custom Endpoint also speaks the Anthropic Messages API. To use Lyceum that way instead, set the API type to **Anthropic Messages API** and point it at: See [Claude Code](/docs/inference/claude-code) for the full list of `claude-*` model aliases. ## What works with BYOK | Feature | BYOK model | | --------------------------------- | ------------------------------------------------------------------------ | | Chat | Yes | | Agent mode | Yes, behind the experimental `chat.agentHost.byokModels.enabled` setting | | Commit messages, title generation | Yes | | Inline code completions | No, stays on Copilot's own models | | Semantic search, embeddings | No, requires a Copilot plan | Models must support tool calling to appear in the agent-mode picker. All models recommended on these pages do. # goose Source: https://docs.lyceum.technology/docs/agents/goose Configure Block's goose agent to use Lyceum inference models [goose](https://block.github.io/goose/) is Block's open-source terminal agent. It runs shell commands and edits files through its built-in developer extension. ## Install ```bash theme={null} curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash ``` ## Configure Supply the key through the environment: goose splits the endpoint into `OPENAI_HOST` (scheme and host) and `OPENAI_BASE_PATH` (the full path *including* `/chat/completions`). This is unlike every other client on these pages, which take one base URL. ## One-shot runs ## Recommended models goose leans on tool calling heavily. `moonshotai/kimi-k2.7-code` and `z-ai/glm-5.2` both handle its shell-and-edit loop well. # Kilo Code Source: https://docs.lyceum.technology/docs/agents/kilo-code Configure the Kilo Code VS Code extension to use Lyceum inference models [Kilo Code](https://kilo.ai) is an open-source coding agent for VS Code and JetBrains. It connects to Lyceum through its OpenAI Compatible provider. ## Install 1. Open the VS Code Extensions panel. 2. Search for **Kilo Code** and click **Install**. 3. Restart VS Code. ## Configure Click the Kilo Code icon in the VS Code side bar, then the gear icon to open the settings panel. Under **API Provider**, select **OpenAI Compatible**. Expand **Model Configuration** and set **Context Window** and **Max Output Tokens** from [active models](/docs/inference/active-models). Leave **Image Support** off unless you picked a vision model. Click **Let's go!**. ## Modes Kilo Code has several modes, and you can point each at a different model: | Mode | Use it for | Suggested model | | ------------- | ----------------------------- | --------------------------------- | | **Code** | General coding tasks | `moonshotai/kimi-k2.7-code` | | **Architect** | Planning and technical design | `z-ai/glm-5.2` | | **Ask** | Questions and explanation | `deepseek/deepseek-v4-flash-0731` | | **Debug** | Systematic problem diagnosis | `z-ai/glm-5.2` | | **Custom** | Specialized personas | any | ## Notes * **Newer builds use a custom provider registry.** If your version shows **Settings → Providers → Add custom provider** instead of an API Provider dropdown, use Provider ID `lyceum`, Display Name `Lyceum`, API Type **OpenAI Compatible**, and the same Base URL and API Key. Model auto-detection reads our `/models` endpoint and should populate the full catalog, which is a quick way to confirm the key and URL are both correct. * **Token limits are never auto-detected.** Our `/models` response carries IDs only, so set limits yourself here or in `kilo.jsonc`. ## Troubleshooting | Issue | Fix | | ----------------- | ---------------------------------------------------------------------------------------------------------------------- | | "Invalid API Key" | Confirm the key is an `lk_` key from the [dashboard](https://dashboard.lyceum.technology) | | "Model Not Found" | Check the model ID matches exactly. IDs are case-insensitive, but typos are not forgiven | | Connection errors | Base URL must be `https://api.lyceum.technology/openai/v1`, including the `/v1` suffix and with no `/chat/completions` | | Replies cut off | Raise **Max Output Tokens** in Model Configuration | # opencode Source: https://docs.lyceum.technology/docs/agents/opencode Configure opencode to use Lyceum inference models [opencode](https://opencode.ai) is a terminal coding agent. Lyceum plugs in as a custom provider. ## Configure Write this to `~/.config/opencode/opencode.json` for every project, or `opencode.json` in a project root for one. Export your key, then start opencode: opencode reads `LYCEUM_API_KEY` from the environment at launch. It does not load `.env` files, so put the export in `~/.zshrc` or `~/.bashrc` if you want it to persist. ## Verify `opencode models` lists what it resolved. Lyceum entries appear as `lyceum/`: ``` lyceum/deepseek/deepseek-v4-flash-0731 lyceum/deepseek/deepseek-v4-pro lyceum/moonshotai/kimi-k2.6 lyceum/moonshotai/kimi-k2.7-code lyceum/moonshotai/kimi-k3 lyceum/z-ai/glm-5.2 ``` Run a one-shot prompt without entering the TUI: ## Notes * **Strict JSON.** `opencode.json` does not allow trailing commas or comments. Rename the file `opencode.jsonc` if you want them. * **Context limits.** Add `"limit": { "context": 200000, "output": 65536 }` to a model entry so opencode's context compaction is accurate. Our `/models` endpoint does not report these, so take them from [active models](/docs/inference/active-models). * **Default model.** Without a top-level `"model"`, opencode picks whatever provider it finds first. # Coding Agents Source: https://docs.lyceum.technology/docs/agents/overview Use Lyceum models in Claude Code, Cursor, Cline, opencode, Zed, aider and other AI coding tools Every AI coding tool that speaks the OpenAI or Anthropic API works with Lyceum. You point it at our endpoint, give it your API key, and pick a model. ## Set up once Enter your key and pick a model below. Every config block across these pages fills itself in with your real values, and the button runs a live request so you know the key works before you paste anything into an editor. Get a key from the [dashboard](https://dashboard.lyceum.technology). See [API keys](/docs/configuration/api-keys) for scopes and rotation. ## Which endpoint Two endpoints, depending on what the tool speaks: | | Endpoint | Auth header | | ------------------------ | ----------------------------------------- | ---------------------------- | | **OpenAI-compatible** | `https://api.lyceum.technology/openai/v1` | `Authorization: Bearer lk_…` | | **Anthropic-compatible** | `https://api.lyceum.technology/anthropic` | `x-api-key: lk_…` | Most tools want the first. Claude Code wants the second. ## Pick your tool CLI and VS Code extension Model override in settings Custom provider block Custom endpoint in VS Code VS Code agent VS Code agent VS Code agent Editor with built-in agent Terminal pair programmer Block's terminal agent Build your own agent ## Verify before you configure If a tool misbehaves, check the API directly first. This is the same request every tool above makes: List every model your key can reach: ## Choosing a model | Model | Good for | | --------------------------------- | -------------------------------- | | `moonshotai/kimi-k2.7-code` | Agentic coding, tool-heavy loops | | `z-ai/glm-5.2` | Strong general reasoning | | `moonshotai/kimi-k3` | Long-context work | | `deepseek/deepseek-v4-pro` | Reasoning at lower cost | | `deepseek/deepseek-v4-flash-0731` | Fast edits, autocomplete | | `minimax/minimax-m3` | Cheap, high throughput | Full list at [active models](/docs/inference/active-models), or call `/models` above. Model IDs are case-insensitive. Tools that reject `/` in model names accept the slash-free form, so `z-ai-glm-5.2` resolves to `z-ai/glm-5.2`. ## Context windows The `/models` response does not report context window sizes. Tools that ask you to set **Context Window** or **Max Output Tokens** by hand (Cline, Roo Code, Kilo Code, Zed) need those numbers from the [active models](/docs/inference/active-models) page. # Roo Code Source: https://docs.lyceum.technology/docs/agents/roo-code Configure the Roo Code VS Code extension to use Lyceum inference models [Roo Code](https://roocode.com) is an agentic coding extension for VS Code with multiple operating modes. It reaches Lyceum through its OpenAI Compatible provider. ## Install 1. Open the VS Code Extensions panel (`Cmd+Shift+X` or `Ctrl+Shift+X`). 2. Search for **Roo Code** and click **Install**. 3. Restart VS Code. ## Configure Click the Roo Code icon in the sidebar, then the gear icon. Set **API Provider** to **OpenAI Compatible**. Set **Context Window** and **Max Output Tokens** from [active models](/docs/inference/active-models). Enable **Image Support** only for vision models. Roo Code uses native tool calling with no fallback. A model without tool-calling support will fail to edit files. Every model in the table below supports it. ## Recommended models | Model | Why | | --------------------------------- | ---------------------------- | | `moonshotai/kimi-k2.7-code` | Built for agentic coding | | `z-ai/glm-5.2` | Strongest general reasoning | | `deepseek/deepseek-v4-flash-0731` | Fast, cheap for simple modes | ## Modes Roo Code ships five built-in modes. Switch with the dropdown left of the chat input, a slash command, or `Cmd+.` on macOS / `Ctrl+.` on Windows and Linux. | Mode | Command | Use it for | Suggested model | | ------------------ | --------------- | ----------------------------------- | --------------------------------- | | **Code** (default) | `/code` | Writing code, implementing features | `moonshotai/kimi-k2.7-code` | | **Architect** | `/architect` | System design, planning | `z-ai/glm-5.2` | | **Debug** | `/debug` | Tracking bugs, diagnosing errors | `z-ai/glm-5.2` | | **Ask** | `/ask` | Explanation, technical questions | `deepseek/deepseek-v4-flash-0731` | | **Orchestrator** | `/orchestrator` | Multi-step work spanning modes | `moonshotai/kimi-k3` | ## Per-mode models Roo Code supports one API configuration profile per mode, so each mode can run a different model. Create a profile per row above under **API Configuration Profiles** in settings, then assign profiles to modes in the mode settings. Every profile uses the same Base URL and API key; only the model differs. ## Custom modes Add a `.roomodes` file to your project root to define specialised modes: ```yaml theme={null} customModes: - slug: reviewer name: "Code Reviewer" roleDefinition: >- You are a senior code reviewer focused on code quality, security, and best practices. groups: - read - browser customInstructions: | Review code for bugs, security issues, and style. Suggest improvements with specific examples. - slug: docs-writer name: "Documentation Writer" roleDefinition: You are a technical writer specialising in clear documentation. groups: - read - - edit - fileRegex: \.(md|mdx)$ description: Markdown files only - browser ``` Assign a model to a custom mode the same way as a built-in one, through an API configuration profile. ## Troubleshooting | Issue | Fix | | ---------------------- | -------------------------------------------------------------------------------------------------------------------- | | Model does not respond | Confirm the key is an `lk_` key and the Base URL ends in `/openai/v1` | | Context window errors | Set **Context Window** in Model Configuration to match the model, see [active models](/docs/inference/active-models) | | Replies cut off | Raise **Max Output Tokens** | | Slow on simple tasks | Switch that mode to `deepseek/deepseek-v4-flash-0731` | # Vercel AI SDK Source: https://docs.lyceum.technology/docs/agents/vercel-ai-sdk Build agents on Lyceum inference models with the Vercel AI SDK The [AI SDK](https://ai-sdk.dev) is the fastest way to build your own agent on Lyceum models. Use the `@ai-sdk/openai-compatible` provider. ## Install ```bash theme={null} npm i ai @ai-sdk/openai-compatible zod ``` ## Create the provider ## Generate and stream ## Tool calling Multi-step tool loops work the same as with any first-party provider: ## Notes * **Reasoning tokens are reported.** `usage.outputTokenDetails.reasoningTokens` separates thinking from visible output on reasoning models like `z-ai/glm-5.2`. * **Not the same as AI Gateway.** Vercel's AI Gateway serves its own model catalog; this provider talks to Lyceum directly, so your traffic and billing stay with us. * Keep `LYCEUM_API_KEY` server-side. Never ship it to a browser bundle. # Zed Source: https://docs.lyceum.technology/docs/agents/zed Configure the Zed editor's agent to use Lyceum inference models [Zed](https://zed.dev) has a built-in agent panel that accepts OpenAI-compatible providers. ## Configure Open `settings.json` with `cmd-shift-p → zed: open settings`, and add: Then set the API key. Zed asks for keys in the UI rather than in `settings.json`. Open the agent panel, choose the **lyceum** provider, and paste your key. Zed also accepts it from the environment: Do not put your API key in `settings.json`. Zed's own docs advise against it, and the file is commonly committed to dotfile repos. ## Using it Open the **Agent Panel** with `Cmd+Shift+A` on macOS or `Ctrl+Shift+A` on Linux. The agent can edit files across the project, run terminal commands, and search the codebase. For a quick edit in place, select some code and press `Cmd+Enter` on macOS or `Ctrl+Enter` on Linux to use the inline assistant. ## Notes * **`api_url` is the base**, without `/chat/completions`. Zed appends that itself. * **`max_tokens` is the context window**, `max_output_tokens` is the reply cap. Values above are safe defaults; check [active models](/docs/inference/active-models) for your model. * **`tools: true` is required** for the agent panel to edit files. * Add more entries to `available_models` to switch models from Zed's model picker. # API Keys Source: https://docs.lyceum.technology/docs/configuration/api-keys Authenticate the CLI, scripts, and integrations API keys are long-lived bearer tokens that authenticate requests to Lyceum Cloud. They're prefixed `lk_` and pass in the `Authorization` header on every request: ```bash theme={null} curl https://api.lyceum.technology/api/v2/external/billing/credits \ -H "Authorization: Bearer lk_your_api_key" ``` Every endpoint in the [API Reference](/api-reference/introduction) accepts the same header. ## Keys belong to an organization Every API key is scoped to a single [organization](/docs/account/organizations). The key authenticates the request **and** pins it to that org, there's no need to send an extra `X-Org-Slug` header. The credits, runs, VMs, and deployments associated with the request are all billed and accessed through the key's org. If you belong to several orgs, create a separate key in each one for the scripts that act on its behalf. ## API keys vs JWT tokens The platform supports two token types: | Token | Lifetime | When to use | | ---------------------- | ------------------------------------ | ---------------------------------------------------------- | | **API key** (`lk_...`) | Long-lived, until revoked or expired | CLI in CI, scripts, integrations, anything non-interactive | | **JWT** | Short-lived, refreshable | Interactive dashboard sessions, API playground, testing | JWT users can act on any org they're a member of by sending `X-Org-Slug: `; with an API key, the org is fixed by the key itself. ## Lifecycle API keys can be: * **Created** with a name and an optional expiration date. Creation requires the `owner` or `admin` role in the org. * **Regenerated** to issue a fresh secret while keeping the key's name, org, and expiration. The previous secret stops working within 5 minutes, so you can rotate a key without touching the scripts that reference it by name. * **Revoked** at any time, effective within 5 minutes. The row is kept for the audit trail. The full key value is returned **exactly once**, in the response to the create or regenerate request. After that, only the prefix (first 8 characters) is visible. If you lose a key, regenerate it or revoke it and create a new one. Creating, regenerating, and revoking all require the `owner` or `admin` role in the org. API keys grant the same access as members of the org they're scoped to. Store them in a password manager or secret store, and never commit them to source control. ## Dashboard Open **API Keys** in the dashboard. The page lists every key across every org where you're an owner or admin, with a filter to narrow by org. Create a new key with the **New API Key** button; the dialog defaults to the active org but you can pick any org you can manage. Each key row has a **regenerate** button (the circular-arrows icon) to rotate its secret in place, and a **revoke** button to disable it. As with creation, the new value from a regenerate is shown only once. ## CLI ```bash theme={null} # List keys in the active org lyceum org keys list # Create a key in the active org lyceum org keys create ci-pipeline # Create a key in a different org without switching active lyceum org keys create deploy-prod --org production --expires-at 2027-01-01T00:00:00Z # Revoke lyceum org keys revoke ``` ## REST API | Method | Endpoint | Purpose | | -------- | ------------------------------------------- | ------------------------------------------------------ | | `GET` | `/orgs/{slug}/api-keys` | List the org's keys (metadata only) | | `POST` | `/orgs/{slug}/api-keys` | Create a key, returns the plaintext value once | | `POST` | `/orgs/{slug}/api-keys/{key_id}/regenerate` | Regenerate a key, returns the new plaintext value once | | `DELETE` | `/orgs/{slug}/api-keys/{key_id}` | Revoke a key | They all require the `owner` or `admin` role in the org. ```bash theme={null} # Create a key curl -X POST https://api.lyceum.technology/api/v2/external/orgs/acme/api-keys \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"name": "ci-pipeline", "expires_at": "2027-01-01T00:00:00Z"}' ``` The response contains `plaintext_key`, store it immediately. # Machine Types Source: https://docs.lyceum.technology/docs/configuration/machine-types GPU options for serverless workloads and VM instances Lyceum offers GPU resources in two distinct contexts: **serverless workloads** (per-job execution via the CLI or API) and **VM instances** (long-running dedicated machines launched from the dashboard or API). The available machine identifiers and pricing differ between the two. ## Serverless Workloads When running code through `lyceum python run`, `lyceum docker run`, `lyceum compose run`, or `lyceum notebook`, select the underlying hardware with the `-m` / `--machine` flag. ```bash theme={null} # Select a GPU profile lyceum python run train.py -m gpu.a100 lyceum docker run pytorch/pytorch:latest -m gpu.h100 -c "python train.py" lyceum compose run docker-compose.yml -m gpu.a100 ``` The default is `gpu.a100`. Common GPU values include `gpu.a100`, `gpu.h100`, `gpu.b200`, and others depending on your account quota. Available machine types are gated per account. The CLI validates your selection against `/api/v2/external/user/quotas/available-hardware` before submitting the job. To see the machines you have access to, run any execution command with an unavailable type, the CLI will print the list. ## VM Instances When launching dedicated VMs via the [dashboard](https://dashboard.lyceum.technology) or the [VMs API](/docs/instances/vms), the following GPU profiles are available: | GPU | VRAM | RAM | vCPU | Peak TFLOPS | | -------- | ------ | ------ | ---- | ----------- | | **B300** | 288 GB | 240 GB | 32 | 720 | | **B200** | 192 GB | 180 GB | 28 | 540 | | **H200** | 141 GB | 200 GB | 16 | 67 | | **H100** | 80 GB | 180 GB | 20 | 67 | | **A100** | 80 GB | 120 GB | 16 | 19.5 | | **L40S** | 48 GB | 128 GB | 12 | 91.6 | Each profile can be launched with 1, 2, 4, or 8 GPUs per instance, subject to availability and account limits. For current pricing and committed-term discounts, see the [dashboard launch page](https://dashboard.lyceum.technology). ## Storage All machine types, serverless and VM, have access to your cloud storage. See [Storage](/docs/configuration/storage) for mount paths and usage. # Storage Source: https://docs.lyceum.technology/docs/configuration/storage Per-user S3-compatible bucket Every Lyceum account gets a dedicated S3-compatible bucket. It's the right place for anything that needs to outlive a single run or VM: input datasets, trained model weights, intermediate artefacts, results. ## Why use it instead of... * **...Secrets**, Secrets are short string values (tokens, URLs). Storage is for files of any size. * **...A VM disk**, VM disks are wiped on termination. The storage bucket persists across every VM and run on your account. * **...Re-uploading on every run**, A run that reads from the bucket starts immediately; one that uploads inputs to itself pays for the upload time on every invocation. ## Access patterns There are two ways to interact with the bucket: 1. **Through the Lyceum REST API**, simple, single-file uploads/downloads, easy `curl` commands. 2. **Direct S3 with temporary credentials**, fetch short-lived MinIO/S3 credentials from `POST /storage/credentials` and use any standard S3 client (`boto3`, `aws-cli`, `mc`, ...). This is the right path for large files, parallel transfers, multipart uploads, and anything that benefits from a real S3 client library. The temporary credentials returned by `/storage/credentials` are STS-style: an access key, secret key, session token, the bucket name, and the endpoint. They expire automatically, request fresh ones whenever you need them. ## CLI ```bash theme={null} lyceum storage ls # list files at the root lyceum storage ls data/ -r # list a folder recursively lyceum storage load local-file.csv # upload lyceum storage load local-file.csv --key data/x.csv # upload to a specific path lyceum storage load ./local-folder -r # upload a directory lyceum storage download path/in/bucket/file.csv # download lyceum storage download path/in/bucket/file.csv -o ./ # download to a specific path lyceum storage rm path/in/bucket/file.csv # delete a file lyceum storage rmdir old-data/ # delete a folder ``` `--key` controls the destination key inside the bucket; without it the file is uploaded under its local name. ## REST API | Method | Endpoint | Purpose | | -------- | ---------------------------------------- | ----------------------------------------------------------- | | `GET` | `/storage/list-files` | List files (optional `prefix`, `max_files`) | | `POST` | `/storage/upload` | Upload a single file (multipart form, optional `key` query) | | `POST` | `/storage/upload-bulk` | Upload multiple files in one request | | `GET` | `/storage/download/{file_key}` | Download a file | | `DELETE` | `/storage/delete/{file_key}` | Delete a file | | `DELETE` | `/storage/delete-folder/{folder_prefix}` | Delete every file under a prefix | | `POST` | `/storage/credentials` | Get temporary S3 credentials for direct access | ## Direct S3 access `POST /storage/credentials` returns a `StorageCredentials` object with these fields: | Field | Description | | --------------- | --------------------------- | | `access_key` | Access key ID | | `secret_key` | Secret access key | | `session_token` | STS session token | | `endpoint` | S3-compatible endpoint URL | | `bucket_name` | Your bucket name | | `region` | Bucket region | | `expires_at` | Credential expiry timestamp | ```python theme={null} import boto3, requests api_key = "lk_..." creds = requests.post( "https://api.lyceum.technology/api/v2/external/storage/credentials", headers={"Authorization": f"Bearer {api_key}"}, ).json() s3 = boto3.client( "s3", endpoint_url=creds["endpoint"], aws_access_key_id=creds["access_key"], aws_secret_access_key=creds["secret_key"], aws_session_token=creds["session_token"], region_name=creds["region"], ) s3.upload_file("local.csv", creds["bucket_name"], "data/local.csv") ``` The same client works for `download_file`, `list_objects_v2`, multipart uploads, presigned URLs, and any other S3 operation. ## Mounting storage inside runs How your bucket is exposed depends on the execution type: | Execution type | Mount behaviour | | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | **Docker** (`lyceum docker run`) | Bucket is mounted at `/mnt/s3` **by default**. Disable with `--no-s3`, change the path with `--s3-mount-path /your/path`. | | **Docker Compose** (`lyceum compose run`) | Mount is **off by default**. Enable by setting `enable_s3_mount: true` in the API request. | | **Python** (`lyceum python run`) | The bucket is **not mounted** as a filesystem. Use the credentials endpoint above and any S3 client to read and write files. | | **VMs** | The bucket is **not auto-mounted**. You can mount it yourself on the VM with `s3fs`, `mc`, `rclone`, or any S3 client using the credentials endpoint. | For Docker runs, files in `/mnt/s3` map directly to objects in your bucket, reading a file fetches the object, writing creates or replaces it. # Lyceum Cloud Source: https://docs.lyceum.technology/docs/index Run Python, Docker, notebooks, and dedicated inference on managed GPU infrastructure ## Overview Lyceum Cloud is a managed compute platform for running code and serving models on cloud GPUs. The dashboard, CLI, and REST API all expose the same underlying capabilities, organised around a few core building blocks. Create an account, install the CLI or VS Code extension, and run your first job. ## What you can do Submit Python scripts, Docker images, or Docker Compose stacks. Pay per second of execution. Provision long-lived GPU VMs with SSH access for interactive work and custom environments. Deploy any Hugging Face model to a dedicated GPU endpoint with autoscaling. Per-user S3-compatible bucket for inputs, outputs, and shared datasets. ## Ways to access the platform Web UI for everything: launching runs and VMs, deploying models, managing storage, billing, and API keys. The `lyceum` command-line tool for runs, VMs, inference, and storage from your terminal. Programmatic access to every dashboard feature. OpenAI-compatible inference endpoints. ## Authentication Every request to the platform is authenticated with a bearer token in the `Authorization` header. Two token types are supported: * **API keys**, long-lived tokens prefixed `lk_`, scoped to one [organization](/docs/account/organizations) at creation. Created and managed in [API Keys](/docs/configuration/api-keys). Use these for CLI, scripts, and integrations. * **JWT tokens**, short-lived tokens issued by the dashboard login flow. Use these for testing in the browser or API playground. JWT users pick which org to act on with the `X-Org-Slug` header, or fall back to their default org. If your company uses an identity provider like Okta, an org owner can enable [SSO](/docs/account/sso) so your team signs in through it directly instead of individual Lyceum passwords. Create your first API key from the dashboard. # Active Models Source: https://docs.lyceum.technology/docs/inference/active-models Inspect running dedicated deployments and replica health A dedicated deployment is made up of one or more **replicas**, identical copies of the model serving requests. Each replica reports its own status (`pending`, `running`, `stopped`, `failed`) and a health flag updated by periodic health checks. The replica count autoscales between `min_replicas` and `max_replicas` based on the deployment's scaling target. This page covers the read-side endpoints for inspecting what's running, plus the model catalogue endpoints for discovering what's available. ## Inspecting deployments | Method | Endpoint | Purpose | | -------- | ----------------------------------- | -------------------------------------- | | `GET` | `/inference/list` | All your deployments | | `GET` | `/inference/get?deployment_id={id}` | Deployment details with replicas | | `DELETE` | `/inference/stop` | Stop a deployment | | `POST` | `/v1/chat/completions` | Send a chat completion to a deployment | `GET /inference/get` returns the deployment's status, scaling config, and a list of replicas with their individual health and last health-check timestamps. By default it returns only active deployments, pass `include_terminated=true` to see stopped ones too (useful for auditing or cost analysis). ## Picking a model to deploy There's no platform-wide model catalogue API to browse before deploying, any Hugging Face model ID can be passed to `POST /inference/create`. The dashboard's [Dedicated Inference page](https://dashboard.lyceum.technology/inference) offers a curated grid of popular models as a convenience. # Claude Code Source: https://docs.lyceum.technology/docs/inference/claude-code Use Claude Code with Lyceum's inference models Point Claude Code at Lyceum's inference endpoint to use it with any of our supported models and switch between them mid-session with `/model`, no restart needed. ## Setup The recommended way is the Lyceum CLI. `lyceum code` launches Claude Code preconfigured, in its own profile that never touches an existing Anthropic login: ```bash theme={null} pip install lyceum-cli lyceum auth login --api-key lk_ # one-time lyceum code ``` Get your API key from the [dashboard](https://dashboard.lyceum.technology). Trying it out in a single terminal without installing anything? Set these two environment variables before starting Claude Code: Use `ANTHROPIC_AUTH_TOKEN`, not `ANTHROPIC_API_KEY`. Both work, but `ANTHROPIC_API_KEY` makes Claude Code prompt for approval of a custom key on first run, which fails outright in non-interactive use (`claude -p`, CI, scripts) with `Not logged in · Please run /login`. `ANTHROPIC_AUTH_TOKEN` skips that prompt and does not conflict with an existing claude.ai session. Or write the same two values into `~/.claude/settings.json` yourself for a setup that persists across terminals: The `ANTHROPIC_DEFAULT_*` entries decide which Lyceum model each Claude tier resolves to, so picking Opus or Sonnet from the `/model` menu selects the model you mapped. `API_TIMEOUT_MS` is worth raising, since reasoning models can think for a while before the first token. Prefer a one-line script that does the settings.json edit for you? Run: ```bash theme={null} curl -fsSL https://lyceum.technology/install-claude.sh | sh -s -- lk_ claude ``` ## VS Code extension The Claude Code extension for VS Code reads its own environment block, not `~/.claude/settings.json`. Open **Settings → Extensions → Claude Code**, or edit `settings.json` directly: `claudeCode.disableLoginPrompt` is what stops the extension asking you to sign in to claude.ai. Without it the login prompt keeps appearing even though the token is set, because the extension does not know the base URL points somewhere other than Anthropic. Reload the window after saving. The same block works in the JetBrains plugin's environment settings. ## Switching models mid-session Pass any Lyceum model ID directly to `/model` inside Claude Code: ``` /model z-ai/glm-5.2 /model moonshotai/kimi-k2.6 /model deepseek/deepseek-v4-pro ``` The switch takes effect with your next message. No restart, your conversation context stays. Any model from the [serverless catalog](https://lyceum.technology/products/inference/models) works. IDs are case-insensitive, and slash-free variants (`z-ai-glm-5.2`) are accepted for clients that reject `/` in model names. Note: the bare `/model` menu (without an argument) only lists Anthropic tier names and selecting there switches between the tiers mapped below, not the full catalog. ## Model mapping Anthropic tier names map to Lyceum models automatically: | Claude tier | Model | | -------------------------------- | -------------- | | claude-fable-\* / claude-opus-\* | GLM-5.2 | | claude-sonnet-\* | Kimi-K2.7-Code | | claude-haiku-\* | MiniMax-M3 | Model selection precedence, highest first: 1. A Lyceum model ID in the request's `model` field (what `/model ` sends) 2. The tier mapping above ## Switching from Anthropic to Lyceum If you previously used Claude Code with a direct Anthropic key, make sure to update both variables - not just the base URL. Claude Code warns you if both a claude.ai session and `ANTHROPIC_API_KEY` are set at the same time. To avoid auth conflicts, run `/logout` inside Claude Code before setting the Lyceum key, or use `lyceum code`, which runs in a separate profile. ## Note on 1M context models Avoid using `sonnet[1m]` or similar extended-context variants. The `[1m]` suffix is Anthropic-specific and Claude Code will not connect through the proxy with it set. Use `sonnet` instead, either via `/model` in Claude Code or by setting `"model": "sonnet"` in `~/.claude/settings.json`. ## Troubleshooting **Selected "No (not recommended)" at the API key prompt?** On first start, Claude Code asks whether to use the configured API key. If you decline, it stores that choice and never asks again, so requests won't reach Lyceum. Reset the profile to get the prompt back: ```bash theme={null} rm -rf ~/.lyceum/claude lyceum code ``` Then select **Yes** at the API key prompt. This only resets the Lyceum profile (including its session history); an existing Anthropic Claude Code setup is untouched. # Cursor Source: https://docs.lyceum.technology/docs/inference/cursor Use Cursor with Lyceum's inference models Point Cursor at Lyceum's serverless endpoint to chat with any of our open-source models directly inside the editor. The endpoint is OpenAI-compatible, so Cursor talks to it through its built-in OpenAI provider. This requires a paid Cursor plan. The custom OpenAI key and base-URL override are not available on Cursor's free tier. Cursor also only routes its **Ask / chat** panel to custom OpenAI models: Agent, Composer, Tab completion, and the cloud agents stay on Cursor's own models. For a full agentic coding workflow on Lyceum models, use [Claude Code](/docs/inference/claude-code). ## Setup In the Cursor desktop app, open **Cursor Settings** (`Cmd + ,`) and go to **Models**, then scroll to **API Keys**: 1. Under **Model Names**, add a Lyceum model ID, for example `z-ai/glm-5.2`. 2. In **OpenAI API Key**, paste your Lyceum key (`lk_...`). Get it from the [dashboard](https://dashboard.lyceum.technology). 3. Enable **Override OpenAI Base URL** and set it to the value below. 4. Click **Verify**. Cursor sends a test request using your first custom model, so the model ID has to be valid before this succeeds. Once verified, pick the model from the model dropdown in the Ask panel and chat as usual. Your requests are billed to your Lyceum credit balance. ## Models Add any model ID from the [serverless catalog](https://lyceum.technology/products/inference/models). Common choices: ``` z-ai/glm-5.2 moonshotai/kimi-k2.7-code minimax/minimax-m3 deepseek/deepseek-v4-pro moonshotai/kimi-k2.6 ``` IDs are case-insensitive. If Cursor rejects a model name containing a slash, use the slash-free variant instead (`z-ai-glm-5.2`). ## Troubleshooting **"Model name is not valid" / "AI Model Not Found."** You are in the Agent or cloud-agent surface, which does not support custom OpenAI models. Use the Ask / chat panel instead, and make sure the base URL override is verified in Settings. **Verify fails or times out.** Confirm the base URL is exactly `https://api.lyceum.technology/openai/v1` (including the `/v1` suffix, with no trailing `/chat/completions`) and that the API key is a valid `lk_` key from the dashboard. # Dedicated Inference Source: https://docs.lyceum.technology/docs/inference/dedicated Deploy a Hugging Face model to a dedicated GPU endpoint with autoscaling A **dedicated deployment** runs a model on reserved GPU replicas and exposes it through an OpenAI-compatible Chat Completions endpoint. You're billed for the time the replicas are running, not per request. This is the right tool when you need predictable latency, full control over the model and the GPU it runs on, and the ability to point any OpenAI-compatible client at your own endpoint. ## Concepts Each deployment is identified by a `deployment_id` and is made up of: * **A model**, referenced by its Hugging Face model ID (e.g. `meta-llama/Llama-3.1-8B-Instruct`). For gated models you supply an HF token at create time. * **A hardware profile**, the GPU type each replica runs on * **Replicas**, one or more identical copies of the model serving requests. The replica count autoscales between `min_replicas` and `max_replicas`. * **A scaling target**, `target_rps` (requests per second per replica) and `target_latency_p95_ms` define when to scale up or down. A stabilisation window prevents flapping. The platform exposes one OpenAI-compatible endpoint per deployment; load balancing across replicas is handled for you. ## Scale to zero Deployments that receive no requests for **1 hour** are automatically scaled down to zero instances and marked as **paused**. This stops billing for idle replicas. When a paused deployment receives a new request, it is automatically **resumed**, scaled back to its `min_replicas`, and the autoscaler takes over from there, scaling up or down based on traffic volume. The first request after a pause will have higher latency while the replicas start up. Scale to zero is enabled for all deployments. There is nothing to configure, it works alongside the existing autoscaler settings. | Deployment state | Replicas | Billing | Behaviour on request | | --------------------------- | ------------------------------- | ------- | -------------------------------------- | | **running** | `min_replicas` – `max_replicas` | Active | Served immediately | | **paused** (scaled to zero) | 0 | Stopped | Resumes to `min_replicas`, then serves | ## When to use what | Use case | Pick | | ------------------------------------------------------- | ------------------------------------------------------------- | | Production traffic, predictable latency, your own model | Dedicated (this page) | | One-off LLM job that runs and exits | A regular Python or Docker [run](/docs/serverless/launch-run) | | Pay-per-request, low-volume, no infra | [Serverless inference](/docs/inference/serverless) | | Distributed multi-node training of new models | [Clusters](/docs/workloads/clusters) (enterprise) | ## Deploying ```bash theme={null} lyceum infer deploy meta-llama/Llama-3.1-8B-Instruct \ -g gpu.a100 \ --min-replicas 1 \ --max-replicas 3 ``` | Flag | Default | Description | | ------------------ | ---------- | ----------------------------------------------- | | `-g`, `--gpu` | `gpu.a100` | Hardware profile (e.g. `gpu.a100`, `gpu.h100`) | | `--min-replicas` | `1` | Minimum replicas kept warm | | `--max-replicas` | `1` | Maximum replicas the deployment can scale to | | `--target-rps` | `10.0` | Target requests/sec per replica | | `--target-latency` | `5000.0` | Target p95 latency in milliseconds | | `--stabilisation` | `300` | Scale-down stabilisation window (seconds) | | `-t`, `--hf-token` | — | Hugging Face token for gated models | | `-w`, `--wait` | off | Block until the deployment has healthy replicas | ```bash theme={null} lyceum infer status # add --all to include stopped deployments lyceum infer stop lyceum infer models # public catalogue + your deployments lyceum infer chat -d -p "Hello" # send a chat completion ``` ```http theme={null} POST /api/v2/external/inference/create ``` Required body fields: `hf_model_id`, `hardware_profile`, `target_rps`, `target_latency_p95_ms`, `stabilisation_window`. Optional: `hf_token` (for gated models), `min_replicas` and `max_replicas` (both default to `1`). Returns `deployment_id` and initial `status`. The deployment starts in `created` and transitions through `provisioning` to `running` once the first replica is healthy. Poll `GET /inference/get?deployment_id=` to track progress. | Method | Endpoint | Purpose | | -------- | ---------------------------------- | ------------------------------- | | `POST` | `/inference/create` | Create a deployment | | `GET` | `/inference/get?deployment_id=...` | Deployment details and replicas | | `GET` | `/inference/list` | List your deployments | | `DELETE` | `/inference/stop` | Stop a deployment | ## Calling your deployment Deployments speak the OpenAI Chat Completions format, so any OpenAI-compatible client works. Set the `model` field to your `deployment_id`: ```http theme={null} POST /api/v2/external/v1/chat/completions Authorization: Bearer Content-Type: application/json { "model": "", "messages": [{"role": "user", "content": "Hello"}] } ``` Response shape mirrors OpenAI's: `id`, `choices`, `usage`, `model`, `created`. ## Using the OpenAI SDK Because the endpoint is OpenAI-compatible, you can drop in the official `openai` client by overriding `base_url` and `api_key`. Set the `model` field to your `deployment_id`: ```python theme={null} from openai import OpenAI client = OpenAI( api_key="lk_...", base_url="https://api.lyceum.technology/api/v2/external", ) resp = client.chat.completions.create( model="", messages=[{"role": "user", "content": "Hello"}], ) print(resp.choices[0].message.content) ``` Anything that speaks the OpenAI Chat Completions protocol, LangChain, LlamaIndex, Continue, your own client, works the same way: point it at the Lyceum base URL and use your API key. ## Batch and streaming For asynchronous batch processing on large input files, the platform implements the OpenAI Batch API: | Method | Endpoint | Purpose | | ------ | ---------------------------- | --------------------------------------- | | `POST` | `/files` | Upload a JSONL input file | | `POST` | `/batches` | Create a batch job referencing the file | | `GET` | `/batches/{batch_id}` | Check status | | `POST` | `/batches/{batch_id}/cancel` | Cancel a batch | | `GET` | `/batches` | List your batches | | `GET` | `/files/{file_id}/content` | Download results | Inspect running deployments and replica health. # Serverless Inference Source: https://docs.lyceum.technology/docs/inference/serverless Pay-per-request, OpenAI-compatible inference endpoints Serverless inference offers pay-per-request pricing with no idle compute cost, the right choice for low-volume or spiky traffic where reserving GPU replicas would be wasteful. The endpoints are **OpenAI-compatible**. Point any OpenAI client at the base URL below, swap in a Lyceum API key and a model name, and everything else, streaming, tool calling, usage accounting, works the same. ## Try it Enter a key and pick a model to send a real request from this page. It runs the same auth, credit and model checks as any client, so a green result means your key is valid, your org has credit, and the model ID resolves. This sends one real chat completion and bills it to your org, the same as any other request. It is a few tokens. ## Endpoint and authentication The base URL for serverless inference is: ``` https://api.lyceum.technology/openai/v1 ``` Authenticate with your Lyceum API key (`lk_...`) as a Bearer token. Most SDKs append the path for you, so you pass only the base URL above. | Method | Path | Purpose | | ------ | ------------------- | ---------------------------------------------- | | `POST` | `/chat/completions` | Chat completions (streaming and non-streaming) | | `POST` | `/embeddings` | Text embeddings | | `GET` | `/models` | List the models currently available | There is no legacy text `/completions` endpoint. All text generation goes through `/chat/completions` using the `messages` format. ## Quickstart ```python theme={null} from openai import OpenAI client = OpenAI( api_key="lk_...", base_url="https://api.lyceum.technology/openai/v1", ) resp = client.chat.completions.create( model="z-ai/glm-5.2", messages=[{"role": "user", "content": "Explain prompt caching in one sentence."}], ) print(resp.choices[0].message.content) ``` ```bash theme={null} curl https://api.lyceum.technology/openai/v1/chat/completions \ -H "Authorization: Bearer lk_..." \ -H "Content-Type: application/json" \ -d '{ "model": "z-ai/glm-5.2", "messages": [{"role": "user", "content": "Explain prompt caching in one sentence."}] }' ``` Browse the available model IDs on the [Active Models](/docs/inference/active-models) page. ## Streaming Set `stream: true` to receive tokens as they are generated. This is the recommended mode for anything user-facing or long-form, it lowers time-to-first-token and keeps the connection active for the full generation. ```python theme={null} stream = client.chat.completions.create( model="z-ai/glm-5.2", messages=[{"role": "user", "content": "Write a short story."}], stream=True, ) for chunk in stream: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True) ``` ## Timeouts and long-running requests The platform allows a single request up to **300 seconds** of generation time. Large context windows or long outputs on a non-streaming call can genuinely take minutes, so the most common source of "cut off" errors is a **client-side timeout that fires before the response finishes**, not the server. For long outputs, **stream the response**. Streaming keeps data flowing over the connection, which avoids idle/read timeouts and gives you tokens as they arrive instead of waiting for the whole completion. If you must run non-streaming for long generations, raise your client's read timeout: ```python theme={null} client = OpenAI( api_key="lk_...", base_url="https://api.lyceum.technology/openai/v1", timeout=600.0, # seconds; default in most SDKs is 600, but some frameworks set it lower ) ``` Some frameworks (for example litellm) apply a separate **stream inactivity timeout** that aborts a stream if no new token arrives within a fixed window. Under heavy load, time-to-first-token can spike, so if you stream through such a framework, raise its inactivity timeout as well as the overall request timeout. ## Function calling and tools All chat models support **function / tool calling** using the standard OpenAI `tools` and `tool_choice` parameters. ```python theme={null} resp = client.chat.completions.create( model="z-ai/glm-5.2", messages=[{"role": "user", "content": "What is the weather in Berlin?"}], tools=[{ "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a city", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"], }, }, }], tool_choice="auto", ) print(resp.choices[0].message.tool_calls) ``` A small number of models prefer to answer in text rather than call a tool when `tool_choice` is left on `"auto"`. If you find a model returning prose instead of a tool call, set `tool_choice="required"` to force it. ## Prompt caching Prompt caching is **automatic**, there is nothing to enable. When consecutive requests share an identical leading prefix (for example a fixed system prompt or a large shared context), the cached portion is reused and billed at a reduced input rate. To benefit, keep the stable part of your prompt at the **front** and vary only the tail (the user's latest message). Caching is best-effort, a request may or may not hit a warm cache depending on recent traffic. Cache hits are reported in the usage object: ```python theme={null} resp = client.chat.completions.create(model="z-ai/glm-5.2", messages=[...]) print(resp.usage.prompt_tokens) # total input tokens print(resp.usage.prompt_tokens_details.cached_tokens) # portion served from cache ``` `prompt_tokens_details` is **absent entirely** on a cache miss rather than reporting zero, so read it defensively: ```python theme={null} details = resp.usage.prompt_tokens_details cached = details.cached_tokens if details else 0 ``` ## Reasoning models Several serverless models produce an explicit reasoning trace before the final answer. The trace is returned in `reasoning_content` and **counts toward output tokens**, so set `max_tokens` high enough to cover both the reasoning and the visible answer. Otherwise the whole budget can be spent on reasoning and `content` comes back empty or truncated. ### Controlling reasoning Reasoning is toggled through `chat_template_kwargs`, which Lyceum forwards to the model's chat template **unchanged**. The default state and the exact key are model-specific: | Model | Reasoning by default | How to toggle | | --------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `deepseek/deepseek-v4-flash-0731` | Off | Enable: `chat_template_kwargs: {"thinking": true}` | | `deepseek/deepseek-v4-pro` | Off | Enable: `chat_template_kwargs: {"thinking": true}` | | `moonshotai/kimi-k3` | On | Disable: `chat_template_kwargs: {"thinking": false}` | | `moonshotai/kimi-k2.6` | On | Disable: `chat_template_kwargs: {"thinking": false}` | | `moonshotai/kimi-k2.7-code` | On | Disable: `chat_template_kwargs: {"thinking": false}` | | `z-ai/glm-5.2` | On | Disable: `chat_template_kwargs: {"enable_thinking": false}`, `reasoning_effort: "none"`, or use `z-ai/glm-5.2-instant` | | `z-ai/glm-5.3` | On | Disable: `chat_template_kwargs: {"enable_thinking": false}` | | `z-ai/glm-5.3-flash` | On | Disable: `chat_template_kwargs: {"enable_thinking": false}` | | `qwen/qwen3.8-flash-next` | On | Disable: `chat_template_kwargs: {"enable_thinking": false}` | | `qwen/qwen3.8-27b` | On | Disable: `chat_template_kwargs: {"enable_thinking": false}` | | `minimax/minimax-m3` | On | Always on, cannot be disabled | | `qwen/qwen3.5-9b` | On | Always on, cannot be disabled | | `qwen/qwen3.8-2.4t-a95b` | On | Always on, cannot be disabled (`{"thinking": false}` is accepted but ignored) | Example, enabling reasoning on a model that defaults to off: ```python theme={null} resp = client.chat.completions.create( model="deepseek/deepseek-v4-flash-0731", messages=[{"role": "user", "content": "Solve step by step: ..."}], extra_body={"chat_template_kwargs": {"thinking": True}}, ) ``` * The kwarg key differs by model family: DeepSeek and Kimi use `thinking`; GLM-5.2 uses `enable_thinking`. Passing the other key has no effect. * Top-level `enable_thinking` and top-level `reasoning_effort` are **not** honored on these models (the one exception is GLM-5.2, which accepts `reasoning_effort: "none"` to disable). Use `chat_template_kwargs` as shown. * `reasoning_effort` levels (`low` / `high` / `max`) are forwarded to the model verbatim and are **not** reliably graded. Treat reasoning as on/off, not a depth dial. ### GLM-5.2 without reasoning `z-ai/glm-5.2` reasons before answering and spends output tokens on it. Clients that cap `max_tokens` and don't render `reasoning_content` (GitHub Copilot, Cursor and similar BYOK setups) can end up with an empty visible answer because the whole budget goes into reasoning. Two ways to turn it off: * Use the model id `z-ai/glm-5.2-instant` (also listed in `GET /models`). Same model, same pricing, answers directly without a reasoning phase. Pick this in clients where you can't pass request parameters. * Or send `reasoning_effort: "none"` in the request body when calling `z-ai/glm-5.2` through the API. ## Embeddings Text embeddings use the same base URL and key: ```python theme={null} emb = client.embeddings.create( model="Qwen/Qwen3-Embedding-8B", input="text to embed", ) print(len(emb.data[0].embedding)) ``` ## Error handling Errors come back in one of two shapes depending on where they originate. **Gateway errors** are raised by Lyceum before the request reaches a model: a bad key, an unknown model ID, or insufficient credit. These use a `detail` string. ```json theme={null} { "detail": "Model 'does/not-exist' is not available. Call GET /models for the list of supported models." } ``` **Upstream errors** are forwarded from the model backend unchanged, in the OpenAI-compatible shape: ```json theme={null} { "error": { "message": "Rate limit reached. Please retry shortly.", "type": "rate_limit_error", "code": 429 } } ``` A client that reads only `error.message` will show nothing useful for authentication and model-name mistakes, which are the most common setup errors. Check for `detail` first, then fall back to `error.message`. Upstream shapes hold for errors that occur mid-stream as well. Retry on `429` and on `5xx` or timeout responses with backoff. ## Serverless vs dedicated | Use case | Pick | | ---------------------------------------------------------------- | ------------------------------------------------ | | Low-volume or spiky traffic, pay-per-request, no infra to manage | Serverless (this page) | | Steady production traffic, predictable latency, your own model | [Dedicated Inference](/docs/inference/dedicated) | Browse the model IDs and context windows currently available. # Availability Reminders Source: https://docs.lyceum.technology/docs/instances/availability-reminders Get notified by email or webhook when a GPU profile becomes available When the hardware you want is out of stock, you can ask Lyceum Cloud to notify you the moment it comes back. Set a reminder for a hardware profile and we email you (and optionally POST to a webhook) as soon as that capacity is live. This is the right tool when the GPU you want shows no availability and you would rather be told than keep refreshing the launch page. ## Setting a reminder from the dashboard On the [Launch Instance](https://dashboard.lyceum.technology/vms/launch) page, any profile that is currently out of stock shows a **Notify when available** button. Open it, optionally paste a webhook URL, and confirm. We record the reminder against your account and email you when the profile is available. ## Setting a reminder via the API ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/infra/hardware-reminders \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "hardware_profile": "h100", "gpu_count": 8, "webhook_url": "https://hooks.slack.com/services/T000/B000/XXXX" }' ``` Fields: * `hardware_profile` (required): the GPU profile to watch, for example `h100` or `b200`. A `gpu.` prefix is stripped and the value is lowercased. * `gpu_count` (optional): only notify when this exact count is available. `8` means "notify me when 8x is free", not "1x or more". Omit it to match any available count. * `webhook_url` (optional): an HTTPS URL notified alongside the email. See below. * `hardware_label`, `company`, `use_case` (optional): free text stored with the reminder. The reminder is tied to your account. Setting a reminder for a profile you already have a pending reminder on updates it rather than creating a duplicate. ## Webhooks If you pass `webhook_url`, Lyceum POSTs to it when the profile becomes available, at the same time as the email. The payload depends on the host. A Slack incoming webhook (`hooks.slack.com`) receives Slack's own message shape, so it renders directly in the channel: ```json theme={null} { "text": "H100 is now available on Lyceum, launch an instance: https://dashboard.lyceum.technology/vms/launch" } ``` Any other HTTPS host receives a generic JSON event you can route yourself: ```json theme={null} { "event": "hardware.available", "hardware_profile": "h100", "hardware_label": "H100", "gpu_counts": [1, 8], "launch_url": "https://dashboard.lyceum.technology/vms/launch" } ``` ### Requirements The webhook URL must be an `https://` URL with a public hostname. URLs that point at `localhost`, a raw IP address, or a hostname that resolves to a private, loopback, or link-local address are rejected with a `422`. This keeps the notifier from being pointed at internal services. ### Getting a Slack webhook URL In Slack, create an app (or use an existing one), enable **Incoming Webhooks**, add a webhook to the channel you want, and copy the `https://hooks.slack.com/services/...` URL. Paste that as `webhook_url`. ## One notification per reminder A reminder fires once. When the profile becomes available and we notify you, the reminder is marked done and will not fire again. Set a new reminder if you want to be told the next time capacity comes online. Delivery is best effort. If your webhook endpoint is down when the profile becomes available, the email still goes out, but the webhook is not retried. # Launch an Instance Source: https://docs.lyceum.technology/docs/instances/launch Provision a dedicated GPU VM with SSH access Launching a VM requires three things: a hardware profile, a number of GPUs, and an SSH public key. The platform provisions the VM, configures it to accept your key, and returns the IP once it's ready. ## SSH keys Every VM is bootstrapped with one or more SSH public keys you provide. The corresponding private key stays on your machine and never touches Lyceum Cloud, that's the whole point of the asymmetric scheme. If you don't have a key, generate one: ```bash theme={null} ssh-keygen -t ed25519 -C "you@example.com" ``` Then provide the contents of `~/.ssh/id_ed25519.pub` (the public half) when launching. ## On-demand vs reserved capacity The platform supports launching VMs **on demand** (billed hourly, terminate any time) or under a **reserved-capacity contract** (1, 3, 6, or 12 months) at a discounted rate. For reserved capacity, contact the team via the dashboard's quote flow or [support@lyceum.technology](mailto:support@lyceum.technology), there's no self-service contract path. ## Checking availability Hardware availability varies by region and demand. Before launching, check whether the profile you want is available: ```http theme={null} GET /api/v2/external/vms/availability?hardware_profile=a100 ``` If the profile you want is out of stock, set an [availability reminder](/docs/instances/availability-reminders) and we notify you by email or webhook when it comes back. ## Launching ```bash theme={null} lyceum vm start \ -h h100 \ -k "$(cat ~/.ssh/id_ed25519.pub)" \ -g 1 ``` | Flag | Default | Description | | -------------------------- | -------- | --------------------------------------------------------- | | `-h`, `--hardware-profile` | `a100` | Hardware profile (`a100`, `h100`, ...) | | `-k`, `--key` | required | SSH public key (the contents, not a path) | | `-g`, `--gpu-count` | `1` | Number of GPUs | | `-a`, `--async` | off | Return immediately without waiting for the VM to be ready | By default the CLI waits for the VM to enter `ready` and prints the IP. Use `--async` to fire-and-forget; you can poll status afterwards with `lyceum vm status`. ```http theme={null} POST /api/v2/external/vms/create ``` Body: `user_public_key`, `hardware_profile`, optional `instance_specs`. Returns `vm_id`, `status`, `ip_address`, `created_at`. The VM starts in `provisioning` and transitions to `ready` once the IP is assigned. Poll `GET /vms/{vm_id}/status` until that happens, or use the CLI which does the polling for you. ## After launching Once the VM is `ready`: ```bash theme={null} ssh lyceum@ ``` From there it's a normal Linux box. Install whatever you need, upload data via [Storage](/docs/configuration/storage) or `scp`, run jobs, and `lyceum vm terminate ` when you're done. # Your VMs Source: https://docs.lyceum.technology/docs/instances/vms Manage long-lived GPU virtual machines A **VM** is a long-lived virtual machine you provision on Lyceum Cloud, accessible over SSH. Unlike serverless [Runs](/docs/serverless/runs), VMs persist until you terminate them and bill for their entire uptime, regardless of whether anything is running on them. ## Runs vs VMs Use a VM when: * You need an interactive shell, for debugging, exploration, or development * Your environment is hard to express as a one-shot script (custom drivers, system packages, manual setup) * You want to keep state between sessions (model weights, datasets, conda envs) * You need to SSH in from another tool (a remote VS Code session, a port-forwarded service, a profiling tool) Use a [Run](/docs/serverless/runs) when: * The work is a one-shot job that has a defined start and end * You don't need the machine after it finishes * You want to be billed only for active execution time ## Lifecycle VMs go through these states: `pending` / `provisioning` → `running` / `ready` → `stopped` / `terminated` / `error` A VM is billable from the moment it enters `running`, regardless of whether you've connected. Terminating it ends both the VM and the billing. ## Storage on VMs Each VM is provisioned with **at least 100 GB of local disk**, used for the OS, your code, and anything you write while the VM is running. This disk is **wiped on termination**, VMs are not durable storage. Your account's S3 bucket is **not auto-mounted** on VMs (unlike Docker runs, where it's mounted at `/mnt/s3` by default). If you want bucket access from a VM, mount it yourself with `s3fs`, `mc`, `rclone`, or any other S3 client using credentials from `POST /storage/credentials`, see [Storage](/docs/configuration/storage#direct-s3-access). Attaching extra persistent volumes to a VM is not currently supported through the API; the dashboard exposes a placeholder for this feature. ## Connecting Once a VM is `ready`, fetch its IP and SSH in using the key you supplied at launch: ```bash theme={null} lyceum vm status # shows IP and connection info ssh lyceum@ ``` The default SSH user is `lyceum`, not `root`. `lyceum vm status ` prints the full SSH command, including the port when the VM is behind a non-standard one. ## CLI ```bash theme={null} lyceum vm availability # show profiles, $/GPU/hr, and supported GPU counts lyceum vm start # provision a VM (-h profile, -k ssh-key, -g gpu-count) lyceum vm list # list your VMs lyceum vm status # status, IP, connection info lyceum vm terminate # terminate (use -f to skip confirmation) ``` `lyceum vm availability` prints a table of hardware profiles you can launch right now, their per-GPU hourly price, and which GPU-count configurations (1×, 2×, 4×, 8×) are currently supported. Use it before `lyceum vm start` to confirm capacity. ## REST API | Method | Endpoint | Purpose | | -------- | --------------------- | ----------------------------------------- | | `GET` | `/vms/list` | List all your VMs | | `GET` | `/vms/{vm_id}/status` | Status, IP, and connection info | | `GET` | `/vms/availability` | Check availability for a hardware profile | | `POST` | `/vms/create` | Provision a new VM | | `DELETE` | `/vms/{vm_id}` | Terminate a VM | For full request and response shapes see the [API Reference](/api-reference/introduction). Termination is permanent, the disk is wiped along with the VM. Save anything important to [Storage](/docs/configuration/storage) before terminating, since your storage bucket persists across VMs. Provision a new GPU VM with your SSH key. # Logs Source: https://docs.lyceum.technology/docs/observability/logs Query stdout, stderr, and platform logs from your runs Logs from every run are forwarded to Loki and exposed via two endpoints, one for everything you've ever run, one scoped to a specific execution. The platform tags log lines with your `user_id` and the run's `execution_id`, so you only ever see logs you own. ## Per-execution logs ```http theme={null} GET /api/v2/external/logs/execution/{execution_id} ``` | Query | Default | Range | Purpose | | ------- | ------- | ----------- | ----------------------------- | | `limit` | `1000` | `1`–`10000` | Maximum log entries to return | | `hours` | `24` | `1`–`168` | Look-back window in hours | This is the right endpoint to grep through a single run's output after it finishes, particularly useful when stdout/stderr were too large to surface inline in the run record. ```bash theme={null} curl "https://api.lyceum.technology/api/v2/external/logs/execution/$EXEC?limit=5000&hours=2" \ -H "Authorization: Bearer $LYCEUM_API_KEY" ``` ## Cross-run logs ```http theme={null} GET /api/v2/external/logs/user ``` | Query | Default | Range | Purpose | | ------- | ------- | ---------- | ----------------------------- | | `limit` | `100` | `1`–`5000` | Maximum log entries to return | | `hours` | `1` | `1`–`24` | Look-back window in hours | Returns log lines from every run on your account in the window. Useful for spot-checking recent activity or building a basic activity feed for a custom dashboard. ## What's logged Anything your code writes to stdout or stderr is captured. Lyceum doesn't inject its own log lines into your output; what you `print` is what you get back, plus any platform-level events surfaced as separate log streams. # GPU & System Metrics Source: https://docs.lyceum.technology/docs/observability/metrics Query per-execution GPU and system metrics from Prometheus For GPU runs, Lyceum Cloud records DCGM-sourced GPU metrics and system telemetry into Prometheus. The metrics endpoint queries that data per execution, so you can attribute time, debug stalls, and confirm a job actually used the GPU it was billed for. ## Endpoint ```http theme={null} GET /api/v2/external/execution/{execution_id}/metrics ``` | Query | Default | Purpose | | ------- | ------------------------------------- | ----------------------------------------- | | `start` | execution `start_time` | ISO 8601 start timestamp | | `end` | execution `end_time`, or current time | ISO 8601 end timestamp | | `step` | `15s` | Query resolution (e.g. `5s`, `15s`, `1m`) | By default the endpoint returns the entire execution at 15-second resolution. Narrow the window or change the step when you're zooming into a specific phase. ## Available series ### GPU (via DCGM) | Metric | Unit | | -------------------------------- | --------- | | `gpuUtilizationPercent` | 0–100 | | `gpuMemoryUtilizationPercent` | 0–100 | | `gpuTemperatureCelsius` | °C | | `gpuPowerWatt` | W | | `gpuPowerLimitWatt` | W | | `gpuClockSmMhz` | MHz | | `gpuClockMemMhz` | MHz | | `gpuPcieThroughputRxBytesPerSec` | bytes/sec | | `gpuPcieThroughputTxBytesPerSec` | bytes/sec | ### System | Metric | Unit | | ----------------------- | ----- | | `systemRamTotalBytes` | bytes | | `systemRamUsedBytes` | bytes | | `systemCpuUsagePercent` | 0–100 | ## Example ```bash theme={null} curl "https://api.lyceum.technology/api/v2/external/execution/$EXEC/metrics?step=5s" \ -H "Authorization: Bearer $LYCEUM_API_KEY" | jq ``` ## What it's good for * **Confirming GPU utilisation**, if `gpuUtilizationPercent` is consistently low during the heavy phase of a run, you're likely bottlenecked on data loading or CPU pre-processing * **Diagnosing OOMs**, `gpuMemoryUtilizationPercent` climbing to 100% just before a crash points to a memory issue rather than a code bug * **Power and thermal investigations**, `gpuPowerWatt` against `gpuPowerLimitWatt` and `gpuTemperatureCelsius` show whether the GPU is throttling * **Cost attribution**, combined with the run's wall-clock time, the metrics let you compute cost per GPU-hour-of-actual-work # Dashboard Overview Source: https://docs.lyceum.technology/docs/overview Navigate the Lyceum Cloud dashboard The [dashboard](https://dashboard.lyceum.technology) is the web UI for Lyceum Cloud. The sidebar groups everything into the same sections used in this documentation. Runs, Launch Run, Secrets Your VMs, Launch Instance Dedicated, Active Models, Serverless Notebooks, Clusters Storage, API Keys Billing, Organizations, Settings # Quickstart Source: https://docs.lyceum.technology/docs/quickstart Create an account, install the CLI or VS Code extension, and run your first job ## 1. Create an account Sign up at the [Lyceum Cloud Dashboard](https://dashboard.lyceum.technology) and verify a payment card (there's no charge until you add credits, and you pay only for what you use). An [organization](/docs/account/organizations) is created automatically as your default, every run, VM, and credit lives there until you create or join more orgs. See [Billing](/docs/account/billing) for how credits work. Setting this up for a whole team? An org owner can enable [SSO](/docs/account/sso) so everyone signs in through your company's identity provider instead of individual passwords. ## 2. Pick how you want to access the platform ### Install the CLI The CLI is published as the `lyceum` binary. Install with pip: ```bash theme={null} pip install lyceum-cli ``` ### Log in ```bash theme={null} lyceum auth login ``` You'll be prompted for your email and password. The command exchanges those for a JWT token stored locally. For long-running scripts and CI, generate an API key instead, see [API Keys](/docs/configuration/api-keys). ### Run your first job Run a one-liner on the platform: ```bash theme={null} lyceum python run "print('Hello from Lyceum')" ``` Run a Python file with a specific machine type: ```bash theme={null} lyceum python run script.py --machine gpu.a100 ``` ### Install the extension 1. Open VS Code 2. Open the Extensions view (Cmd/Ctrl+Shift+X) 3. Search for **Lyceum Cloud** 4. Install the official extension published by `lyceumtechnology` ### Authenticate 1. Open any `.py` or `.ipynb` file 2. Click the cloud icon in the editor toolbar 3. The extension opens the dashboard for sign-in and redirects back to VS Code ### Run code With a Python file open, click the cloud icon (or run the **Lyceum Cloud: Execute on Cloud** command). The current file is submitted as a run and streamed back into VS Code. ### Get an API key 1. Open the [Lyceum Cloud Dashboard](https://dashboard.lyceum.technology) 2. Go to **API Keys** 3. Click **New API Key**, pick the organization to scope it to, give it a name, optionally set an expiration 4. Copy the key (starts with `lk_`), it's only shown once API keys are tied to one organization at creation time. Requests made with the key are billed and authorized against that org. API keys grant full access to your account. Store them in a secret manager and never commit them to source control. ### Make a request The API base is `https://api.lyceum.technology/api/v2/external`. Authentication is `Authorization: Bearer `. ```bash theme={null} curl https://api.lyceum.technology/api/v2/external/billing/credits \ -H "Authorization: Bearer lk_your_api_key" ``` See the [API Reference](/api-reference/introduction) for the full endpoint list. ## 3. Where to next Submit Python or Docker workloads to GPU machines. Provision a dedicated GPU VM with SSH access. Stand up a Hugging Face model behind an OpenAI-compatible endpoint. Use your per-user S3 bucket for inputs and outputs. Need help? Email [support@lyceum.technology](mailto:support@lyceum.technology). # Webhook Callbacks Source: https://docs.lyceum.technology/docs/serverless/callbacks Get notified when a run finishes by having Lyceum POST to your URL When you submit a run, you can supply a list of URLs that Lyceum Cloud will call with completion info once the execution finishes. This is the right tool when you don't want to poll the API yourself. Common patterns: * A backend that submits a long training job and wants to be notified when it's done * A queue worker that fans out runs and needs to mark them complete in its own database * A CI pipeline waiting on a remote build For interactive workflows where a human is watching, the dashboard or `lyceum workloads list` is simpler. ## How it works The execution request body for all three execution types accepts a `user_callback_urls` field, a list of HTTPS URLs. When the run reaches a terminal state (`completed`, `failed`, `system_failure`, `aborted`, `cancelled`), Lyceum POSTs job completion info to each URL. The same field exists on: * Python runs (`POST /execution/streaming/start`) * Docker image runs (`POST /execution/image/start`) * Docker Compose runs (`POST /execution/compose/start`) ## Submitting a callback ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/execution/streaming/start \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "print(1+1)", "machine_type": "gpu.a100", "user_callback_urls": [ "https://your-service.example.com/lyceum-webhook" ] }' ``` You can supply more than one URL, useful for fanning out to multiple downstream systems. ## Receiving the callback The endpoint at the URL you registered will receive an HTTPS POST when the run finishes. Build your handler to: * **Return 2xx quickly**, Lyceum doesn't need a body, just an acknowledgement * **Verify the source**, gate the endpoint behind a shared secret in the URL path or a header you control, since Lyceum doesn't sign callbacks * **Be idempotent**, handle the case where a callback is delivered more than once ## Combining with secrets If your callback handler requires a token (e.g. an HMAC secret you check), put it in an environment variable on Lyceum [Secrets](/docs/serverless/secrets) and reference it in the URL or as a query parameter when you submit the run. That keeps the secret out of your codebase. # Launch a Run Source: https://docs.lyceum.technology/docs/serverless/launch-run Submit Python, Docker, or Docker Compose workloads Lyceum Cloud accepts three execution types: **Python**, **Docker image**, and **Docker Compose**. Pick the one that matches how your code is packaged. | You have... | Use | | -------------------------------------------- | -------------- | | A `.py` script and a `requirements.txt` | Python | | A container image (public or private) | Docker image | | A multi-service stack (`docker-compose.yml`) | Docker Compose | All three submission paths return an `execution_id` plus a streaming URL. From that point on you read logs, fetch results, and abort through the same set of endpoints, see [Runs](/docs/serverless/runs). ## Choosing a hardware profile Every run targets a **hardware profile** (e.g. `gpu.a100`, `gpu.h100`). You can only launch on profiles your account is authorised for. Three endpoints help you check what's available: | Endpoint | Returns | | ------------------------------------- | -------------------------------------- | | `GET /machine-types` | The full catalogue with hourly pricing | | `GET /user/quotas/available-hardware` | Just the profiles your account can use | | `GET /resources/available-resources` | Detailed hardware specs and pricing | If a profile you need isn't listed, contact [support@lyceum.technology](mailto:support@lyceum.technology). ## Python Python is the most common entry point. The CLI handles packaging your script, bundling local imports, and installing pip requirements before the code runs. The `.lyceum/config.json` workspace file lets you persist requirements and import paths so you don't need to repeat them on every invocation. ```bash theme={null} lyceum python run script.py --machine gpu.a100 --requirements requirements.txt ``` | Flag | Description | | ------------------------------ | ---------------------------------------------- | | `-m`, `--machine` | Hardware profile (`gpu.a100`, `gpu.h100`, ...) | | `-r`, `--requirements` | Path to a `requirements.txt` | | `-f`, `--file-name` | Display name for the run | | `--import` | Local imports to bundle | | `--use-config` / `--no-config` | Toggle reading `.lyceum/config.json` | | `-d`, `--debug` | Enable debug logging | Manage the workspace config (`.lyceum/config.json`) for shared dependencies and import paths: ```bash theme={null} lyceum python config init # create lyceum python config show # print current config lyceum python config refresh # regenerate ``` ```http theme={null} POST /api/v2/external/execution/streaming/start ``` Body fields include `code` (the Python source as a string), `machine_type`, optional `requirements`, `imports`, and other execution options. Returns `execution_id` and `stream_url`. ## Docker image Use Docker when your environment is already containerised, for example a CUDA image with preinstalled dependencies, or a job that doesn't fit the Python entrypoint cleanly. The platform pulls the image, runs the command you specify, and streams stdout/stderr back. For private registries, the request supports two credential modes: * **Basic auth**, username and password for any registry * **AWS**, access key, secret key, session token, and region for Amazon ECR ```bash theme={null} lyceum docker run python:3.11-slim -c "python -c 'print(1)'" -m gpu.a100 ``` | Flag | Default | Description | | ------------------ | ---------- | ---------------------------------------------------------------------------------------------------------- | | `-c`, `--command` | — | Command to run inside the container | | `-e`, `--env` | — | Environment variable, e.g. `KEY=value` (repeatable) | | `-m`, `--machine` | `gpu.a100` | Machine type | | `--s3` / `--no-s3` | on | Mount your storage bucket inside the container | | `--s3-mount-path` | `/mnt/s3` | Where to mount the bucket inside the container | | `--callback` | — | Webhook URL for completion notification | | `--registry-creds` | — | Private registry auth as a JSON string (see [Private registry credentials](#private-registry-credentials)) | | `--registry-type` | — | Registry credential type (`basic`, `aws`) | By default the bucket is mounted at `/mnt/s3` inside the container. ```http theme={null} POST /api/v2/external/execution/image/start ``` Body: `docker_image_ref`, `docker_run_cmd`, `docker_run_env`, `machine_type`, and `docker_registry_credential_type` (`basic` or `aws`) plus the matching credential fields when needed. For ECR pass `aws_access_key_id`, `aws_secret_access_key`, `aws_session_token`, `aws_region`. For other registries pass `docker_username` and `docker_password`. Set `enable_s3_mount: true` to mount your storage bucket (path defaults to `s3_mount_path: "/mnt/s3"`). `docker_run_env` is a single string of newline-separated `KEY=VALUE` pairs (`.env` style), for example `"DEBUG=true\nTIMEOUT=300"`. The CLI builds this for you from repeated `-e` flags. ### Private registry credentials Public images (for example `python:3.11-slim` from Docker Hub) need no credentials, just leave them out. A **private** registry requires login details so Lyceum can pull your image. Two modes are supported: | Mode | Use for | Fields | | ------- | ------------------------------------------------------------------------------- | --------------------------------------------- | | `basic` | Docker Hub (private), GHCR, GitLab, self-hosted, any username/password registry | username, password | | `aws` | Amazon ECR | access key, secret key, session token, region | `--registry-creds` and `--registry-type` must always be passed together. A Kubernetes-style `.dockerconfigjson` is not supported; use the JSON shapes below. The `--registry-creds` value is a JSON string whose keys depend on `--registry-type`: ```bash theme={null} # Basic auth (any username/password registry) lyceum docker run myregistry.io/team/app:tag -m gpu.a100 \ --registry-type basic \ --registry-creds '{"username": "me", "password": "••••••"}' # AWS ECR lyceum docker run 123456.dkr.ecr.eu-central-1.amazonaws.com/app:latest -m gpu.a100 \ --registry-type aws \ --registry-creds '{"aws_access_key_id": "AKIA…", "aws_secret_access_key": "••••", "aws_session_token": "••••", "region": "eu-central-1"}' ``` | Type | Required JSON keys | | ------- | ----------------------------------------------------------------------------------------------------- | | `basic` | `username`, `password` | | `aws` | `aws_access_key_id`, `aws_secret_access_key`, `aws_session_token`, `region` (defaults to `us-east-1`) | In the request body the fields are flat, not nested in a JSON string. Set `docker_registry_credential_type`, then the matching fields: ```json theme={null} // Basic auth { "docker_image_ref": "myregistry.io/team/app:tag", "docker_registry_credential_type": "basic", "docker_username": "me", "docker_password": "••••••" } ``` ```json theme={null} // AWS ECR { "docker_image_ref": "123456.dkr.ecr.eu-central-1.amazonaws.com/app:latest", "docker_registry_credential_type": "aws", "aws_access_key_id": "AKIA…", "aws_secret_access_key": "••••", "aws_session_token": "••••", "aws_region": "eu-central-1" } ``` The region key differs by interface: the CLI JSON uses `region`, the REST body uses `aws_region`. ## Docker Compose For multi-service stacks (e.g. an app talking to a database) you can submit a whole `docker-compose.yml`. The platform brings up all services on the same machine and tears them down when the entrypoint service exits. ```bash theme={null} lyceum compose run docker-compose.yml --machine gpu.a100 ``` | Flag | Description | | ----------------- | ---------------------------- | | `-m`, `--machine` | Hardware profile | | `--env-file` | Env file passed to the stack | ```http theme={null} POST /api/v2/external/execution/compose/start ``` Body: the `compose_file_code` as a string and a `machine_type`. ## Aborting Each execution type has its own abort endpoint. Use `abort` to immediately kill a run; for graceful stop (notebooks, interactive sessions), use `POST /workloads/stop/{execution_id}` instead. ```http theme={null} POST /execution/streaming/abort/{execution_id} POST /execution/image/abort/{execution_id} POST /execution/compose/abort/{execution_id} ``` ## See also Real CLI and curl invocations for Python and Docker runs. Monitor, log, and abort executions after they're submitted. # Runs Source: https://docs.lyceum.technology/docs/serverless/runs Monitor and manage executions on Lyceum Cloud A **run** (also called an **execution**) is a single, ephemeral invocation of code on Lyceum Cloud. You submit some code or a container, the platform schedules it onto a machine matching the hardware profile you asked for, streams logs back, and bills you per second of wall-clock time. When the code finishes, successfully or otherwise, the machine is released. Runs are the right tool when: * The work has a clear start and end (a training step, a batch transform, a one-off job) * You don't need to keep the environment around between invocations * You'd rather not manage a long-lived server For interactive sessions over SSH, use [Instances](/docs/instances/vms). For long-running model serving, use [Dedicated Inference](/docs/inference/dedicated). ## Execution types Three submission paths produce the same kind of run record: | Type | When to use it | Endpoint | | ------------------ | ------------------------------------------------------------------------------------- | --------------------------------- | | **Python** | A `.py` file or a snippet, with optional `requirements.txt` and bundled local imports | `POST /execution/streaming/start` | | **Docker image** | You already have a container image with your environment baked in | `POST /execution/image/start` | | **Docker Compose** | A multi-service stack defined in `docker-compose.yml` | `POST /execution/compose/start` | All three return an `execution_id` and a streaming URL. From that point on you interact with the run through the same set of read/abort endpoints, regardless of how it was submitted. ## Lifecycle A run moves through these states: `pending` → `starting` → `running` → `completed` (or `failed`, `system_failure`, `cancelled`, `aborted`) `pending` and `starting` cover queueing and machine provisioning. `running` is what it sounds like. The terminal states are mutually exclusive, `aborted` means a user (or the API) hard-stopped it, `cancelled` is a graceful stop (used for notebooks), `failed` is a non-zero exit, `system_failure` is an infra problem, and `completed` is a clean finish. You can list runs by status to monitor the queue or to find recently failed runs to investigate. ## CLI ```bash theme={null} lyceum workloads list # active runs lyceum workloads history # recent runs lyceum workloads abort # hard stop ``` `abort` marks the run `aborted` and kills the underlying job. The graceful-stop variant (which marks the run `completed`, used for notebook sessions you're ending normally) is API-only, see the workloads `/stop` endpoint below. ## REST API | Method | Endpoint | Purpose | | -------- | ----------------------------------- | ------------------------------------------------------ | | `GET` | `/workloads/list` | List your executions | | `GET` | `/execution/{execution_id}` | Full execution record (status, stdout, stderr, result) | | `GET` | `/execution/{execution_id}/timing` | Start, end, duration | | `GET` | `/execution/{execution_id}/metrics` | GPU and system metrics | | `GET` | `/logs/execution/{execution_id}` | Logs from Loki | | `POST` | `/workloads/abort/{execution_id}` | Hard-stop | | `POST` | `/workloads/stop/{execution_id}` | Graceful stop | | `DELETE` | `/execution/{execution_id}` | Delete the execution record | ## GPU and system metrics For runs on GPU machines, the platform records per-execution metrics from DCGM and system telemetry. The metrics endpoint exposes: * **GPU**, utilisation %, memory utilisation %, temperature °C, power draw and limit (W), SM and memory clocks (MHz), PCIe RX/TX throughput * **System**, total and used RAM, CPU usage % The query supports `start`, `end`, and `step` (default `15s`) so you can pull the whole run or a window. This is the same data the dashboard charts; you can use it to attribute time, debug stalls, and confirm a job actually used the GPU it was billed for. ## Logs Logs are stored in Loki and queryable by execution. By default the logs endpoint returns the last 24 hours of output for an execution (max 10,000 entries), adjust with `limit` and `hours` query parameters. ### Streaming logs live When you launch a run, the response includes a streaming URL (`stream_url`, also called `streaming_url`) that delivers stdout and stderr live over Server-Sent Events while the run executes. This is separate from the Loki query above, which serves historical logs after the fact. Connect with an HTTP `POST` and the header `Accept: text/event-stream`. Each message is a `data:` line carrying a JSON object. Three shapes occur: | Event (JSON in the `data:` line) | Meaning | | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | | `{"output": {"content": "..."}}` | One chunk of stdout or stderr | | `{"jobFinished": {"job": {"result": {"returnCode": 0, "error": null}}}}` | Run finished, carries the exit code and error. The stream ends here. | | `{"type": "output" \| "completed" \| "error", ...}` | Legacy format, still parsed for backward compatibility | ```python theme={null} import httpx, json with httpx.stream("POST", stream_url, headers={"Accept": "text/event-stream"}, timeout=600.0) as resp: for line in resp.iter_lines(): if not line.startswith("data: "): continue event = json.loads(line[6:]) if "output" in event: print(event["output"]["content"], end="") elif "jobFinished" in event: code = event["jobFinished"]["job"]["result"]["returnCode"] print(f"\nrun finished, exit code {code}") break ``` ## GPU selection runs In addition to the three standard execution types, Lyceum Cloud supports a **GPU selection** flow where the platform fans your code out across multiple GPU types and hands back results from each. The use case is benchmarking: you want to know which GPU is fastest or most cost-effective for a given workload before committing to a hardware profile. The flow is asynchronous: you submit code, get back an `execution_id` plus a callback token, and poll the status endpoint until the parent execution and its sub-jobs complete. ### CLI ```bash theme={null} lyceum gpu-selection run script.py lyceum gpu-selection status ``` ### REST API | Method | Endpoint | Purpose | | ------ | ------------------------------------------------ | --------------------------------------------------------------- | | `POST` | `/execution/gpu_selection/start` | Submit code for GPU selection | | `GET` | `/execution/gpu_selection/{execution_id}/status` | Get the current status of the parent execution and any sub-jobs | The parent execution gets recorded like any other run, and sub-jobs appear as related executions you can query individually. ## Inspecting an end-to-end workflow For a worked example that submits a run, polls until it's done, and pulls logs and metrics, see [End-to-End API Workflow](/examples/api-integration). Submit a Python or Docker workload. Inject environment variables into runs. # Secrets Source: https://docs.lyceum.technology/docs/serverless/secrets Store environment variables and credentials for your runs Secrets are key-value pairs stored on your account and injected into your runs as environment variables. Anything you'd otherwise put in a `.env` file or hard-code into your script, API keys, model tokens, database URLs, webhook endpoints, belongs here instead. The advantage over baking values into source is twofold: 1. **They never appear in your code or container images.** You can commit your script to a public repo and the secret stays with your Lyceum account. 2. **They're shared across runs.** Update a token once and every subsequent run picks up the new value. Each secret has a `key`, a `value`, and an `is_secret` flag. The `is_secret` flag affects how the value is displayed (always masked when set), but both kinds reach your code identically. ## Reading secrets in your code Secrets become normal environment variables in the run's environment, accessible the usual way: ```python theme={null} import os token = os.environ["HF_TOKEN"] db_url = os.environ.get("DATABASE_URL") ``` Inside Docker containers the same applies. ## REST API | Method | Endpoint | Purpose | | -------- | ------------------------------------- | ---------------------------------------------- | | `GET` | `/environment-variables/` | List your variables | | `POST` | `/environment-variables/` | Create or upsert (`key`, `value`, `is_secret`) | | `DELETE` | `/environment-variables/{env_var_id}` | Delete a single variable | | `DELETE` | `/environment-variables/` | Delete every variable on your account | The upsert endpoint accepts a single object or a batch, you can roll out a new set of variables in one call. ### Validation * Names must start with a letter or underscore * Names may contain letters, digits, and underscores only * Duplicate names within a single request are rejected ### What not to put here Secrets are intended for short, individual values, tokens, URLs, IDs. Don't try to store large blobs (private keys, certificates, configuration files) here. For those, upload to [Storage](/docs/configuration/storage) and read the file from your run. # Pythia Source: https://docs.lyceum.technology/docs/tools/pythia Automatically find the right GPU for your PyTorch workload Pythia analyzes the computational characteristics of your PyTorch workloads and maps them to the GPU architectures that deliver the best performance-cost efficiency. Instead of guessing which GPU you need or over-provisioning expensive resources, Pythia provides clear, evidence-based guidance on which option matches your requirements. Pythia currently evaluates T4, A100, and H100 GPUs. ## What You Get Run a single command and Pythia will: * **Predict memory requirements** to identify compatible hardware * **Estimate runtime** for each GPU option * **Recommend the best GPU** based on your workload ``` ╭─────────────────────── GPU Selection Results ───────────────────────╮ │ │ │ ✓ Analysis complete │ │ │ │ Recommended: A100 (40 GB VRAM) │ │ │ ╰─────────────────────────────────────────────────────────────────────╯ Performance Results ┏━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┓ ┃ GPU ┃ Status ┃ Time ┃ Cost ┃ ┡━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━┩ │ T4 │ success │ 8.2hr │ $2.46 │ │ A100 │ success │ 3.1hr │ $5.27 │ │ H100 │ success │ 1.8hr │ $10.44 │ └──────┴─────────┴─────────┴─────────┘ Compatible GPUs ┏━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓ ┃ GPU ┃ VRAM ┃ GPUs Needed ┃ Utilization ┃ ┡━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩ │ T4 │ 16 GB │ 1 │ 68% │ │ A100 │ 40 GB │ 1 │ 27% │ │ H100 │ 80 GB │ 1 │ 14% │ └───────┴───────┴─────────────┴─────────────┘ ``` ## CLI Usage ### Run GPU Selection ```bash theme={null} lyceum gpu-selection run train.py ``` Pass script arguments after `--`: ```bash theme={null} lyceum gpu-selection run train.py -- --epochs 10 --lr 0.001 ``` ### Options | Option | Description | | -------------------- | --------------------------------------------------- | | `-f, --file-name` | Name for the execution | | `-t, --timeout` | Timeout per sub-job in seconds (1-600, default: 60) | | `-r, --requirements` | Requirements file path or pip requirements string | | `--import` | Pre-import modules (can be used multiple times) | | `--no-config` | Skip workspace config from `.lyceum/config.json` | | `-d, --debug` | Show detailed debug information | ### Check Status ```bash theme={null} lyceum gpu-selection status ``` ## Requirements * Single Python script as entry point (executed as main) * Must target CUDA/GPU as the PyTorch device * Command-line arguments are supported * PyTorch and PyTorch Lightning * Imported models (Hugging Face, torchvision, timm) * Single model, single GPU * Jupyter notebooks (not supported at this point) * Advanced or custom gradient manipulation * CUDA extensions that bypass PyTorch tensors ## Prediction Details Predicts training and inference memory for a single model. Accounts for gradients, model weights, activations, optimizers, and mixed-precision. Does not account for data transfer outside the training/inference loop or operator memory overhead (intermediate tensors). Estimates inference/training loop time based on iteration time. Does not account for VM startup time or initial data downloading and loading. # Clusters Source: https://docs.lyceum.technology/docs/workloads/clusters Dedicated multi-node GPU clusters with InfiniBand networking Clusters are an **enterprise** offering. Capacity is reserved per contract, there is no self-service launch flow. Clusters are dedicated multi-node GPU deployments built for distributed training and large-scale inference. They span H100, H200, and B200 hardware interconnected with 400 Gb/s NDR InfiniBand for the cross-node bandwidth distributed training requires. ## When you need a cluster A cluster is the right primitive when: * Your training job spans multiple machines (NCCL all-reduce across nodes) * You need a fixed pool of capacity reserved for a project, with predictable cost * You want bare-metal performance with no neighbours and a known network topology * You're running for weeks or months, not hours For everything else, use: * **A single VM** for one machine of any size, see [Instances](/docs/instances/vms) * **Serverless runs** for one-shot jobs that fit on a single node, see [Runs](/docs/serverless/runs) * **Dedicated inference** for serving models, see [Dedicated Inference](/docs/inference/dedicated) ## What you get * Bare-metal nodes (H100, H200, or B200) with 400 Gb/s InfiniBand * Reserved capacity for the contract duration (1 week to 1 year+) * Enterprise SLA support * Per-cluster access credentials (SSH keys, VPN, kubeconfig as appropriate) * Hardware health telemetry (GPU temperature, memory, power) and benchmark results ## Managing a cluster Provisioned multi-node deployments are exposed under `/api/v2/external/infra/deployments`. The same data backs the dashboard's nodes, access, benchmarks, contract, and invoice views. Each cluster has: * **Nodes**, per-node hardware specs and live health snapshots * **Access**, credentials for SSH, VPN, kubeconfig, or web console * **Benchmarks**, synthetic performance benchmarks run on the hardware (with downloadable raw results) * **Contract**, the agreement covering this cluster (term, monthly price, notice period, downloadable PDF) * **Invoices**, billing history specific to this cluster ## Get in touch For pricing and provisioning details, contact [support@lyceum.technology](mailto:support@lyceum.technology). # Docker Execution Source: https://docs.lyceum.technology/docs/workloads/docker-execution Run any OCI-compatible Docker container on Lyceum Cloud, with optional GPU ## Video Tutorial
Bring your own image from Docker Hub, AWS ECR, or any private registry. Containers run with optional GPU acceleration and stream output back to you in real time. ## CLI The `lyceum docker run` command mirrors `docker run` semantics. ```bash theme={null} # Public image, CPU lyceum docker run python:3.11 -c "python -c 'print(1+1)'" # Override the entrypoint and pass environment variables lyceum docker run myapp:latest -e "DEBUG=true" -e "API_KEY=secret" # GPU workload, detached lyceum docker run nvidia/cuda:12.0-base -m gpu.a100 -d ``` By default the command streams container output until completion. Pass `-d` / `--detach` to return immediately and reconnect later with `lyceum docker logs `. ### Flags | Flag | Default | Description | | -------------------- | ---------- | ----------------------------------------------------------------------- | | `image` (positional) | — | Docker image reference, e.g. `python:3.11` or `myregistry.com/img:tag` | | `-m, --machine` | `gpu.a100` | Machine type, see [Machine Types](/docs/configuration/machine-types) | | `-t, --timeout` | `300` | Execution timeout in seconds | | `-c, --command` | — | Command to run inside the container (single string, parsed shell-style) | | `-e, --env` | — | Environment variable as `KEY=value` (repeatable) | | `-f, --file-name` | — | Friendly name for the execution | | `-d, --detach` | `false` | Return immediately after starting the container | | `--callback` | — | Webhook URL notified on completion | | `--registry-creds` | — | Private registry credentials as a JSON string | | `--registry-type` | — | Credential type: `basic` or `aws` | | `--s3 / --no-s3` | `--s3` | Mount your S3 storage inside the container | | `--s3-mount-path` | `/mnt/s3` | Path inside the container where S3 is mounted | | `--graceful-timeout` | — | Seconds to wait for graceful shutdown on cancel | ### Private Registries ```bash theme={null} lyceum docker run myuser/private-image:tag \ --registry-type basic \ --registry-creds '{"username": "myuser", "password": "mytoken"}' ``` ```bash theme={null} lyceum docker run 123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:latest \ --registry-type aws \ --registry-creds '{ "region": "us-west-2", "aws_access_key_id": "AKIAI...", "aws_secret_access_key": "wJalrX..." }' ``` An `aws_session_token` field can be added if you use temporary credentials. For a printable cheat-sheet, run `lyceum docker registry-examples`. ### Streaming Logs Later ```bash theme={null} lyceum docker logs 9d73319c-6f1c-4b4c-90e4-044244353ce4 ``` `Ctrl+C` only disconnects from the log stream, it does not cancel the running execution. ## API The CLI is a thin wrapper around `POST /api/v2/external/execution/image/start`. Use the API directly when integrating with custom orchestration. ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/execution/image/start \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "docker_image_ref": "pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime", "docker_run_cmd": ["python", "train.py"], "execution_type": "gpu.a100", "timeout": 3600 }' ``` ### Request fields | Field | Type | Description | | ---------------------------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------- | | `docker_image_ref` | string (required) | Fully qualified image reference | | `execution_type` | string | Machine type (`gpu.a100`, `gpu.h100`, …). Defaults to `gpu.a100`. | | `docker_run_cmd` | string\[] | Override container command | | `docker_run_env` | string | Environment variables, newline-separated `KEY=VALUE` pairs | | `timeout` | integer | Maximum execution time in seconds | | `docker_registry_credential_type` | string | `basic` or `aws` | | `docker_username` / `docker_password` | string | Basic registry credentials | | `aws_access_key_id` / `aws_secret_access_key` / `aws_session_token` / `aws_region` | string | AWS ECR credentials | | `enable_s3_mount` | bool | Mount user S3 storage inside the container | | `s3_mount_path` | string | Mount path (default `/mnt/s3`) | | `user_callback_urls` | string\[] | Webhook URLs for streamed output | | `graceful_timeout` | integer | Seconds to wait for graceful shutdown on cancel | | `file_name` | string | Friendly name for the execution | See the [API Reference](/api-reference/introduction) for the complete schema and response shape. ## Troubleshooting * Verify the image name and tag exist * Check registry credentials and `--registry-type` * Ensure the image is built for `linux/amd64` * Test the pull locally first: `docker pull your-image:tag` Run `lyceum auth login` to refresh your credentials, then retry. Need help? Contact [support@lyceum.technology](mailto:support@lyceum.technology). # Notebooks Source: https://docs.lyceum.technology/docs/workloads/notebooks Launch Jupyter notebook servers on Lyceum Cloud machines `lyceum notebook launch` provisions a machine, starts a Jupyter notebook server on it, and prints the URL once it's ready. You connect to the server in your browser and work in JupyterLab as you would locally. This is the right tool when you want an interactive notebook session on a GPU machine without managing a VM yourself. The session bills for as long as the notebook server is up; stop it from the CLI when you're done. For one-shot batch execution of an existing `.ipynb` file, submit it as a Python run instead, see [Launch a Run](/docs/serverless/launch-run). ## Launching a notebook ```bash theme={null} lyceum notebook launch -m gpu.a100 ``` | Flag | Default | Description | | ----------------- | ------------------------------ | -------------------------------------- | | `-m`, `--machine` | `gpu.a100` | Hardware profile | | `-t`, `--timeout` | `600` | Session timeout in seconds (max `600`) | | `-i`, `--image` | `jupyter/base-notebook:latest` | Custom Jupyter image | | `--token` | `lyceum` | Jupyter notebook token | | `-p`, `--port` | `8888` | Port for the Jupyter server | The CLI prints the notebook URL once the server is ready. Open it in your browser to start working. ## Listing and stopping ```bash theme={null} lyceum notebook list # list active sessions lyceum notebook stop # stop a session ``` The session shows up in `lyceum workloads list` like any other execution, with logs and metrics available through the same endpoints. # Python Execution Source: https://docs.lyceum.technology/docs/workloads/python-execution Run Python code and Jupyter notebooks on Lyceum Cloud from VS Code or the CLI ## Video Tutorial
## VS Code Extension The recommended way to run Python on Lyceum Cloud. ### Installation 1. Open VS Code 2. Go to Extensions (`Ctrl+Shift+X`) 3. Search for "Lyceum Cloud" 4. Click Install
Install Lyceum Cloud Extension
### Authentication 1. Open any `.py` or `.ipynb` file 2. Click the cloud icon in the editor toolbar 3. Authenticate in your browser 4. Return to VS Code, you're ready Authentication tokens are securely stored and automatically refreshed. ### Running Code **Toolbar Button**, Click the cloud icon in the editor toolbar **Keyboard Shortcut**, `Ctrl+Shift+E` (`Cmd+Shift+E` on Mac) **Command Palette**, `Ctrl+Shift+P` → "Execute on Cloud"
Execute Python Code
**Cell Execution** * Open a Jupyter notebook * Click "Execute on Cloud" on any code cell * View real-time output as it streams from the cloud **Kernel State** * The extension keeps track of previous executions * Variables are synced between local and remote environments Notebook outputs, including visualizations and data frames, are displayed in VS Code just like local execution.
### File Operations Right-click files or folders in the VS Code Explorer → "Upload to Lyceum Cloud". View, download, and delete files in the "Lyceum Cloud Files" panel. ### Commands Reference | Command | Shortcut | Description | | ---------------------- | -------------- | ------------------------------------------ | | Execute on Cloud | `Ctrl+Shift+E` | Run current Python file or notebook cell | | Upload to Lyceum Cloud | `Ctrl+Shift+U` | Upload selected files | | Manage Executions | `Ctrl+Shift+M` | Open execution management panel | | Configure API Token | — | Set authentication token (manual override) | | View Execution History | — | Show execution history | ## CLI ### Installation ```bash theme={null} pip install lyceum-cli ``` ### Authentication ```bash theme={null} lyceum auth login ``` This opens a browser for OAuth-style authentication. For headless environments, use `lyceum auth login --manual --api-key `. Check status at any time: ```bash theme={null} lyceum auth status ``` ### Running Code ```bash theme={null} # Run inline code lyceum python run "print('Hello from Lyceum Cloud!')" # Run a file lyceum python run hello.py # Run with a specific machine type (defaults to cpu) lyceum python run train.py -m gpu.a100 # Run with requirements lyceum python run script.py -r requirements.txt # Pre-import modules (can be repeated) lyceum python run script.py --import numpy --import pandas # Pass script arguments after -- lyceum python run train.py -- --epochs 10 --batch-size 32 ``` See [Machine Types](/docs/configuration/machine-types) for the available `-m` values. ### Workspace Configuration If a `.lyceum/config.json` file exists in your project, the CLI will automatically pick up dependencies and other settings from it. ```bash theme={null} # Initialize config in the current workspace lyceum python config init # View current config lyceum python config show ``` To skip the workspace config for a single run, pass `--no-config`. ## Troubleshooting * Re-authenticate by clicking the cloud icon in VS Code or running `lyceum auth login` * Check that you're signed in to the [dashboard](https://dashboard.lyceum.technology) * Verify internet connectivity and firewall settings * Verify network connectivity during upload * Ensure you are authenticated * For very large files, contact support Need help? Contact [support@lyceum.technology](mailto:support@lyceum.technology). # End-to-End API Workflow Source: https://docs.lyceum.technology/examples/api-integration Authenticate, list machines, submit a run, and stream the result over the REST API This walks through a full API workflow with `curl` and `jq`. Replace `$LYCEUM_API_KEY` with an API key from [API Keys](/docs/configuration/api-keys). ```bash theme={null} export LYCEUM_API_KEY="lk_..." export BASE="https://api.lyceum.technology/api/v2/external" ``` ## 1. Verify the key works ```bash theme={null} curl -s "$BASE/billing/credits" \ -H "Authorization: Bearer $LYCEUM_API_KEY" | jq ``` ## 2. List the hardware profiles you can use ```bash theme={null} curl -s "$BASE/user/quotas/available-hardware" \ -H "Authorization: Bearer $LYCEUM_API_KEY" | jq ``` For pricing too: ```bash theme={null} curl -s "$BASE/machine-types" \ -H "Authorization: Bearer $LYCEUM_API_KEY" | jq ``` ## 3. Submit a run ```bash theme={null} EXEC=$(curl -s -X POST "$BASE/execution/streaming/start" \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "import sys; print(sys.version)", "machine_type": "cpu" }' | jq -r .execution_id) echo "execution: $EXEC" ``` ## 4. Poll status until done ```bash theme={null} while :; do STATUS=$(curl -s "$BASE/execution/streaming/$EXEC/status" \ -H "Authorization: Bearer $LYCEUM_API_KEY" | jq -r .status) echo "$STATUS" case "$STATUS" in completed|failed|aborted|cancelled|system_failure) break ;; esac sleep 2 done ``` ## 5. Fetch the full record ```bash theme={null} curl -s "$BASE/execution/$EXEC" \ -H "Authorization: Bearer $LYCEUM_API_KEY" | jq ``` ## 6. Fetch logs ```bash theme={null} curl -s "$BASE/logs/execution/$EXEC" \ -H "Authorization: Bearer $LYCEUM_API_KEY" | jq ``` ## 7. Fetch GPU metrics For GPU runs, per-execution GPU and system metrics are available: ```bash theme={null} curl -s "$BASE/execution/$EXEC/metrics?step=15s" \ -H "Authorization: Bearer $LYCEUM_API_KEY" | jq ``` ## 8. Aborting a run ```bash theme={null} curl -X POST "$BASE/execution/streaming/abort/$EXEC" \ -H "Authorization: Bearer $LYCEUM_API_KEY" ``` Use `/execution/image/abort/...` for Docker runs and `/execution/compose/abort/...` for Compose stacks. # Upload and Process Data Source: https://docs.lyceum.technology/examples/data-processing Move files into your storage bucket and read them from a run Every account has a per-user S3-compatible bucket. This example uploads a CSV, runs a Python job that reads it, and downloads the result. ## Upload a file ### CLI ```bash theme={null} lyceum storage load data.csv --key inputs/data.csv ``` ### REST API ```bash theme={null} curl -X POST "https://api.lyceum.technology/api/v2/external/storage/upload?key=inputs/data.csv" \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -F "file=@data.csv" ``` ## Direct S3 access via temporary credentials For larger transfers or programmatic clients, fetch short-lived MinIO/S3 credentials and use any standard S3 client: ```python theme={null} import boto3, requests api_key = "lk_..." creds = requests.post( "https://api.lyceum.technology/api/v2/external/storage/credentials", headers={"Authorization": f"Bearer {api_key}"}, ).json() s3 = boto3.client( "s3", endpoint_url=creds["endpoint"], aws_access_key_id=creds["access_key"], aws_secret_access_key=creds["secret_key"], aws_session_token=creds["session_token"], region_name=creds["region"], ) s3.upload_file("data.csv", creds["bucket_name"], "inputs/data.csv") ``` ## List, download, delete ```bash theme={null} lyceum storage ls inputs/ -r lyceum storage download inputs/data.csv ``` ```bash theme={null} curl "https://api.lyceum.technology/api/v2/external/storage/list-files?prefix=inputs/" \ -H "Authorization: Bearer $LYCEUM_API_KEY" curl "https://api.lyceum.technology/api/v2/external/storage/download/inputs/data.csv" \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -o data.csv curl -X DELETE "https://api.lyceum.technology/api/v2/external/storage/delete/inputs/data.csv" \ -H "Authorization: Bearer $LYCEUM_API_KEY" ``` Delete a whole prefix with `DELETE /storage/delete-folder/{folder_prefix}`. # Run Docker Containers Source: https://docs.lyceum.technology/examples/docker-containers Submit Docker images and Docker Compose stacks to Lyceum Cloud Lyceum Cloud supports two Docker execution modes: **single image** and **Compose stack**. Both stream logs back the same way as Python runs. ## Single image ### CLI ```bash theme={null} lyceum docker run python:3.11-slim \ -c "python -c 'print(1+1)'" \ -m cpu ``` ### REST API ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/execution/image/start \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "docker_image_ref": "python:3.11-slim", "docker_run_cmd": ["python", "-c", "print(1+1)"], "machine_type": "cpu", "docker_registry_credential_type": "none" }' ``` ### Private registries For private images, set `docker_registry_credential_type` to `basic` (with `username`/`password`) or `aws` (with `aws_access_key_id`, `aws_secret_access_key`, `aws_session_token`, `aws_region`) for ECR. ## Docker Compose ### CLI ```bash theme={null} lyceum compose run docker-compose.yml --machine gpu.a100 ``` ### REST API ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/execution/compose/start \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "compose_file_code": "version: \"3\"\nservices:\n app:\n image: python:3.11-slim\n command: python -c \"print(1+1)\"", "machine_type": "cpu" }' ``` ## Aborting ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/execution/image/abort/ \ -H "Authorization: Bearer $LYCEUM_API_KEY" ``` The same `/abort/{execution_id}` shape exists under `execution/streaming/` and `execution/compose/`. # Deploy a Hugging Face Model Source: https://docs.lyceum.technology/examples/machine-learning Stand up a dedicated inference endpoint and call it like OpenAI Dedicated deployments host any Hugging Face model on reserved GPU replicas and expose it through an OpenAI-compatible Chat Completions endpoint. ## Deploy ### CLI ```bash theme={null} lyceum infer deploy meta-llama/Llama-3.1-8B-Instruct \ --hardware-profile gpu.a100 \ --min-replicas 1 \ --max-replicas 3 ``` ### REST API ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/inference/create \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "hf_model_id": "meta-llama/Llama-3.1-8B-Instruct", "hardware_profile": "gpu.a100", "min_replicas": 1, "max_replicas": 3 }' ``` For gated models, include `hf_token` in the request body or pass `--hf-token` to the CLI. ## Wait for it to be healthy ```bash theme={null} lyceum infer status --wait ``` Or poll the API: ```bash theme={null} curl "https://api.lyceum.technology/api/v2/external/inference/get?deployment_id=" \ -H "Authorization: Bearer $LYCEUM_API_KEY" ``` ## Call the deployment ```bash theme={null} curl https://api.lyceum.technology/api/v2/external/v1/chat/completions \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "", "messages": [{"role": "user", "content": "Say hello in three languages."}] }' ``` The response shape mirrors OpenAI's: `id`, `choices`, `usage`, `model`, `created`. ### From the OpenAI Python SDK Because the endpoint is OpenAI-compatible, you can point the official `openai` client at it: ```python theme={null} from openai import OpenAI client = OpenAI( api_key="", base_url="https://api.lyceum.technology/api/v2/external", ) resp = client.chat.completions.create( model="", messages=[{"role": "user", "content": "hello"}], ) print(resp.choices[0].message.content) ``` ## Stop the deployment ```bash theme={null} lyceum infer stop ``` ```bash theme={null} curl -X DELETE https://api.lyceum.technology/api/v2/external/inference/stop \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{"deployment_id": ""}' ``` # Run Python Source: https://docs.lyceum.technology/examples/python-basics Submit Python code to Lyceum Cloud from the CLI or REST API The simplest workflow: submit a Python snippet, get back an `execution_id`, fetch the result. ## CLI ```bash theme={null} lyceum python run "print('hello from lyceum')" --machine cpu ``` Run a file with a `requirements.txt`: ```bash theme={null} lyceum python run script.py --machine gpu.a100 --requirements requirements.txt ``` ## REST API ```bash theme={null} curl -X POST https://api.lyceum.technology/api/v2/external/execution/streaming/start \ -H "Authorization: Bearer $LYCEUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "print(1 + 1)", "machine_type": "cpu" }' ``` The response includes an `execution_id` and a streaming URL for live output. ## Fetching results ```bash theme={null} # Get the full execution record (status, stdout, stderr, result) curl https://api.lyceum.technology/api/v2/external/execution/ \ -H "Authorization: Bearer $LYCEUM_API_KEY" ``` ```bash theme={null} # Get just the logs curl https://api.lyceum.technology/api/v2/external/logs/execution/ \ -H "Authorization: Bearer $LYCEUM_API_KEY" ``` # VM Instances Source: https://docs.lyceum.technology/examples/vm-instances Provision GPU/CPU virtual machines and run workloads on them VMs are dedicated, long-lived machines you SSH into. Use them for training jobs that span hours, interactive development, or anything that doesn't fit a one-shot serverless run. Provisioning typically takes 1–3 minutes. The CLI polls automatically; the API exposes `GET /vms/{vm_id}/status` so you can poll yourself. ## Quick Start with the CLI ```bash theme={null} # Authenticate (one-time) lyceum auth login # See what hardware is available right now lyceum vm availability # Start an A100 instance lyceum vm start \ -h a100 \ -k "$(cat ~/.ssh/id_ed25519.pub)" # CLI waits for the VM to become ready and prints its IP. # Then SSH in as the lyceum user (vm status prints the full command). ssh lyceum@ ``` To launch with multiple GPUs, add `-g 2` (or `4`, `8` if the profile supports it). To return immediately without waiting, add `-a` / `--async`. For the full set of available profiles and flags see the [VM page](/docs/instances/vms) and [Launch an Instance](/docs/instances/launch). ## ML Training Workflow ```bash theme={null} lyceum vm start -h a100 -k "$(cat ~/.ssh/id_ed25519.pub)" ``` ```bash theme={null} ssh lyceum@ # On the VM nvidia-smi # verify GPU is visible git clone https://github.com/your-org/ml-project.git cd ml-project python -m venv venv && source venv/bin/activate pip install -r requirements.txt ``` ```bash theme={null} tmux new -s training python train.py --epochs 100 --batch-size 32 # Detach with Ctrl+B, D, reattach later with: tmux attach -t training ``` ```bash theme={null} # From your local machine scp root@:~/ml-project/model.pt ./ # Tear the VM down lyceum vm terminate -f ``` Local disk on a VM is **wiped on termination**. Anything you want to keep should be `scp`'d off, pushed to git, or written to your [storage bucket](/docs/configuration/storage) before terminating. ## API Examples The CLI is a thin wrapper around the VMs API. Use the API directly when you need to integrate provisioning into your own tooling. ### Provision and wait ```python Python theme={null} import requests import time BASE_URL = "https://api.lyceum.technology" TOKEN = "your-token" def create_vm(public_key: str, hardware_profile: str = "a100", gpu_count: int = 1): """Create a new VM instance.""" payload = { "user_public_key": public_key, "hardware_profile": hardware_profile, "instance_specs": {"gpu_count": gpu_count}, } r = requests.post( f"{BASE_URL}/api/v2/external/vms/create", headers={"Authorization": f"Bearer {TOKEN}"}, json=payload, ) r.raise_for_status() return r.json() def wait_for_ready(vm_id: str, timeout: int = 600) -> dict: """Poll until the VM is ready or fails.""" start = time.time() while time.time() - start < timeout: r = requests.get( f"{BASE_URL}/api/v2/external/vms/{vm_id}/status", headers={"Authorization": f"Bearer {TOKEN}"}, ) r.raise_for_status() status = r.json() if status["status"] in ("ready", "running"): return status if status["status"] in ("failed", "error"): raise RuntimeError(f"VM failed: {status}") print(f"Status: {status['status']}...") time.sleep(20) raise TimeoutError("VM provisioning timed out") with open("/Users/me/.ssh/id_ed25519.pub") as f: public_key = f.read().strip() vm = create_vm(public_key, hardware_profile="a100", gpu_count=1) print(f"Created VM: {vm['vm_id']}") ready = wait_for_ready(vm["vm_id"]) print(f"VM ready! IP: {ready['ip_address']}") ``` ```bash cURL theme={null} # Create VM curl -X POST https://api.lyceum.technology/api/v2/external/vms/create \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "user_public_key": "ssh-ed25519 AAAAC3...", "hardware_profile": "a100", "instance_specs": {"gpu_count": 1} }' # Poll status curl https://api.lyceum.technology/api/v2/external/vms//status \ -H "Authorization: Bearer " # Terminate curl -X DELETE https://api.lyceum.technology/api/v2/external/vms/ \ -H "Authorization: Bearer " ``` ### List VMs ```python theme={null} def list_vms(): r = requests.get( f"{BASE_URL}/api/v2/external/vms/list", headers={"Authorization": f"Bearer {TOKEN}"}, ) r.raise_for_status() return r.json().get("vms", []) for vm in list_vms(): name = vm.get("name") or "-" print(f"{vm['vm_id']:38} {vm['status']:10} {vm.get('hardware_profile', '-'):10} {name}") ``` ### Check availability ```python theme={null} def check_availability(): r = requests.get( f"{BASE_URL}/api/v2/external/vms/availability", headers={"Authorization": f"Bearer {TOKEN}"}, ) r.raise_for_status() return r.json().get("available_hardware_profiles", []) for profile in check_availability(): print(f"{profile['hardware_profile']}: ${profile.get('price_per_hour', 0):.2f}/GPU/hr") ``` ## Common Patterns ```bash theme={null} tmux new -s training python train.py --epochs 1000 # Detach: Ctrl+B, D # Reattach: tmux attach -t training ``` ```bash theme={null} # Upload scp ./data.tar.gz root@:~/ # Download scp root@:~/results/* ./local-results/ # Sync directories rsync -avz ./project/ root@:~/project/ ``` ```bash theme={null} # Jupyter on 8888 ssh -L 8888:localhost:8888 root@ # Multiple ports ssh -L 8888:localhost:8888 -L 6006:localhost:6006 root@ ``` VMs bill from the moment they enter `running` until you terminate them, regardless of whether anything is executing. Run `lyceum vm list` periodically to make sure you don't have idle instances.