# Mobiscroll Connect Documentation > API reference and developer guides for Mobiscroll Connect — a unified OAuth 2.0 calendar integration service supporting Google Calendar, Outlook, Apple Calendar, and CalDAV. This file contains all documentation content in a single document following the llmstxt.org standard. ## AI Integration # AI Integration Mobiscroll provides a set of machine-readable documentation files, AI behavior rules, and a live **MCP server** that enable coding assistants to generate accurate Connect API code. These prevent common AI issues like hallucinated endpoints, mixing Connect API calls with UI component code, and outdated authentication patterns. ## Why AI integration? AI coding assistants work best when they have access to structured, authoritative documentation. Without it, they often: - **Hallucinate APIs** — invent endpoint paths, request parameters, response shapes, or SDK method signatures that don't exist - **Mix UI with backend** — generate JSX component code when asked about the Connect REST API, or vice versa - **Reference outdated versions** — generate API calls or SDK code that no longer match the current Connect schema - **Ignore authentication requirements** — skip OAuth flows or use incorrect scopes for calendar access The Mobiscroll AI integration solves these problems by providing Connect-specific documentation optimized for AI consumption, combined with behavior rules that enforce domain isolation, and an optional MCP server that serves live, version-stamped endpoint and SDK schemas on demand. ## Architecture overview The integration consists of four layers: ### Data layer — llms files Machine-readable documentation files containing the complete Mobiscroll Connect API reference and guides. These are the source of truth that AI tools read to answer questions. | File | Description | |:---|:---| | `llms-connect-full.txt` | Complete Mobiscroll Connect documentation | | `llms-connect.txt` | Connect table of contents (links to individual pages) | | `llms-icons.txt` | Icon names (IcoMoon, Font Awesome, Ionicons) — all frameworks | :::info You don't need to download or host these files — the rules and routing layers reference them directly and fetch their content automatically. ::: ### Rules layer — .mdc files Behavior rule files that tell AI assistants which package to use, which APIs are available, and what to avoid. | File | Domain | |:---|:---| | | Mobiscroll Connect (backend / API) | ### Routing layer — CLAUDE.md A context file specifically for Claude Code that provides domain detection signals, deterministic routing rules, API intent mapping, and anti-pattern examples. It ensures Claude selects the Connect documentation and never conflates Connect API calls with UI component code. ### Live schema layer — MCP server The **Mobiscroll MCP server** serves structured, version-stamped knowledge over the Model Context Protocol. It is a single, unified server: the same `mobiscroll` server that serves UI component schemas also exposes the Connect tools, generated directly from the Connect REST source and the 7-language SDK suite. The Connect tools are all prefixed `Connect` so they never collide with the UI component tools. Instead of relying on documentation snapshots, an assistant can call these tools to fetch the exact endpoint schema, SDK method signature, or cross-language equivalent it needs at generation time. It is a **hosted HTTP server** at — no local install required. | Tool | What it does | |:---|:---| | `resolveConnectEnvironment` | Detects which Connect SDK (language + version) a project uses from its dependency manifest, and echoes the served versions. **Call this first.** | | `listConnectEndpoints` | Lists every Connect REST endpoint with its method, path, summary, and authentication. | | `getConnectEndpointSchema` | Returns one endpoint's full schema — query/body params with types, authentication, responses, status codes, and examples. | | `listConnectSdkMethods` | Lists a language's resources (`auth` / `calendars` / `events`) and the methods on each. | | `getConnectSdkMethod` | Returns one SDK method's full signature, doc, params, return type, and example, in the language you pick. | | `searchConnect` | Searches REST endpoints and SDK methods across all languages by keyword, ranked by relevance. | | `mapConnectEndpointToSdk` | Maps a REST endpoint to its equivalent SDK call in each language — built on the SDKs' shared surface. | | `getConnectErrorTaxonomy` | Returns the shared error categories and the idiomatic exception type for each language. | ## Which tool uses which files? | AI Tool | Documentation Source | Behavior Rules | Routing | Live lookups (MCP) | |:---|:---|:---|:---|:---| | **Cursor** | `llms-connect-full.txt` via @docs | `mobiscroll-connect.mdc` | — | `mobiscroll` server (Connect tools) | | **GitHub Copilot** | `.mdc` file (contains doc URLs) | `.mdc` file | — | `mobiscroll` server (Connect tools) | | **Claude Code** | `llms-connect-full.txt` | `CLAUDE.md` | `CLAUDE.md` | `mobiscroll` server (Connect tools) | ## Cursor setup ### Step 1: Register documentation sources Open **Cursor Settings → Indexing & Docs** and add the documentation source for Connect: | Framework | Name | URL | |:---|:---|:---| | Connect | Mobiscroll Connect | | Only register the source matching your use case. Do not register multiple sources — this prevents cross-domain contamination. ### Step 2: Add the rules file Download the mobiscroll-connect.mdc file and place it in your project's `.cursor/rules/` directory: ``` your-project/ ├── .cursor/ │ └── rules/ │ └── mobiscroll-connect.mdc ├── src/ ├── package.json └── ... ``` ### Step 3: Configure the MCP server (Optional) For live endpoint and SDK lookups, configure the Mobiscroll Connect MCP server so Cursor can call it during generation. Create or edit `.cursor/mcp.json` in your project root: :::warning No `type` field Cursor infers the transport type from the URL. Do **not** add `"type": "http"` to Cursor's config — it causes an error. ::: ``` your-project/ ├── .cursor/ │ ├── mcp.json │ └── rules/ │ └── mobiscroll-connect.mdc ├── src/ └── package.json ``` | Scope | Config file | Shared with team | |:---|:---|:---| | project | `.cursor/mcp.json` in project root | Yes, if committed | | global | `~/.cursor/mcp.json` | No, all your projects | **Verify the connection:** Open the **Output** panel in Cursor and select **MCP Logs** from the dropdown. A successful connection logs tool discovery messages for the `mobiscroll` server, including the `Connect`-prefixed tools. ### Step 4: Use @docs in queries When asking Cursor about Mobiscroll Connect, include `@docs` to ensure it reads the registered documentation: ``` @docs How do I authenticate a user with the Mobiscroll Connect OAuth flow? ``` ``` @docs How do I list all calendars for a connected Google account? ``` ## GitHub Copilot setup ### Step 1: Add the rules file Download the mobiscroll-connect.mdc file and place it at the root of your project or alternatively [copy it's content](#rules-files-mdc) to the rules files under the `.github/` directory: ``` your-project/ ├── mobiscroll-connect.mdc ├── .github/ | ├── copilot-instructions.md <-- Global repo rules | └── instructions/ | └── connect-logic.instructions.md <-- Specific rules ├── src/ ├── package.json └── ... ``` The `.mdc` file will automatically influence Copilot responses when you work on files in that project. It tells Copilot which APIs are available and how to use them correctly. ### Step 2: Configure the MCP server (Optional) For live endpoint and SDK lookups, configure the Mobiscroll Connect MCP server so VS Code can call it during generation. Create or edit `.vscode/mcp.json` in your project root: :::warning `"servers"` not `"mcpServers"` VS Code uses `"servers"` as the root key — not `"mcpServers"` like Claude Code and Cursor. Using the wrong key silently breaks the config with no error message. ::: ``` your-project/ ├── .vscode/ │ └── mcp.json ├── .github/ │ └── instructions/ │ └── connect-logic.instructions.md ├── src/ └── package.json ``` | Scope | Config file | Shared with team | |:---|:---|:---| | workspace | `.vscode/mcp.json` in project root | Yes, if committed | | user profile | Opened via **MCP: Open User Configuration** | No, all your workspaces | **Verify the connection:** Open the **Command Palette** and run **MCP: List Servers**. The `mobiscroll` server should appear with a connected status, exposing the `Connect`-prefixed tools. A trust dialog appears on first use — approve it to allow the server to start. ### How it works The `.mdc` file contains: - **Documentation URLs** — points Copilot to the correct Connect docs - **Component mapping** — maps user intents to the correct Mobiscroll Connect APIs - **Rules** — enforces correct API usage, authentication flows, and webhook handling - **Constraints** — prevents conflation of Connect REST endpoints with UI component APIs With the MCP server configured, Copilot can additionally call the Connect tools for live endpoint and SDK schema lookups instead of relying on the documentation snapshot alone. ## Claude Code setup Set Connect up with config files: a `CLAUDE.md` routing file (Step 1) and the Mobiscroll MCP server (Step 2). Don't install the `mobiscroll@mobiscroll` plugin for Connect — its skills are currently UI-specific. ### Step 1: Add CLAUDE.md If you don't already have a `CLAUDE.md` in your project root, download CLAUDE.md and place it there. If you already have one, copy the contents into your existing file instead — see [File contents](#file-contents) below. ``` your-project/ ├── CLAUDE.md ├── src/ ├── package.json └── ... ``` When Claude Code opens your project, it automatically reads `CLAUDE.md` from the project root. The file provides: - **Domain detection** — Claude detects Connect usage from `package.json`, import patterns, and API call signatures - **Routing rules** — deterministic IF/THEN rules that select `llms-connect-full.txt` and never route to UI framework docs - **API mapping** — translates user intents to the correct Connect REST endpoints and OAuth flows - **Anti-patterns** — explicit WRONG → RIGHT examples that prevent mixing Connect API calls with UI component code ### Step 2: Configure the MCP server (Optional) For live endpoint and SDK lookups, add the Mobiscroll MCP server: :::warning VS Code extension If you are using the Claude Visual Studio Code extension, the server will not appear unless it is added with project scope. See the next command below. ::: To share the server with your team automatically, use project scope: This creates or updates `.mcp.json` in your project root. You can also create that file manually: | Scope | CLI flag | Config location | Shared with team | |:---|:---|:---|:---| | local (default) | `--scope local` | `~/.claude.json` | No | | project | `--scope project` | `.mcp.json` in project root | Yes, via version control | | user | `--scope user` | `~/.claude.json` | No, all your projects | :::info Use `--scope project` for team repos so everyone gets the MCP server automatically when they clone the project. ::: **Verify the connection:** Run `/mcp` inside Claude Code. The panel lists each connected server and its tool count. A healthy connection shows `mobiscroll` with its tools, including the `Connect`-prefixed ones. ### How it works With `CLAUDE.md` in place and the MCP server configured, when you ask Claude Code to write Connect code it: 1. Detects your SDK language and version via `resolveConnectEnvironment` 2. Looks up the endpoint schema (`getConnectEndpointSchema`) or SDK method (`getConnectSdkMethod`) before writing any call 3. Uses `mapConnectEndpointToSdk` when translating a REST endpoint into SDK code, and `getConnectErrorTaxonomy` when writing error handling So Claude always uses the current Connect API and SDK signatures, never hallucinated or outdated ones. ## Domain isolation :::warning Critical Mobiscroll Connect is a backend integration layer — OAuth 2.0, REST API, webhooks, backend calendar sync. It has **no UI components**. Never mix Connect docs with UI framework docs. ::: **Why this matters:** - Connect uses `mobiscroll-connect.mdc` and `llms-connect-full.txt` — never the UI framework files - Mixing Connect docs with UI docs causes the AI to conflate REST endpoints with component APIs - Connect has no JSX, no frontend framework bindings, no CSS **Rules:** 1. Add only **one** `.mdc` file per project — the one matching your framework or domain 2. Register only **one** documentation source in Cursor 3. The MCP server is unified — the single `mobiscroll` server serves both UI and Connect tools. Isolation happens at the tool level: the Connect tools are all prefixed `Connect` (`listConnectEndpoints`, `getConnectSdkMethod`, …), so they never collide with the UI component tools. The `.mdc` and `CLAUDE.md` rules keep the AI on the Connect tools for Connect work. 4. If your project uses both a UI framework and Mobiscroll Connect, use separate AI context directories for each 5. If an AI assistant generates UI component code when you asked about Connect, check that `mobiscroll-connect.mdc` is active ## Example queries These examples show the kind of questions the AI integration is designed to handle correctly. ``` How do I authenticate a user with the Mobiscroll Connect OAuth flow? How do I list all calendars for a connected Google account? How do I create an event in an Outlook calendar via the Connect API? What's the Python SDK call equivalent to POST /event? How do I subscribe to webhook notifications for calendar changes? What scopes are required for read-write calendar access? ``` ## Troubleshooting ### AI generates UI component code instead of Connect API calls **Symptom:** You asked about backend calendar sync or OAuth but the AI generates JSX components like `` instead of Connect REST API calls. **Fix:** Verify that `mobiscroll-connect.mdc` is in place and that the registered @docs source in Cursor points to `llms-connect-full.txt` — not a UI framework file. The `mobiscroll` MCP server exposes both UI and Connect tools, so make sure the AI is using the `Connect`-prefixed tools (e.g. `getConnectEndpointSchema`) rather than the UI component tools. Connect and Eventcalendar are entirely separate products. ### AI invents non-existent endpoints or parameters **Symptom:** The AI suggests REST endpoints, request parameters, response fields, or SDK methods that don't exist in the Connect API. **Fix:** The `.mdc` rules instruct the AI to only use APIs found in the Connect docs. If this still happens, explicitly reference `@docs` in Cursor queries, or verify that `CLAUDE.md` is in the project root for Claude Code. For the highest accuracy, enable the Mobiscroll MCP server so the AI fetches live endpoint and SDK schemas via the Connect tools instead of guessing. You can also ask the AI to confirm an endpoint exists in the Mobiscroll Connect docs. ### MCP server does not appear after setup **Symptom:** The MCP server panel shows no `mobiscroll` entry, or its Connect tools are not available. **Fix:** Check that the config file is in the correct location and uses the correct root key — `mcpServers` for Claude Code and Cursor, `servers` for VS Code. Validate that the file is well-formed JSON, and that the server name is `mobiscroll`. For Claude Code, run `/mcp` to inspect connected servers; for the VS Code extension, add the server with project scope. If the server connects but the `Connect`-prefixed tools are missing, your deployed server may predate the Connect merge — reconnect after it is updated. ### AI mixes Mobiscroll Connect with UI components **Symptom:** The AI generates REST API calls when you asked about a frontend calendar component, or generates JSX/component code when you asked about the Connect API. **Fix:** Mobiscroll Connect is a backend integration layer (OAuth, REST, webhooks) and has no UI components. Eventcalendar is a frontend UI component with no REST API. They use entirely separate `.mdc` files, documentation sources, and MCP servers. Verify that the correct `.mdc` file is active for your project. If you use both in the same codebase, keep separate AI context directories for each. ## File reference All AI integration files and endpoints are available at the following URLs: ### Documentation files | File | URL | |:---|:---| | Connect | | | Connect (full) | | ### Rules files | File | URL | |:---|:---| | Connect rules | | ### Routing file | File | URL | |:---|:---| | Claude Code context | | ### MCP server | Server | URL | |:---|:---| | Mobiscroll MCP (UI + Connect) | | ## File contents The complete contents of each file are shown below. You can copy directly from these blocks or use the download links above. ### CLAUDE.md
View CLAUDE.md
### Rules files (.mdc)
View mobiscroll-connect.mdc
--- ## Application Setup Guide ## Prerequisites & Account Setup Before creating your application, ensure you are logged into your Mobiscroll account. Head over to the [pricing page](https://mobiscroll.com/connect-pricing) and choose the plan that suits you the best. After your subscription is started, the [Mobiscroll Connect dashboard](https://app.mobiscroll.com/connect) will become available. :::info This guide assumes your initial Mobiscroll account setup is already complete. If you have not yet set up your Mobiscroll account, please finalize your profile registration before proceeding. ::: ## Creating the First Application To use Mobiscroll Connect services, you must register your application to generate the necessary API credentials. 1. Navigate to the **[Connect dashboard](https://app.mobiscroll.com/connect)** tab on your account page. 2. Click the **Create new app**. 3. You will be prompted to fill in the project details: - **Application Name**: An internal, descriptive name for your application (e.g., "My Scheduling App"). This is not shown to your users. - **Display Name (Optional)**: The public name shown to your users on the Connect authorization and connection-management screens. When set, the screens read "_<Display Name>_ is using Mobiscroll Connect ..." and the return button reads "Back to _<Display Name>_", so users always know which app they are connecting to and where they will return. When left empty, a generic wording ("The application ...") is used instead. - **Redirect URI (Callback URL)**: The URL where users will be redirected after successfully authorizing with a provider. This must match the route in your application that handles the OAuth callback. - **Webhook URL (Optional)**: If you plan to use webhooks for event notifications, provide the endpoint URL where Mobiscroll Connect can send these updates. See [Webhooks API](/connect/webhooks) for subscription and payload details. 4. Click **Create application** to finalize the application setup. :::info Both the **Application Name** and **Display Name** can be changed later from the **Settings** menu of your application. ::: ## Retrieving Client Credentials Once your application is created, you need to retrieve the credentials required to authenticate your client with the Mobiscroll Connect service. 1. Navigate to the **Settings** left menu item of your newly created application. 2. Locate the **API Credentials** section. 3. **Client ID**: Copy the string displayed in the Client ID field. This is your public identifier. :::info What is a Client ID? The **Client ID** is a unique, public identifier for your application. It is used by Mobiscroll Connect to recognize which app is making a request. You can safely share your Client ID in client-side code or with users. ::: 4. **Client secret**: Click *Show* to reveal or copy the Client secret. :::info What is a Client Secret? The **Client secret** is a confidential string used to prove your application's identity to Mobiscroll Connect. It must be kept private and only used in server-side code. Never expose it in public repositories or client-side code. ::: :::info The **Client secret** is vital for the authentication flow. Ensure you copy it now, as you will need it to configure your environment in the next step. ::: ## Configuring the Client Application With your **Client ID** and **Client Secret** secured, you are ready to configure your application to communicate with Mobiscroll Connect. Create or update a `.env` file in the root of your project (Node.js, Python, etc.) to store these credentials securely. ```bash title=".env" # Your Mobiscroll Connect Credentials MOBISCROLL_CONNECT_CLIENT_ID=proj-123456... MOBISCROLL_CONNECT_CLIENT_SECRET=secret-xyz789... ``` :::warning Security Note **Never** expose your `MOBISCROLL_CONNECT_CLIENT_SECRET` in client-side code (browsers, mobile apps, or public repositories). This secret should only be used in server-side environments to exchange authorization codes for access tokens. ::: ## Verifying the Integration To validate that your credentials are correct and the connection is established, you can perform a "Hello World" connection test by manually constructing an authorization URL that matches the integration code sample. **Step 1:** Construct the URL: ``` bash https://connect.mobiscroll.com/api/oauth/authorize?response_type=code&client_id=${MOBISCROLL_CONNECT_CLIENT_ID}&user_id=test-user-1&scope=read-write ``` **Parameter notes:** - `user_id` is the unique identifier of a user in your system. - `scope` defines the level of access requested (e.g., `read-write` for full calendar access). For more details, see [scopes](/connect/scopes). **Step 2:** Paste this URL into your browser. **Validation Results:** - **Success:** If your browser redirects you to the **Provider Selection** page (or a login screen for Google/Outlook), your `client_id` is recognized, and the service is reachable. - **Failure:** If you receive a JSON response stating `401 Unauthorized` or `Invalid Client`, double-check the credentials in your `.env` file and ensure the `client_id` matches exactly what is in your dashboard. --- ## Branding The Connect screens your users see when they link their calendars (authorization, provider login, and error pages) can be customized per application from the **Branding** menu of your application in the dashboard. You can set a logo and dark-mode logo, a primary color, a default theme, and optionally hide the "Powered by Mobiscroll Connect" footer. Branding is applied automatically based on the `client_id` in the authorization request - no request parameters or integration changes are required. :::info Branding is available on the **Scale** plan or higher. On lower plans the Connect screens use the default Mobiscroll branding. ::: See [Branding](/connect/branding) for the full list of fields, how the theme default is resolved, and how the display name and support contact appear on these screens. --- ## Next Steps Now that your credentials are verified and the connection is established, you can proceed with integrating Mobiscroll Connect into your application. Choose the approach that best fits your workflow: ### Explore the API (no code required) Before writing any integration code, use the Postman collection to run the full OAuth flow, inspect real API responses, and get familiar with the available endpoints. This is the fastest way to understand how Mobiscroll Connect works and to validate your credentials end to end. **[Get started with the Postman Collection](/connect/postman-collection)** ### REST API Integration Build your integration directly using HTTP requests to the Mobiscroll Connect API endpoints. This approach gives you full control and flexibility. **[Get started with REST API Integration](/connect/rest-api-integration)** ### Node.js SDK Use the official Node.js SDK for a streamlined integration experience. The SDK provides typed methods, automatic token management, and simplified error handling. **[Get started with the Node.js SDK](/connect/node-sdk)** ### PHP SDK Use the official PHP SDK for backend integrations in PHP applications. The SDK provides resource-based API access, typed models, and consistent exception handling. **[Get started with the PHP SDK](/connect/php-sdk)** ### .NET SDK Use the official .NET SDK for backend integrations in .NET 6+ applications. The SDK runs on .NET Core and provides async methods, typed models, and consistent exception handling. **[Get started with the .NET SDK](/connect/dotnet-sdk)** ### Python SDK Use the official Python SDK for backend integrations in Python 3.9+ applications. The SDK provides both sync and async clients, typed models, automatic token management, and consistent exception handling. **[Get started with the Python SDK](/connect/python-sdk)** ### Demo Application Start with our complete working example that demonstrates the full OAuth flow, calendar synchronization, and event management using the Node.js SDK out of the box. **[View the Demo App on GitHub](https://github.com/acidb/mobiscroll-connect-demo)** --- ## Best Practices This page collects recommended practices for building, testing, and operating your Mobiscroll Connect integration. ## Testing Across Multiple Environments Each Connect application currently supports a single **Redirect URI** and a single **Webhook URL**. If you need to run your integration in multiple environments (for example, local development, staging, and production), create a separate Connect application for each environment and configure its Redirect URI and Webhook URL accordingly. Use a distinct **Client ID** and **Client Secret** per environment in your `.env` files so each deployment authenticates against its own app: - **Local development**: Redirect URI pointing to `http://localhost:3000/...`, Webhook URL pointing to your tunneling service (e.g. ngrok). - **Staging**: Redirect URI and Webhook URL pointing to your staging domain. - **Production**: Redirect URI and Webhook URL pointing to your production domain. --- ## Getting Started ## What is Mobiscroll Connect? Mobiscroll Connect is an API service that acts as a full-fledged OAuth 2.0 provider, enabling seamless integration with multiple calendar providers through a unified interface. It connects to Google Calendar, Microsoft 365/Outlook.com, Apple Calendar, and CalDAV, exposing a standardized API for client applications to access calendar data securely. The service handles the complexity of working with different calendar providers, offering a single authentication flow and consistent API endpoints. Client applications can retrieve calendars and events from all connected providers without managing multiple OAuth implementations or dealing with provider-specific APIs. ## Key Capabilities Mobiscroll Connect provides secure OAuth 2.0 authentication with JWT-based access tokens, a streamlined authorization flow with provider selection UI, and a unified way to work with calendar data across providers. Beyond authentication, Connect acts as the infrastructure layer for calendar integrations, handling connectivity, normalization, and real-time updates. - **Calendar connectivity & account linking** Connect multiple calendars per user across Google Calendar, Microsoft Outlook (Office 365), Apple Calendar, and any CalDAV-compatible service. - **Unified API & data normalization** Work with a single, consistent data model while Connect handles provider differences (events, availability, time zones, recurrence). - **Real-time sync & webhooks** Stay in sync with external calendars and react to changes (create, update, delete) through webhook notifications. - **Permissions & secure access** Scoped access with OAuth ensures your app only interacts with data users explicitly authorize. - **Conferencing integrations** Add meeting links from providers like Google Meet, Zoom, and Microsoft Teams directly to scheduled events. ## Quick Overview When users connect their calendar accounts through Mobiscroll Connect, they go through a simple flow: select their calendar provider, authenticate with that provider, grant consent, and receive an access token. This token can then be used to fetch calendars and events through a single, consistent API, regardless of which providers the user has connected. The service includes a modern, mobile-responsive interface for provider selection and consent, comprehensive error handling, and a complete test application demonstrating the entire OAuth 2.0 authorization code flow. ## Set up your developer account Get started with Mobiscroll Connect using a free developer account. Create your application, obtain your API credentials, and start integrating calendar connectivity in minutes.
--- ## Get started with the Postman Collection Use the Mobiscroll Connect Postman assets to explore the API, validate OAuth flows, and test endpoints against the production server — without writing any code. It is most useful after completing the [Application Setup Guide](/connect/application-setup#verifying-the-integration), before starting SDK or REST integration work. ## Quick start from App Settings (recommended) If you are starting from the docs, the fastest setup is from your Connect app settings page. 1. Open **Connect → Applications → Your app → Settings**. 2. In the **API credentials** section, click **Download environment**. 3. Click **Run in Postman** to fork the collection. 4. In Postman, import the downloaded `Mobiscroll-Connect-Environment.postman_environment.json` file. 5. Select this environment for the forked collection, then continue with OAuth and endpoint tests below. This path pre-fills `baseUrl`, `clientId`, and `clientSecret` for the selected app, so setup is faster and less error-prone. :::info For Postman OAuth testing, your Connect app must allow `https://oauth.pstmn.io/v1/callback` as a redirect URI. If this URI is missing from app settings, token exchange/auth flows in Postman can fail with `invalid_grant` or redirect URI mismatch errors. ::: ## Run in Postman You can also fork the collection directly from this page: ## Download assets If you are not using the app settings page, use these direct files: - [Mobiscroll Connect API Collection](/connect/postman/mobiscroll-connect-api-collection.json) - [Mobiscroll Connect API Environment](/connect/postman/mobiscroll-connect-api-environment.json) ## Prerequisites Before using the collection, make sure: - `baseUrl` is already set to `https://connect.mobiscroll.com` in the preset environment file — no server setup needed - You have valid OAuth credentials (`clientId`, `clientSecret`) — see [Retrieving Client Credentials](/connect/application-setup#retrieving-client-credentials) - You have a valid `userId` for your environment - see [`user_id` request parameter](/connect/oauth#authorize-user_id) - At least one provider account is connectable (Google, Microsoft, Apple, or CalDAV) Quick health check: - Request: `GET /api/health` - Expected response: `{ "status": "ok" }` ## Step 1: Import or fork the collection If you used **Run in Postman** from app settings, you can skip this step. 1. Open Postman. 2. Click **Import**. 3. Import `Mobiscroll Connect API Collection`. ## Step 2: Create and configure a Postman environment If you downloaded environment from app settings, import that file and use it. Preferred: import the preset environment file. 1. In Postman, click **Import**. 2. Import `Mobiscroll Connect API Environment`. 3. Fill in `clientId`, `clientSecret`, and `userId` (see [Where to get each value](#where-to-get-each-value)). 4. Select this environment before sending requests. Alternative: create an environment manually and define the variables below. ## Environment variables The collection expects these variables: - `baseUrl` - `clientId` - `clientSecret` - `userId` - `scope` (`read-write`, `read`, or `free-busy`) - `redirectUri` (must match your OAuth client config) - `accessToken` - `refreshToken` ### Where to get each value - `baseUrl`: The base API server URL used by every request in the collection. See more: [Base URL](/connect/overview#base-url). - `clientId`: The public identifier for your Mobiscroll Connect application, copied from your app settings. See more: [Retrieving Client Credentials](/connect/application-setup#retrieving-client-credentials). - `clientSecret`: The private application secret used when exchanging codes and refreshing tokens. See more: [Retrieving Client Credentials](/connect/application-setup#retrieving-client-credentials). - `userId`: Your own unique identifier for the user in your system who is being authorized. See more: [`user_id` request parameter](/connect/oauth#authorize-user_id). - `scope`: The permission level requested for the connection, such as `free-busy`, `read`, or `read-write`. See more: [`scope` request parameter](/connect/oauth#authorize-scope) and [Available Scopes](/connect/scopes#available-scopes). - `redirectUri`: The callback URL registered for your app and used to receive the authorization code after consent. See more: [Creating the First Application](/connect/application-setup#creating-the-first-application) and [`redirect_uri` request parameter](/connect/oauth#authorize-redirect_uri). - `accessToken`: The short-lived Bearer token used to authenticate protected API requests. See more: [`access_token` response field](/connect/oauth#token-response-access_token) and [Authentication](/connect/overview#authentication). - `refreshToken`: The rotating token used to obtain a new access token without asking the user to sign in again. See more: [`refresh_token` response field](/connect/oauth#token-response-refresh_token) and [Refresh Access Token](/connect/oauth#endpoint-refresh-token). If your team already provides preset Postman environments, use those first and only update sensitive values locally. ### Redirect URI The preset production environment sets `redirectUri` to `https://oauth.pstmn.io/v1/callback`. Ensure the OAuth client in your [Creating the First Application](/connect/application-setup#creating-the-first-application) setup has this URL registered as an allowed redirect URI. ## Step 3: Understand collection auth behavior The collection sends OAuth 2.0 Bearer auth globally using `{{accessToken}}`. - **OAuth** folder: obtain, refresh, and revoke tokens - **Calendars**, **Events**, **Webhooks** folders: require a valid token If you get `401 Unauthorized`, refresh or replace `{{accessToken}}`. ### Step 3.1: Get a new token from the Postman Authorization tab You can fetch a token directly from Postman without manually running token requests. :::info If you successfully get and apply a token from the Postman **Authorization** tab, you do **not** need to run the manual OAuth API flow in the next section. The manual flow is included for clarity, debugging, and cases where you want to run each OAuth step explicitly. ::: 1. Open the collection and go to the **Authorization** tab. 2. Confirm **Type** is **OAuth 2.0**. 3. Click **Get New Access Token**. 4. In the token dialog, verify: - Auth URL: `{{baseUrl}}/api/oauth/authorize?user_id={{userId}}&scope={{scope}}` - Access Token URL: `{{baseUrl}}/api/oauth/token` - Client ID: `{{clientId}}` - Client Secret: `{{clientSecret}}` - Callback URL: `https://oauth.pstmn.io/v1/callback` - Scope: `{{scope}}` - Client Authentication: `Send client credentials in body` 5. Complete login/consent flow. 6. Click **Use Token**. Optionally copy the token to `{{accessToken}}` to keep environment variables in sync. ## Step 4: Run the OAuth authorization-code flow manually (optional) ### Step 4.1: Open authorization URL in browser Use **OAuth → Authorize (Browser)**: - Method: `GET` - Endpoint: `{{baseUrl}}/api/oauth/authorize` - Query params: `response_type=code`, `client_id`, `user_id`, `scope`, `redirect_uri` After redirect to `{{redirectUri}}?code=...`, copy the `code` value. See more: [Authorize](/connect/oauth#endpoint-authorize). ### Step 4.2: Exchange code for tokens Use **OAuth → Exchange Code for Token**: See more: [Get Access Token](/connect/oauth#endpoint-token). - Method: `POST` - Endpoint: `{{baseUrl}}/api/oauth/token` - Auth: Basic (`{{clientId}}` / `{{clientSecret}}`) - Body (`x-www-form-urlencoded`): - `grant_type=authorization_code` - `code=` - `redirect_uri={{redirectUri}}` Save values from response: - `accessToken = access_token` - `refreshToken = refresh_token` ### Step 4.3: Refresh access token Use **OAuth → Refresh Token**: See more: [Refresh Access Token](/connect/oauth#endpoint-refresh-token). - Method: `POST` - Endpoint: `{{baseUrl}}/api/oauth/token` - Auth: Basic (`{{clientId}}` / `{{clientSecret}}`) - Body (`x-www-form-urlencoded`): - `grant_type=refresh_token` - `refresh_token={{refreshToken}}` Replace `{{accessToken}}` with the new token. ### Step 4.4: Revoke token when done (recommended) Use **OAuth → Revoke Token**: See more: [Revoke Token](/connect/oauth#endpoint-revoke). - Method: `POST` - Endpoint: `{{baseUrl}}/api/oauth/revoke` - Body (`application/json`): - `token={{accessToken}}` Use this as a cleanup/security step, especially in shared or production-like environments. ## Step 5: Test core API endpoints Run these requests in order. ### Step 5.1: Verify service health - Folder: **Health** - Request: **Health Check** - Expected: `200` with `{ "status": "ok" }` ### Step 5.2: List connected calendars - Folder: **Calendars** - Request: [**Get Calendars**](/connect/calendars#endpoint-get-calendars) (`GET /api/calendars`) - Requires: valid Bearer token (`{{accessToken}}`) - Scope: requires `read-write` or `read` scope; tokens issued with [`free-busy`](/connect/scopes#free-busy-high-privacy) scope return `403` If result is empty, connect at least one provider account first. ### Step 5.3: Query events - Folder: **Events** - Request: [**Get Events**](/connect/events#endpoint-get-events) (`GET /api/events`) - Required query params: `start`, `end` - Optional query params: `pageSize`, `singleEvents`, `calendarIds`, `nextPageToken` Use `nextPageToken` from response for pagination. ### Step 5.4: Create, update, and delete an event All write operations require `read-write` scope. Tokens issued with `free-busy`, `read`, or other non-write scopes return `403`. 1. [**Create Event**](/connect/events#endpoint-create-event) (`POST /api/event`) with `provider`, `calendarId`, `title`, `start`, `end` — returns `201` 2. [**Update Event**](/connect/events#endpoint-update-event) (`PUT /api/event`) with `provider`, `calendarId`, `eventId` — returns `200` 3. [**Delete Event**](/connect/events#endpoint-delete-event) (`DELETE /api/event`) with `provider`, `calendarId`, `eventId` — returns `204` (no body) ### Step 5.5: Subscribe to webhook notifications - Folder: **Webhooks** - Request: [**Subscribe Webhook**](/connect/webhooks#endpoint-subscribe-webhook) (`POST /api/subscribe-webhook`) - Body fields: `provider`, `calendarId`, `expiration` (future timestamp in ms) ## Recommended test sequence 1. `Health Check` 2. `Authorize (Browser)` 3. `Exchange Code for Token` 4. `Get Calendars` 5. `Get Events` 6. `Create Event` 7. `Update Event` 8. `Delete Event` 9. `Refresh Token` 10. `Subscribe Webhook` 11. `Revoke Token` ## Troubleshooting ### 401 Unauthorized - Confirm `{{accessToken}}` is set and current - Run **Refresh Token** - Verify OAuth client credentials for the current environment ### `invalid_client` or `invalid_grant` - Verify `clientId` and `clientSecret` - Ensure `redirectUri` exactly matches your OAuth client config. See more: [Creating the First Application](/connect/application-setup#creating-the-first-application) and [`redirect_uri` validation](/connect/oauth#token-redirect_uri) - Ensure the auth code is unused and not expired ### Empty calendars/events - Complete provider connection flow first - Verify the token belongs to the correct user/project - Verify your query date range includes data ### 400 on event create/update - Validate `provider` and `calendarId` - Ensure `start` and `end` are valid ISO timestamps - Ensure required fields are present ## Security notes - Access tokens are short-lived; use refresh flow for renewals - Keep `clientSecret` private - Do not share exported environments that contain secrets --- ## Calendars API ## List Calendars Retrieves all calendar lists from all connected providers (Google Calendar, Microsoft Outlook, Apple Calendar, CalDAV) for the authenticated user. Fetches the list of calendars from all connected calendar providers and returns a unified array of calendars across all providers with provider-specific metadata including timezone, color, and access role information. **Endpoint:** `GET /calendars` ### Response Array of calendar objects from all connected providers. Each Calendar object contains: Provider name: `'google'`, `'microsoft'`, `'apple'`, or `'caldav'` Unique calendar identifier from the provider Display name of the calendar Calendar timezone (e.g., "America/New_York") Calendar color code Calendar description Original calendar object from the provider ### Error Responses - **401** - Unauthorized. Invalid or missing bearer token - **500** - Internal Server Error. Provider error or unexpected failure ### Examples **REST** ```bash title="Fetch all calendars for authenticated user" GET /calendars ``` **Node.js SDK** ```typescript const calendars = await client.calendars.list(); ``` **Python SDK** ```python calendars = client.calendars.list() ``` **PHP SDK** ```php $calendars = $client->calendars()->list(); ``` **.NET SDK** ```csharp var calendars = await client.Calendars.ListAsync(); ``` **Java SDK** ```java List calendars = client.calendars().list(); ``` **Go SDK** ```go calendars, err := client.Calendars().List(ctx) ``` **Ruby SDK** ```ruby calendars = client.calendars.list ``` ```json title="Response" [ { "id": "work@company.com", "title": "My Calendar", "provider": "google", "timeZone": "America/Los_Angeles", "color": "#9fc6e7", "description": "Work calendar" }, { "id": "AAMkAGI2T...", "title": "Work Calendar", "provider": "microsoft", "timeZone": "UTC", "description": "" }, { "id": "E2857962-EE43-4E90-829C-A826D534C0D9", "title": "Personal", "provider": "apple", "timeZone": "America/New_York", "description": "Personal events" }, { "id": "https://nextcloud.example.com/remote.php/dav/calendars/user/personal/", "title": "CalDAV Personal", "provider": "caldav", "timeZone": "UTC", "description": "CalDAV calendar" } ] ``` :::info - No query parameters are required for this endpoint - The endpoint returns calendars from all connected providers in a single response - Each calendar includes provider-specific metadata where available - The `projectId` is automatically retrieved from the user's session cookies ::: --- ## Events API ## List Events Retrieves calendar events from all connected providers (Google Calendar, Microsoft Outlook, Apple Calendar, CalDAV) for the authenticated user. Fetches events from multiple calendar providers simultaneously with support for pagination, filtering by date range and specific calendars, and handling of recurring events. Returns chronologically sorted events across all providers with "load more" functionality using nextPageToken tokens. **Endpoint:** GET /events ### Request Parameters 250} id="param-pageSize"> Number of events to fetch per request. Maximum value is capped at 1000. ISO date string to filter events starting from this date. Date strings are automatically normalized to handle malformed ISO 8601 formats. ISO date string to filter events ending before this date. Date strings are automatically normalized to handle malformed ISO 8601 formats. undefined} id="param-calendarIds"> JSON stringified object of calendar IDs per provider. If not provided, events from all calendars will be fetched. Calendar IDs that are not associated with the authenticated user's connected accounts will be silently ignored. ```js title="Example" '{"google":["john.andrew@mobiscroll.com"],"microsoft":[],"apple":[],"caldav":[]}' ``` undefined} id="param-nextPageToken"> Base64 encoded JSON pagination state object containing token information for each provider (Google, Microsoft, Apple, CalDAV). This parameter should be passed as-is from the previous response when loading more events. true} id="param-singleEvents"> Controls how recurring events are returned: - true - Expands recurring events into individual instances within the specified time range - false - Returns only the master recurring event (series definition) without individual occurrences ### Response Array of calendar events from all providers, sorted chronologically by start time. Each CalendarEvent object contains: Provider name: 'google', 'microsoft', 'apple', or 'caldav' Event ID Calendar ID Event title/summary Event description or notes (optional) Event start date/time Event end date/time True if all-day event ID of the recurring event series (if this is an instance of a recurring event) (optional) Event background color (optional) Event location (optional) Array of event attendees (optional). Each object contains: Attendee email address Response status: 'accepted', 'declined', 'tentative', or 'none' True if this attendee is the event organizer (optional) Custom key-value pairs for additional event data (optional) Conference meeting URL (optional). Provider-specific conference metadata returned by the API (optional). Use this when you need details beyond the conference link. Conference provider identifier. Typical values: google-meet, microsoft-teams, zoom, webex. Additional fields vary by provider and are returned as-is when available. Typical examples: - Google: entryPoints, conferenceSolution, conferenceId - Microsoft: provider meeting details from Graph (for example join URL-related fields) - Apple/CalDAV: usually only provider Event availability: 'busy' or 'free' (optional) Event privacy: 'public', 'private', or 'confidential' (optional) Event status: 'confirmed', 'tentative', or 'cancelled' (optional) ISO 8601 timestamp of when the event was last modified (optional) Public event link (optional) Original event object from the provider Number of events per page. Base64 encoded pagination state for the next request. Pass this value as the [nextPageToken](#param-nextPageToken) parameter when loading more events. Only included in the response if more events are available. ### Error Responses - **400** - Bad Request. Invalid nextPageToken parameters - **401** - Unauthorized. Invalid or missing bearer token - **500** - Internal Server Error. Provider error or unexpected failure ### Examples **REST** ```bash title="Fetch initial events with date range filter" GET /events?pageSize=50&start=2025-10-01T00:00:00Z&end=2025-10-31T23:59:59Z ``` ```json title="Response" { "events": [ { "id": "event123", "title": "Team Meeting", "start": "2025-10-15T10:00:00Z", "end": "2025-10-15T11:00:00Z", "provider": "google", "allDay": false, "location": "Conference Room A", "color": "#9fc6e7", "lastModified": "2026-03-10T13:36:08.000Z" } ], "pageSize": 50, "nextPageToken": "eyJnb29nbGUiOnsidG9rZW4iOnsiY2FsMTIzIjp7Im5leHRQYWdlVG9rZW4iOiJhYmMifX0sImlzRGVwbGV0ZWQiOmZhbHNlfX0=" } ``` ```bash title="Load more events using nextPageToken" GET /events?pageSize=50&nextPageToken=eyJnb29nbGUiOnsidG9rZW4iOnsiY2FsMTIzIjp7Im5leHRQYWdlVG9rZW4iOiJhYmMifX0sImlzRGVwbGV0ZWQiOmZhbHNlfX0= ``` ```bash title="Filter by specific calendars" GET /events?pageSize=25&calendarIds=%7B%22google%22:%5B%22john.andrew%40mobiscroll.com%22%5D,%22microsoft%22:%5B%5D,%22apple%22:%5B%5D,%22caldav%22:%5B%5D%7D ``` ```bash title="Get recurring event series masters (not expanded)" GET /events?pageSize=50&singleEvents=false ``` **Node.js SDK** ```typescript // Fetch initial events with date range filter const response = await client.events.list({ pageSize: 50, start: '2025-10-01T00:00:00Z', end: '2025-10-31T23:59:59Z' }); console.log(response.events); // Load more events using nextPageToken const nextResponse = await client.events.list({ pageSize: 50, nextPageToken: response.nextPageToken }); // Filter by specific calendars const filteredEvents = await client.events.list({ pageSize: 25, calendarIds: ['personal@gmail.com', 'work@company.com'] }); // Get recurring event series masters (not expanded) const masters = await client.events.list({ pageSize: 50, singleEvents: false }); ``` **Python SDK** ```python # Fetch initial events with date range filter response = client.events.list( page_size=50, start='2025-10-01T00:00:00Z', end='2025-10-31T23:59:59Z', ) # Load more events using next_page_token next_response = client.events.list( page_size=50, next_page_token=response.next_page_token, ) # Filter by specific calendars filtered_events = client.events.list( page_size=25, calendar_ids={'google': ['personal@gmail.com', 'work@company.com']}, ) # Get recurring event series masters (not expanded) masters = client.events.list( page_size=50, single_events=False, ) ``` **PHP SDK** ```php // Fetch initial events with date range filter $response = $client->events()->list([ 'pageSize' => 50, 'start' => '2025-10-01T00:00:00Z', 'end' => '2025-10-31T23:59:59Z', ]); $events = $response['events']; // Load more events using nextPageToken $nextResponse = $client->events()->list([ 'pageSize' => 50, 'nextPageToken' => $response['nextPageToken'], ]); // Filter by specific calendars $filteredEvents = $client->events()->list([ 'pageSize' => 25, 'calendarIds' => ['google' => ['personal@gmail.com', 'work@company.com']], ]); // Get recurring event series masters (not expanded) $masters = $client->events()->list([ 'pageSize' => 50, 'singleEvents' => false, ]); ``` **.NET SDK** ```csharp // Fetch initial events with date range filter var response = await client.Events.ListAsync(new EventListParams { PageSize = 50, Start = DateTime.Parse("2025-10-01T00:00:00Z"), End = DateTime.Parse("2025-10-31T23:59:59Z"), }); // Load more events using nextPageToken var nextResponse = await client.Events.ListAsync(new EventListParams { PageSize = 50, NextPageToken = response.NextPageToken, }); // Filter by specific calendars var filteredEvents = await client.Events.ListAsync(new EventListParams { PageSize = 25, CalendarIds = new Dictionary> { { "google", new List { "personal@gmail.com", "work@company.com" } } }, }); // Get recurring event series masters (not expanded) var masters = await client.Events.ListAsync(new EventListParams { PageSize = 50, SingleEvents = false, }); ``` **Java SDK** ```java // Fetch initial events with date range filter EventsListResponse response = client.events().list(EventListParams.builder() .pageSize(50) .start(OffsetDateTime.parse("2025-10-01T00:00:00Z")) .end(OffsetDateTime.parse("2025-10-31T23:59:59Z")) .build()); // Load more events using nextPageToken EventsListResponse nextResponse = client.events().list(EventListParams.builder() .pageSize(50) .nextPageToken(response.getNextPageToken()) .build()); // Filter by specific calendars EventsListResponse filtered = client.events().list(EventListParams.builder() .pageSize(25) .calendarIds(Map.of( Provider.GOOGLE, List.of("personal@gmail.com", "work@company.com") )) .build()); // Get recurring event series masters (not expanded) EventsListResponse masters = client.events().list(EventListParams.builder() .pageSize(50) .singleEvents(false) .build()); ``` **Go SDK** ```go "time" mobiscroll "github.com/acidb/mobiscroll-connect-sdks/sdks/go" ) start, _ := time.Parse(time.RFC3339, "2025-10-01T00:00:00Z") end, _ := time.Parse(time.RFC3339, "2025-10-31T23:59:59Z") // Fetch initial events with date range filter response, err := client.Events().List(ctx, &mobiscroll.EventListParams{ PageSize: mobiscroll.Ptr(50), Start: &start, End: &end, }) // Load more events using NextPageToken nextResponse, err := client.Events().List(ctx, &mobiscroll.EventListParams{ PageSize: mobiscroll.Ptr(50), NextPageToken: response.NextPageToken, }) // Filter by specific calendars filtered, err := client.Events().List(ctx, &mobiscroll.EventListParams{ PageSize: mobiscroll.Ptr(25), CalendarIDs: map[mobiscroll.Provider][]string{ mobiscroll.ProviderGoogle: {"personal@gmail.com", "work@company.com"}, }, }) // Get recurring event series masters (not expanded) masters, err := client.Events().List(ctx, &mobiscroll.EventListParams{ PageSize: mobiscroll.Ptr(50), SingleEvents: mobiscroll.Ptr(false), }) ``` **Ruby SDK** ```ruby # Fetch initial events with date range filter response = client.events.list( page_size: 50, start: '2025-10-01T00:00:00Z', end: '2025-10-31T23:59:59Z' ) # Load more events using next_page_token next_response = client.events.list( page_size: 50, next_page_token: response.next_page_token ) # Filter by specific calendars filtered = client.events.list( page_size: 25, calendar_ids: { Mobiscroll::Connect::Provider::GOOGLE => ['personal@gmail.com', 'work@company.com'] } ) # Get recurring event series masters (not expanded) masters = client.events.list( page_size: 50, single_events: false ) ``` :::info - Events are sorted chronologically by start time across all providers - The pageSize is distributed across all active providers (not per provider) - Maximum pageSize is capped at 1000 events - The nextPageToken token is Base64 encoded and should be passed as-is to subsequent requests - Each provider (Google, Microsoft, Apple, CalDAV) tracks its own pagination state independently - The nextPageToken parameter is only included in the response when more events are available - Date strings are automatically normalized to handle malformed ISO 8601 formats before parsing - The color property contains the background color value directly (not an object) - Conference links are returned in the conference string field - All event endpoints return events in the **unified CalendarEvent format** across all providers ::: :::info Conference raw data examples The conferenceData field stores provider-specific raw meeting metadata. ```json title="Google" { "conferenceData": { "conferenceId": "abc-defg-hij", "entryPoints": [ { "entryPointType": "video", "uri": "https://meet.google.com/abc-defg-hij" } ] } } ``` ```json title="Microsoft" { "onlineMeeting": { "joinUrl": "https://teams.microsoft.com/l/meetup-join/...", "conferenceId": "123456789" } } ``` ::: --- ## Create Event Creates a new calendar event in the specified calendar for the authenticated user. Supports creating single events or recurring events with recurrence rules. The event is created in the provider's calendar system (Google Calendar, Microsoft Outlook, Apple Calendar, or CalDAV) based on the calendar ID. **Endpoint:** POST /event ### Request Body Calendar provider where the event will be created. One of: 'google', 'microsoft', 'apple', or 'caldav'. The calendar identifier where the event will be created. The event title/summary. ISO date string for the event start time. ISO date string for the event end time. undefined} id="create-description"> Event description or notes. undefined} id="create-location"> Event location (address, meeting room, etc.). false} id="create-allDay"> Whether this is an all-day event. undefined} id="create-attendees"> Array of attendee email addresses. undefined} id="create-recurrence"> Recurrence rule for creating a recurring event series. Object with the following properties: Recurrence frequency: 'DAILY', 'WEEKLY', 'MONTHLY', or 'YEARLY' Interval between occurrences (e.g., 2 for every 2 weeks) Number of occurrences (mutually exclusive with until) End date in format YYYYMMDDTHHMMSSZ (mutually exclusive with count) Array of weekday codes: ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'] Array of days of the month (1-31) Array of months (1-12) undefined} id="create-custom"> Custom key-value pairs for additional event data. :::info External IDs If you need to associate provider-generated event IDs with your own domain entities, store your external/business ID in custom (for example custom.externalEventId = "icoll-rdv-123"). ::: undefined} id="create-conference"> Conference meeting URL (optional). Backward compatibility is preserved for legacy conference string usage. undefined} id="create-conferenceData" isObject> Extra conference options and provider-specific metadata (optional). Conference provider identifier. Typical values: google-meet, microsoft-teams, zoom, webex. Set to true to auto-generate a provider meeting link when supported. When autoGenerate is true, conference and other conferenceData fields are ignored. undefined} id="create-availability"> Event availability: 'free' or 'busy'. undefined} id="create-privacy"> Event privacy: 'public', 'private', or 'confidential'. undefined} id="create-status"> Event status: 'confirmed', 'tentative', or 'cancelled'. ### Response Indicates whether the event was created successfully. The event ID. The event title. The event start date/time. The event end date/time. Error or status message (included on failure). :::info The response includes all properties of the created [CalendarEvent](#response-events) object merged at the root level. ::: ### Error Responses - **400** - Bad Request. Invalid event data or missing required fields - **401** - Unauthorized. Invalid or missing bearer token - **404** - Not Found. Calendar not found - **500** - Internal Server Error. Provider error or unexpected failure ### Examples **REST** ```bash title="Create a simple event" POST /event Content-Type: application/json ``` ```json { "provider": "google", "calendarId": "primary", "title": "Team Meeting", "description": "Discuss project updates", "start": "2025-11-01T10:00:00Z", "end": "2025-11-01T11:00:00Z", "location": "Conference Room A", "conference": { "url": "https://meet.google.com/abc-defg-hij", "provider": "google-meet" } } ``` ```json title="Response" { "success": true, "provider": "google", "id": "event123abc", "title": "Team Meeting", "start": "2025-11-01T10:00:00.000Z", "end": "2025-11-01T11:00:00.000Z", "allDay": false, "location": "Conference Room A", "conference": { "url": "https://meet.google.com/abc-defg-hij", "provider": "google-meet" }, "attendees": [], "color": "#9fc6e7", "availability": "busy", "privacy": "public", "status": "confirmed", "lastModified": "2026-03-10T13:36:08.000Z", "link": "https://calendar.google.com/calendar/event?eid=...", "original": { "kind": "calendar#event", "etag": "\"3366428166344000\"", "id": "event123abc", "summary": "Team Meeting" ... } } ``` ```bash title="Create a recurring event" POST /event Content-Type: application/json ``` ```json { "provider": "microsoft", "calendarId": "AAMkAGVmMDEz...", "title": "Weekly Standup", "start": "2025-11-01T09:00:00Z", "end": "2025-11-01T09:30:00Z", "allDay": false, "recurrence": { "frequency": "WEEKLY", "interval": 1, "count": 10, "byDay": ["MO", "WE", "FR"] } } ``` ```bash title="Create an all-day event" POST /event Content-Type: application/json ``` ```json { "provider": "apple", "calendarId": "https://caldav.icloud.com/.../calendars/...", "title": "Conference", "start": "2025-11-15T00:00:00Z", "end": "2025-11-16T00:00:00Z", "allDay": true, "description": "Annual tech conference" } ``` **Node.js SDK** ```typescript // Create a simple event const event = await client.events.create({ provider: 'google', calendarId: 'primary', title: 'Team Meeting', description: 'Discuss project updates', start: '2025-11-01T10:00:00Z', end: '2025-11-01T11:00:00Z', location: 'Conference Room A', conference: { url: 'https://meet.google.com/abc-defg-hij', provider: 'google-meet' } }); // Create a recurring event const recurringEvent = await client.events.create({ provider: 'microsoft', calendarId: 'AAMkAGVmMDEz...', title: 'Weekly Standup', start: '2025-11-01T09:00:00Z', end: '2025-11-01T09:30:00Z', allDay: false, recurrence: { frequency: 'WEEKLY', interval: 1, count: 10, byDay: ['MO', 'WE', 'FR'] } }); // Create an all-day event const allDayEvent = await client.events.create({ provider: 'apple', calendarId: 'https://caldav.icloud.com/.../calendars/...', title: 'Conference', start: '2025-11-15T00:00:00Z', end: '2025-11-16T00:00:00Z', allDay: true, description: 'Annual tech conference' }); ``` **Python SDK** ```python # Create a simple event event = client.events.create({ 'provider': 'google', 'calendarId': 'primary', 'title': 'Team Meeting', 'description': 'Discuss project updates', 'start': '2025-11-01T10:00:00Z', 'end': '2025-11-01T11:00:00Z', 'location': 'Conference Room A', }) # Create a recurring event recurring_event = client.events.create({ 'provider': 'microsoft', 'calendarId': 'AAMkAGVmMDEz...', 'title': 'Weekly Standup', 'start': '2025-11-01T09:00:00Z', 'end': '2025-11-01T09:30:00Z', 'allDay': False, 'recurrence': { 'frequency': 'WEEKLY', 'interval': 1, 'count': 10, 'byDay': ['MO', 'WE', 'FR'], }, }) # Create an all-day event all_day_event = client.events.create({ 'provider': 'apple', 'calendarId': 'https://caldav.icloud.com/.../calendars/...', 'title': 'Conference', 'start': '2025-11-15T00:00:00Z', 'end': '2025-11-16T00:00:00Z', 'allDay': True, 'description': 'Annual tech conference', }) ``` **PHP SDK** ```php // Create a simple event $event = $client->events()->create([ 'provider' => 'google', 'calendarId' => 'primary', 'title' => 'Team Meeting', 'description' => 'Discuss project updates', 'start' => '2025-11-01T10:00:00Z', 'end' => '2025-11-01T11:00:00Z', 'location' => 'Conference Room A', ]); // Create a recurring event $recurringEvent = $client->events()->create([ 'provider' => 'microsoft', 'calendarId' => 'AAMkAGVmMDEz...', 'title' => 'Weekly Standup', 'start' => '2025-11-01T09:00:00Z', 'end' => '2025-11-01T09:30:00Z', 'allDay' => false, ]); // Create an all-day event $allDayEvent = $client->events()->create([ 'provider' => 'apple', 'calendarId' => 'https://caldav.icloud.com/.../calendars/...', 'title' => 'Conference', 'start' => '2025-11-15T00:00:00Z', 'end' => '2025-11-16T00:00:00Z', 'allDay' => true, 'description' => 'Annual tech conference', ]); ``` **.NET SDK** ```csharp // Create a simple event var event = await client.Events.CreateAsync(new CreateEventParams { Provider = "google", CalendarId = "primary", Title = "Team Meeting", Description = "Discuss project updates", Start = DateTime.Parse("2025-11-01T10:00:00Z"), End = DateTime.Parse("2025-11-01T11:00:00Z"), Location = "Conference Room A", }); // Create a recurring event var recurringEvent = await client.Events.CreateAsync(new CreateEventParams { Provider = "microsoft", CalendarId = "AAMkAGVmMDEz...", Title = "Weekly Standup", Start = DateTime.Parse("2025-11-01T09:00:00Z"), End = DateTime.Parse("2025-11-01T09:30:00Z"), AllDay = false, Recurrence = new RecurrenceRule { Frequency = "WEEKLY", Interval = 1, Count = 10, ByDay = new[] { "MO", "WE", "FR" }, }, }); // Create an all-day event var allDayEvent = await client.Events.CreateAsync(new CreateEventParams { Provider = "apple", CalendarId = "https://caldav.icloud.com/.../calendars/...", Title = "Conference", Start = DateTime.Parse("2025-11-15T00:00:00Z"), End = DateTime.Parse("2025-11-16T00:00:00Z"), AllDay = true, Description = "Annual tech conference", }); ``` **Java SDK** ```java // Create a simple event CalendarEvent event = client.events().create(EventCreateData.builder() .provider(Provider.GOOGLE) .calendarId("primary") .title("Team Meeting") .description("Discuss project updates") .start(OffsetDateTime.parse("2025-11-01T10:00:00Z")) .end(OffsetDateTime.parse("2025-11-01T11:00:00Z")) .location("Conference Room A") .build()); // Create a recurring event CalendarEvent recurringEvent = client.events().create(EventCreateData.builder() .provider(Provider.MICROSOFT) .calendarId("AAMkAGVmMDEz...") .title("Weekly Standup") .start(OffsetDateTime.parse("2025-11-01T09:00:00Z")) .end(OffsetDateTime.parse("2025-11-01T09:30:00Z")) .allDay(false) .recurrence(RecurrenceRule.builder() .frequency("WEEKLY") .interval(1) .count(10) .byDay(List.of("MO", "WE", "FR")) .build()) .build()); // Create an all-day event CalendarEvent allDayEvent = client.events().create(EventCreateData.builder() .provider(Provider.APPLE) .calendarId("https://caldav.icloud.com/.../calendars/...") .title("Conference") .start(OffsetDateTime.parse("2025-11-15T00:00:00Z")) .end(OffsetDateTime.parse("2025-11-16T00:00:00Z")) .allDay(true) .description("Annual tech conference") .build()); ``` **Go SDK** ```go "time" mobiscroll "github.com/acidb/mobiscroll-connect-sdks/sdks/go" ) start, _ := time.Parse(time.RFC3339, "2025-11-01T10:00:00Z") end, _ := time.Parse(time.RFC3339, "2025-11-01T11:00:00Z") // Create a simple event event, err := client.Events().Create(ctx, &mobiscroll.EventCreateData{ Provider: mobiscroll.ProviderGoogle, CalendarID: "primary", Title: "Team Meeting", Description: "Discuss project updates", Start: start, End: end, Location: "Conference Room A", }) // Create a recurring event recurringStart, _ := time.Parse(time.RFC3339, "2025-11-01T09:00:00Z") recurringEnd, _ := time.Parse(time.RFC3339, "2025-11-01T09:30:00Z") recurringEvent, err := client.Events().Create(ctx, &mobiscroll.EventCreateData{ Provider: mobiscroll.ProviderMicrosoft, CalendarID: "AAMkAGVmMDEz...", Title: "Weekly Standup", Start: recurringStart, End: recurringEnd, AllDay: mobiscroll.Ptr(false), Recurrence: &mobiscroll.RecurrenceRule{ Frequency: "WEEKLY", Interval: mobiscroll.Ptr(1), Count: mobiscroll.Ptr(10), ByDay: []string{"MO", "WE", "FR"}, }, }) // Create an all-day event allDayStart, _ := time.Parse(time.RFC3339, "2025-11-15T00:00:00Z") allDayEnd, _ := time.Parse(time.RFC3339, "2025-11-16T00:00:00Z") allDayEvent, err := client.Events().Create(ctx, &mobiscroll.EventCreateData{ Provider: mobiscroll.ProviderApple, CalendarID: "https://caldav.icloud.com/.../calendars/...", Title: "Conference", Start: allDayStart, End: allDayEnd, AllDay: mobiscroll.Ptr(true), Description: "Annual tech conference", }) ``` **Ruby SDK** ```ruby # Create a simple event event = client.events.create( provider: Mobiscroll::Connect::Provider::GOOGLE, calendar_id: 'primary', title: 'Team Meeting', description: 'Discuss project updates', start: '2025-11-01T10:00:00Z', end: '2025-11-01T11:00:00Z', location: 'Conference Room A' ) # Create a recurring event recurring_event = client.events.create( provider: Mobiscroll::Connect::Provider::MICROSOFT, calendar_id: 'AAMkAGVmMDEz...', title: 'Weekly Standup', start: '2025-11-01T09:00:00Z', end: '2025-11-01T09:30:00Z', all_day: false, recurrence: Mobiscroll::Connect::RecurrenceRule.new( frequency: 'WEEKLY', interval: 1, count: 10, by_day: %w[MO WE FR] ) ) # Create an all-day event all_day_event = client.events.create( provider: Mobiscroll::Connect::Provider::APPLE, calendar_id: 'https://caldav.icloud.com/.../calendars/...', title: 'Conference', start: '2025-11-15T00:00:00Z', end: '2025-11-16T00:00:00Z', all_day: true, description: 'Annual tech conference' ) ``` :::info - The provider parameter must match the calendar provider of the calendarId - For recurring events, either count or until can be specified, but not both - The byDay property is typically used with WEEKLY frequency - All-day events should have start and end times at midnight (00:00:00) - The response event object format varies by provider (Google, Microsoft, Apple, CalDAV) ::: --- ## Update Event Updates an existing calendar event. Supports updating single events, recurring event series, or individual instances of recurring events. For recurring events, you can specify whether to update only the current instance, all following instances, or the entire series. **Endpoint:** PUT /event ### Request Body Calendar provider where the event exists. One of: 'google', 'microsoft', 'apple', or 'caldav'. The event identifier to update. The calendar identifier containing the event. undefined} id="update-recurringEventId"> The ID of the recurring event series. Required when updating an instance of a recurring event. undefined} id="update-updateMode"> Controls which events in a recurring series are updated: - 'this' - Update only this specific instance - 'following' - Update this and all following instances - 'all' - Update all instances in the series Only applicable for recurring events. If not specified, updates the single event or series master. The event title/summary. ISO date string for the event start time. ISO date string for the event end time. Event description or notes. Event location. Whether this is an all-day event. Array of attendee email addresses. Custom key-value pairs for additional event data. Conference meeting URL update (optional). Extra conference options and provider-specific metadata update (optional). Conference provider identifier. Typical values: google-meet, microsoft-teams, zoom, webex. Set to true to auto-generate a provider meeting link when supported. When autoGenerate is true, conference and other conferenceData fields are ignored. :::info Conference update behavior by provider - google: supports conference and conferenceData.autoGenerate - microsoft: supports conferenceData.autoGenerate (Teams link generation); manual conference is ignored for online meeting generation - apple and caldav: support conference; conferenceData.autoGenerate is ignored - conferenceData.autoGenerate takes precedence over conference when both are provided - conferenceData.provider identifies the conference system (for example google-meet, microsoft-teams) ::: Event availability: 'free' or 'busy'. Event privacy: 'public', 'private', or 'confidential'. Event status: 'confirmed', 'tentative', or 'cancelled'. Recurrence rule for updating recurring event properties. Same structure as [create recurrence](#create-recurrence). ### Response Same structure as [Create Event response](#create-response-success). Indicates whether the event was updated successfully. Error or status message (included on failure). :::info The response includes all properties of the updated [CalendarEvent](#response-events) object merged at the root level. ::: ### Error Responses - **400** - Bad Request. Invalid event data or missing required fields - **401** - Unauthorized. Invalid or missing bearer token - **404** - Not Found. Event or calendar not found - **500** - Internal Server Error. Provider error or unexpected failure ### Examples **REST** ```bash title="Update a simple event" PUT /event Content-Type: application/json ``` ```json { "provider": "google", "eventId": "event123abc", "calendarId": "primary", "title": "Updated Team Meeting", "start": "2025-11-01T14:00:00Z", "end": "2025-11-01T15:00:00Z", "conference": { "url": "https://meet.google.com/new-room-link", "provider": "google-meet" } } ``` ```bash title="Update a single instance of a recurring event" PUT /event Content-Type: application/json ``` ```json { "provider": "microsoft", "eventId": "instance456", "recurringEventId": "series123", "calendarId": "AAMkAGVmMDEz...", "updateMode": "this", "title": "Standup - Special Topic Today", "start": "2025-11-01T09:00:00Z", "end": "2025-11-01T10:00:00Z" } ``` ```bash title="Update all future occurrences" PUT /event Content-Type: application/json ``` ```json { "provider": "apple", "eventId": "recurring-event-id", "calendarId": "https://caldav.icloud.com/.../calendars/...", "updateMode": "following", "location": "New Conference Room B" } ``` **Node.js SDK** ```typescript // Update a simple event const updatedEvent = await client.events.update({ provider: 'google', calendarId: 'primary', eventId: 'event123abc', title: 'Updated Team Meeting', start: '2025-11-01T14:00:00Z', end: '2025-11-01T15:00:00Z', conference: { url: 'https://meet.google.com/new-room-link', provider: 'google-meet' } }); // Update a single instance of a recurring event const updatedInstance = await client.events.update({ provider: 'microsoft', eventId: 'instance456', recurringEventId: 'series123', calendarId: 'AAMkAGVmMDEz...', updateMode: 'this', title: 'Standup - Special Topic Today', start: '2025-11-01T09:00:00Z', end: '2025-11-01T10:00:00Z' }); // Update all future occurrences const updatedFuture = await client.events.update({ provider: 'apple', eventId: 'recurring-event-id', calendarId: 'https://caldav.icloud.com/.../calendars/...', updateMode: 'following', location: 'New Conference Room B' }); ``` **Python SDK** ```python # Update a simple event updated_event = client.events.update({ 'provider': 'google', 'calendarId': 'primary', 'eventId': 'event123abc', 'title': 'Updated Team Meeting', 'start': '2025-11-01T14:00:00Z', 'end': '2025-11-01T15:00:00Z', }) # Update a single instance of a recurring event updated_instance = client.events.update({ 'provider': 'microsoft', 'eventId': 'instance456', 'recurringEventId': 'series123', 'calendarId': 'AAMkAGVmMDEz...', 'updateMode': 'this', 'title': 'Standup - Special Topic Today', 'start': '2025-11-01T09:00:00Z', 'end': '2025-11-01T10:00:00Z', }) # Update all future occurrences updated_future = client.events.update({ 'provider': 'apple', 'eventId': 'recurring-event-id', 'calendarId': 'https://caldav.icloud.com/.../calendars/...', 'updateMode': 'following', 'location': 'New Conference Room B', }) ``` **PHP SDK** ```php // Update a simple event $updatedEvent = $client->events()->update([ 'provider' => 'google', 'calendarId' => 'primary', 'eventId' => 'event123abc', 'title' => 'Updated Team Meeting', 'start' => '2025-11-01T14:00:00Z', 'end' => '2025-11-01T15:00:00Z', ]); // Update a single instance of a recurring event $updatedInstance = $client->events()->update([ 'provider' => 'microsoft', 'eventId' => 'instance456', 'recurringEventId' => 'series123', 'calendarId' => 'AAMkAGVmMDEz...', 'title' => 'Standup - Special Topic Today', 'start' => '2025-11-01T09:00:00Z', 'end' => '2025-11-01T10:00:00Z', ]); // Update all future occurrences $updatedFuture = $client->events()->update([ 'provider' => 'apple', 'eventId' => 'recurring-event-id', 'calendarId' => 'https://caldav.icloud.com/.../calendars/...', 'location' => 'New Conference Room B', ]); ``` **.NET SDK** ```csharp // Update a simple event var updatedEvent = await client.Events.UpdateAsync(new UpdateEventParams { Provider = "google", CalendarId = "primary", EventId = "event123abc", Title = "Updated Team Meeting", Start = DateTime.Parse("2025-11-01T14:00:00Z"), End = DateTime.Parse("2025-11-01T15:00:00Z"), }); // Update a single instance of a recurring event var updatedInstance = await client.Events.UpdateAsync(new UpdateEventParams { Provider = "microsoft", EventId = "instance456", RecurringEventId = "series123", CalendarId = "AAMkAGVmMDEz...", UpdateMode = "this", Title = "Standup - Special Topic Today", Start = DateTime.Parse("2025-11-01T09:00:00Z"), End = DateTime.Parse("2025-11-01T10:00:00Z"), }); // Update all future occurrences var updatedFuture = await client.Events.UpdateAsync(new UpdateEventParams { Provider = "apple", EventId = "recurring-event-id", CalendarId = "https://caldav.icloud.com/.../calendars/...", UpdateMode = "following", Location = "New Conference Room B", }); ``` **Java SDK** ```java // Update a simple event CalendarEvent updatedEvent = client.events().update(EventUpdateData.builder() .provider(Provider.GOOGLE) .calendarId("primary") .eventId("event123abc") .title("Updated Team Meeting") .start(OffsetDateTime.parse("2025-11-01T14:00:00Z")) .end(OffsetDateTime.parse("2025-11-01T15:00:00Z")) .build()); // Update a single instance of a recurring event CalendarEvent updatedInstance = client.events().update(EventUpdateData.builder() .provider(Provider.MICROSOFT) .eventId("instance456") .recurringEventId("series123") .calendarId("AAMkAGVmMDEz...") .updateMode("this") .title("Standup - Special Topic Today") .start(OffsetDateTime.parse("2025-11-01T09:00:00Z")) .end(OffsetDateTime.parse("2025-11-01T10:00:00Z")) .build()); // Update all future occurrences CalendarEvent updatedFuture = client.events().update(EventUpdateData.builder() .provider(Provider.APPLE) .eventId("recurring-event-id") .calendarId("https://caldav.icloud.com/.../calendars/...") .updateMode("following") .location("New Conference Room B") .build()); ``` **Go SDK** ```go "time" mobiscroll "github.com/acidb/mobiscroll-connect-sdks/sdks/go" ) start, _ := time.Parse(time.RFC3339, "2025-11-01T14:00:00Z") end, _ := time.Parse(time.RFC3339, "2025-11-01T15:00:00Z") // Update a simple event updatedEvent, err := client.Events().Update(ctx, &mobiscroll.EventUpdateData{ Provider: mobiscroll.ProviderGoogle, CalendarID: "primary", EventID: "event123abc", Title: "Updated Team Meeting", Start: &start, End: &end, }) // Update a single instance of a recurring event instanceStart, _ := time.Parse(time.RFC3339, "2025-11-01T09:00:00Z") instanceEnd, _ := time.Parse(time.RFC3339, "2025-11-01T10:00:00Z") updatedInstance, err := client.Events().Update(ctx, &mobiscroll.EventUpdateData{ Provider: mobiscroll.ProviderMicrosoft, EventID: "instance456", RecurringEventID: "series123", CalendarID: "AAMkAGVmMDEz...", UpdateMode: "this", Title: "Standup - Special Topic Today", Start: &instanceStart, End: &instanceEnd, }) // Update all future occurrences updatedFuture, err := client.Events().Update(ctx, &mobiscroll.EventUpdateData{ Provider: mobiscroll.ProviderApple, EventID: "recurring-event-id", CalendarID: "https://caldav.icloud.com/.../calendars/...", UpdateMode: "following", Location: "New Conference Room B", }) ``` **Ruby SDK** ```ruby # Update a simple event updated_event = client.events.update( provider: Mobiscroll::Connect::Provider::GOOGLE, calendar_id: 'primary', event_id: 'event123abc', title: 'Updated Team Meeting', start: '2025-11-01T14:00:00Z', end: '2025-11-01T15:00:00Z' ) # Update a single instance of a recurring event updated_instance = client.events.update( provider: Mobiscroll::Connect::Provider::MICROSOFT, event_id: 'instance456', recurring_event_id: 'series123', calendar_id: 'AAMkAGVmMDEz...', update_mode: 'this', title: 'Standup - Special Topic Today', start: '2025-11-01T09:00:00Z', end: '2025-11-01T10:00:00Z' ) # Update all future occurrences updated_future = client.events.update( provider: Mobiscroll::Connect::Provider::APPLE, event_id: 'recurring-event-id', calendar_id: 'https://caldav.icloud.com/.../calendars/...', update_mode: 'following', location: 'New Conference Room B' ) ``` :::info - The provider parameter must match the calendar provider where the event exists - For recurring events, use recurringEventId and updateMode to control update scope - Only include fields you want to update; omitted fields remain unchanged - When using updateMode: 'this' on a recurring event, a new exception instance may be created - The updateMode parameter behavior may vary slightly between providers ::: --- ## Delete Event Deletes a calendar event. Supports deleting single events, recurring event series, or individual instances of recurring events. For recurring events, you can specify whether to delete only the current instance, all following instances, or the entire series. **Endpoint:** DELETE /event ### Request Body Calendar provider where the event exists. One of: 'google', 'microsoft', 'apple', or 'caldav'. The event identifier to delete. The calendar identifier containing the event. undefined} id="delete-recurringEventId"> The ID of the recurring event series. Required when deleting an instance of a recurring event. undefined} id="delete-deleteMode"> Controls which events in a recurring series are deleted: - 'this' - Delete only this specific instance - 'following' - Delete this and all following instances - 'all' - Delete all instances in the series Only applicable for recurring events. If not specified, deletes the single event or entire series. ### Response Indicates whether the event was deleted successfully. Confirmation or error message. ### Error Responses - **400** - Bad Request. Invalid request data or missing required fields - **401** - Unauthorized. Invalid or missing bearer token - **404** - Not Found. Event or calendar not found - **500** - Internal Server Error. Provider error or unexpected failure ### Examples **REST** ```bash title="Delete a simple event" DELETE /event Content-Type: application/json ``` ```json { "provider": "google", "eventId": "event123abc", "calendarId": "primary" } ``` ```json title="Response" { "success": true, "message": "Event deleted successfully" } ``` ```bash title="Delete a single instance of a recurring event" DELETE /event Content-Type: application/json ``` ```json { "provider": "microsoft", "eventId": "instance456", "recurringEventId": "series123", "calendarId": "AAMkAGVmMDEz...", "deleteMode": "this" } ``` ```bash title="Delete entire recurring series" DELETE /event Content-Type: application/json ``` ```json { "provider": "apple", "eventId": "recurring-event-id", "calendarId": "https://caldav.icloud.com/.../calendars/...", "deleteMode": "all" } ``` **Node.js SDK** ```typescript // Delete a simple event await client.events.delete({ provider: 'google', calendarId: 'primary', eventId: 'event123abc' }); // Delete a single instance of a recurring event await client.events.delete({ provider: 'microsoft', calendarId: 'AAMkAGVmMDEz...', eventId: 'instance456', recurringEventId: 'series123', deleteMode: 'this' }); // Delete entire recurring series await client.events.delete({ provider: 'apple', calendarId: 'https://caldav.icloud.com/.../calendars/...', eventId: 'recurring-event-id', deleteMode: 'all' }); ``` **Python SDK** ```python # Delete a simple event client.events.delete({ 'provider': 'google', 'calendarId': 'primary', 'eventId': 'event123abc', }) # Delete a single instance of a recurring event client.events.delete({ 'provider': 'microsoft', 'calendarId': 'AAMkAGVmMDEz...', 'eventId': 'instance456', 'recurringEventId': 'series123', 'deleteMode': 'this', }) # Delete entire recurring series client.events.delete({ 'provider': 'apple', 'calendarId': 'https://caldav.icloud.com/.../calendars/...', 'eventId': 'recurring-event-id', 'deleteMode': 'all', }) ``` **PHP SDK** ```php // Delete a simple event $client->events()->delete([ 'provider' => 'google', 'calendarId' => 'primary', 'eventId' => 'event123abc', ]); // Delete a single instance of a recurring event $client->events()->delete([ 'provider' => 'microsoft', 'calendarId' => 'AAMkAGVmMDEz...', 'eventId' => 'instance456', 'recurringEventId' => 'series123', 'deleteMode' => 'this', ]); // Delete entire recurring series $client->events()->delete([ 'provider' => 'apple', 'calendarId' => 'https://caldav.icloud.com/.../calendars/...', 'eventId' => 'recurring-event-id', 'deleteMode' => 'all', ]); ``` **.NET SDK** ```csharp // Delete a simple event await client.Events.DeleteAsync(new DeleteEventParams { Provider = "google", CalendarId = "primary", EventId = "event123abc", }); // Delete a single instance of a recurring event await client.Events.DeleteAsync(new DeleteEventParams { Provider = "microsoft", CalendarId = "AAMkAGVmMDEz...", EventId = "instance456", RecurringEventId = "series123", DeleteMode = "this", }); // Delete entire recurring series await client.Events.DeleteAsync(new DeleteEventParams { Provider = "apple", CalendarId = "https://caldav.icloud.com/.../calendars/...", EventId = "recurring-event-id", DeleteMode = "all", }); ``` **Java SDK** ```java // Delete a simple event client.events().delete(EventDeleteParams.builder() .provider(Provider.GOOGLE) .calendarId("primary") .eventId("event123abc") .build()); // Delete a single instance of a recurring event client.events().delete(EventDeleteParams.builder() .provider(Provider.MICROSOFT) .calendarId("AAMkAGVmMDEz...") .eventId("instance456") .recurringEventId("series123") .deleteMode("this") .build()); // Delete entire recurring series client.events().delete(EventDeleteParams.builder() .provider(Provider.APPLE) .calendarId("https://caldav.icloud.com/.../calendars/...") .eventId("recurring-event-id") .deleteMode("all") .build()); ``` **Go SDK** ```go // Delete a simple event err := client.Events().Delete(ctx, &mobiscroll.EventDeleteParams{ Provider: mobiscroll.ProviderGoogle, CalendarID: "primary", EventID: "event123abc", }) // Delete a single instance of a recurring event err = client.Events().Delete(ctx, &mobiscroll.EventDeleteParams{ Provider: mobiscroll.ProviderMicrosoft, CalendarID: "AAMkAGVmMDEz...", EventID: "instance456", RecurringEventID: "series123", DeleteMode: "this", }) // Delete entire recurring series err = client.Events().Delete(ctx, &mobiscroll.EventDeleteParams{ Provider: mobiscroll.ProviderApple, CalendarID: "https://caldav.icloud.com/.../calendars/...", EventID: "recurring-event-id", DeleteMode: "all", }) ``` **Ruby SDK** ```ruby # Delete a simple event client.events.delete( provider: Mobiscroll::Connect::Provider::GOOGLE, calendar_id: 'primary', event_id: 'event123abc' ) # Delete a single instance of a recurring event client.events.delete( provider: Mobiscroll::Connect::Provider::MICROSOFT, calendar_id: 'AAMkAGVmMDEz...', event_id: 'instance456', recurring_event_id: 'series123', delete_mode: 'this' ) # Delete entire recurring series client.events.delete( provider: Mobiscroll::Connect::Provider::APPLE, calendar_id: 'https://caldav.icloud.com/.../calendars/...', event_id: 'recurring-event-id', delete_mode: 'all' ) ``` :::info - The provider parameter must match the calendar provider where the event exists - For recurring events, use recurringEventId and deleteMode to control deletion scope - Deleting with deleteMode: 'this' creates an exception (cancelled instance) in the series - Deleting with deleteMode: 'all' removes the entire recurring series - Deleted events cannot be recovered through the API - The deleteMode parameter behavior may vary slightly between providers ::: --- ## OAuth API ## Authorize Entry point for the OAuth2 authorization flow. Creates or retrieves the user in the database, loads any existing tokens, stores the OAuth request in a cookie, and redirects to the provider selection page where users can connect their calendar accounts. :::info This endpoint does not require authentication. It initiates the OAuth2 authorization flow. **Endpoint:** `GET /authorize` ::: ### Request Parameters Project/application identifier. This uniquely identifies your application in the Mobiscroll Connect system. Your own unique identifier for the user in your system who is being authorized. read-write} id="authorize-scope"> The scope of access requested from the user. Can be one of `free-busy`, `read`, or `read-write`. If not specified, defaults to `read-write`. Callback URL after authorization completes. **Note:** This parameter is retrieved from the database based on the `client_id`, not from the query parameter. The user will be redirected to this URL with the authorization code. undefined} id="authorize-state"> Optional state parameter to maintain across the OAuth flow. This is passed back to your redirect_uri and can be used to prevent CSRF attacks and maintain application state. Accept-Language, then en} id="authorize-lng"> Language for the Connect pages (provider selection, consent, login, and error pages). Supported values: `en`, `es`, `fr`, `ar`. When omitted, the UI falls back to the browser's `Accept-Language` header, then English. Arabic (`ar`) renders right-to-left. Example: `?lng=es`. undefined} id="authorize-response_type"> OAuth2 response type. Typically set to `"code"` for the authorization code flow. ### Response Redirects to the provider selection page (`/authorize`) with all query parameters preserved (including the resolved `redirect_uri` from the database). The provider selection page allows the user to: 1. Choose which calendar provider(s) to connect (Google Calendar, Microsoft Outlook, Apple Calendar, CalDAV) 2. Authenticate with each selected provider 3. Review and approve the OAuth consent screen 4. Complete the OAuth authorization flow After the user successfully authenticates and approves the authorization, they will be redirected back to your application's `redirect_uri` with: - `code`: The authorization code that can be exchanged for an access token (this is the `user_id` value) - `state`: The state parameter you provided (if any), for CSRF protection **Example redirect to your application:** ``` https://app.example.com/callback?code=user-456&state=xyz789 ``` Sets `oauth_req` cookie containing the complete OAuth request for later retrieval. The cookie has the following properties: - **httpOnly**: true - **path**: / - **maxAge**: 2 hours - **sameSite**: lax ### Error Responses - **400** - Bad Request. Error creating or retrieving user - **401** - Unauthorized. Invalid or unrecognized `client_id` - **500** - Internal Server Error. Database or unexpected error ### Examples **REST** ```bash title="Initiate OAuth authorization" GET /authorize?client_id=proj-123&user_id=user-456&redirect_uri=https://app.example.com/callback&response_type=code&state=xyz789&scope=read-write # Optional: append &lng=es to localize the Connect pages (en | es | fr | ar) ``` ```bash title="Redirects to authorization page" 302 Redirect Location: /authorize?client_id=proj-123&user_id=user-456&redirect_uri=https://app.example.com/callback&response_type=code&state=xyz789&scope=read-write Set-Cookie: oauth_req={"client_id":"proj-123","user_id":"user-456",...}; HttpOnly; Path=/; Max-Age=7200; SameSite=Lax ``` **Node.js SDK** ```typescript // Generate the authorization URL const authUrl = client.auth.generateAuthUrl({ userId: 'user-456', // Optional parameters // state: 'xyz789', // scope: 'calendar.readonly', // lng: 'es', // localize the Connect pages: en | es | fr | ar }); // Redirect the user to authUrl // res.redirect(authUrl); ``` **Python SDK** ```python # Generate the authorization URL auth_url = client.auth.generate_auth_url( user_id='user-456', # Optional parameters: # scope='read-write', # state='xyz789', # lng='es', # localize the Connect pages: en | es | fr | ar ) # Redirect the user to auth_url # return redirect(auth_url) ``` **PHP SDK** ```php // Generate the authorization URL $authUrl = $client->auth()->generateAuthUrl( userId: 'user-456', // Optional parameters: // scope: 'read-write', // state: 'xyz789', // lng: 'es', // localize the Connect pages: en | es | fr | ar ); // Redirect the user to $authUrl header('Location: ' . $authUrl); ``` **.NET SDK** ```csharp // Generate the authorization URL var authUrl = client.Auth.GenerateAuthUrl(new AuthorizeParams { UserId = "user-456", // Optional parameters: // Scope = "read-write", // State = "xyz789", // Lng = "es", // localize the Connect pages: en | es | fr | ar }); // Redirect the user to authUrl // return Redirect(authUrl); ``` **Java SDK** ```java // Generate the authorization URL String authUrl = client.auth().generateAuthUrl(AuthUrlParams.builder() .userId("user-456") // Optional parameters: // .scope("read-write") // .state("xyz789") // .lng("es") // localize the Connect pages: en | es | fr | ar .build()); // Redirect the user to authUrl // response.sendRedirect(authUrl); ``` **Go SDK** ```go // Generate the authorization URL authURL := client.Auth().GenerateAuthURL(&mobiscroll.AuthURLParams{ UserID: "user-456", // Optional parameters: // Scope: "read-write", // State: "xyz789", // Lng: "es", // localize the Connect pages: en | es | fr | ar }) // Redirect the user to authURL // http.Redirect(w, r, authURL, http.StatusFound) ``` **Ruby SDK** ```ruby # Generate the authorization URL auth_url = client.auth.generate_auth_url( user_id: 'user-456' # Optional parameters: # scope: 'read-write', # state: 'xyz789', # lng: 'es' # localize the Connect pages: en | es | fr | ar ) # Redirect the user to auth_url # redirect auth_url ``` :::info - The `client_id` is validated against the database before proceeding - If the `client_id` is invalid, the user is redirected to an error page - The `redirect_uri` is retrieved from the database based on the `client_id` (not from the query parameter) - The OAuth request is stored in a cookie to maintain state across the authorization flow - If the user already exists in the database, their existing information is retrieved - Any existing provider tokens are loaded and initialized in the token store - The cookie expires after 2 hours if the flow is not completed - All query parameters plus the database `redirect_uri` are preserved and passed to the provider selection page ::: :::tip Localizing the Connect pages Pass `lng` on the authorize URL to render the Connect pages (provider selection, consent, login, and error pages) in a specific language. Supported: English (`en`), Spanish (`es`), French (`fr`), and Arabic (`ar`). - If `lng` is omitted, the UI falls back to the browser's `Accept-Language` header, then English. - Arabic (`ar`) is right-to-left; the UI direction switches automatically. - Example: `/authorize?client_id=...&user_id=...&lng=es`. In the SDKs, pass `lng` to the auth-URL builder (see the examples above). ::: :::tip Personalizing the Connect pages Set a **Display Name** for your application in the [dashboard](/connect/application-setup#creating-the-first-application). When set, the authorization and connection-management screens are personalized with it (e.g. "_<Display Name>_ is using Mobiscroll Connect ..." and a "Back to _<Display Name>_" button) so users always know which app they are connecting to. When no display name is set, a generic wording is shown. No request parameter is required - the display name is resolved from the `client_id`. ::: :::info Re-authorizing an existing user Calling `/authorize` again for a `user_id` that already has tokens **does not revoke** any existing access or refresh tokens. The existing tokens are loaded and remain valid. The authorization flow simply allows the user to connect additional calendar provider accounts on top of the ones already connected. To fully invalidate a user's tokens before re-authorization, call [`POST /revoke`](#endpoint-revoke) first. ::: --- ## Token management ### Token Lifetimes The following table summarizes the lifetime of every credential issued by Mobiscroll Connect. | Token / Credential | Lifetime | Notes | |--------------------|----------|-------| | **Authorization code** | 10 minutes | Single-use — deleted on first exchange | | **Access token (JWT)** | 1 hour (`3600 s`) | `exp` claim embedded in the JWT; rejected immediately after expiry even if the signature is valid | | **Refresh token** | No embedded expiry | The refresh token **does not** expire after a given time. Validity enforced server-side via JTI rotation (rotation happens on refresh call, not by time). Remains valid until used/rotated or explicitly revoked. If refresh returns `invalid_grant` or the newly issued refresh token is lost before persistence, the user must re-authorize. | | **OAuth session cookie** | 2 hours | `oauth_req` cookie that carries the in-progress authorization state | :::info Refresh Token Rotation Every `POST /token` call with `grant_type=refresh_token` issues a **new** refresh token and immediately invalidates the previous one. Presenting an already-rotated refresh token returns `invalid_grant`. Calling `/revoke` invalidates the entire token family (all access tokens and refresh tokens for that user/client pair) at once. ::: ### Handling token expiry on API calls All protected API endpoints (`/events`, `/calendars`, `/webhooks/subscribe`, etc.) validate the `Authorization: Bearer ` header. When the access token is missing, expired, or revoked the server returns **`401 Unauthorized`**. Recommended handling flow: ``` API call └─► 401 Unauthorized └─► POST /token (grant_type=refresh_token) ├─► 200 OK → persist new tokens, retry original request └─► 400 invalid_grant → user must re-authorize (redirect to GET /authorize) ``` :::tip Implement a single request-retry wrapper (or an HTTP interceptor) that catches `401` responses, attempts one token refresh, and only falls back to re-authorization if the refresh itself fails. ::: --- ## Get Access Token Exchanges an authorization code for an access token. This is the token endpoint of the OAuth2 authorization code flow. :::info This endpoint requires client authentication using either HTTP Basic authentication or client credentials in the request body. **Endpoint:** `POST /token` ::: ### Authentication This endpoint supports two authentication methods: **HTTP Basic Authentication:** ``` Authorization: Basic base64(client_id:client_secret) ``` **Request Body Authentication:** - Include `client_id` and `client_secret` in the request body ### Request Parameters The OAuth2 grant type. Must be set to `"authorization_code"` for the authorization code flow. The authorization code received from the authorization endpoint. This code is the `user_id` and is returned to your `redirect_uri` after the user completes the authorization flow. The redirect URI used in the authorization request. This must match exactly with the redirect URI used when obtaining the authorization code. undefined} id="token-client_id"> Your application's client identifier. Required if not using HTTP Basic authentication. undefined} id="token-client_secret"> Your application's client secret. Required if not using HTTP Basic authentication. ### Response The access token (JWT) that can be used to authenticate API requests. This token contains the user ID, client ID, and project ID in the payload. :::info What is an Access Token? An **access token** is a credential (usually a string) that your application receives after a successful authentication. It is used to authorize API requests on behalf of a user. In Mobiscroll Connect, the access token is a JWT (JSON Web Token). ::: :::info What is a JWT (JSON Web Token)? A **JWT** is a secure, compact, URL-safe token format that encodes claims about the user and the application. Mobiscroll Connect uses JWTs as access tokens, which include information such as the user ID, client ID, and expiration time. The server verifies the JWT to ensure requests are authentic and authorized. ::: The type of token returned. Always `"Bearer"`. The lifetime in seconds of the access token. `3600` (1 hour). A refresh token that can be used to obtain a new access token when the current one expires. Refresh tokens do not expire on their own — they remain valid until used or explicitly revoked. Each use of a refresh token issues a new access token **and** a new refresh token (token rotation), and the previous refresh token is invalidated. Store this token securely on the server side. :::info What is a Refresh Token? An **access token** is short-lived (1 hour). Rather than forcing the user to log in again every hour, the server issues a **refresh token** alongside the access token. Your application stores the refresh token securely (server-side, never in the browser) and uses it — silently, without user interaction — to obtain a fresh access token whenever the current one expires. If you lose the refresh token (e.g. it was never persisted, or was overwritten before the new one was saved), the user must complete the full OAuth authorization flow again to restore access. ::: :::info Refresh Token Rotation Mobiscroll Connect implements **refresh token rotation**: every time you exchange a refresh token for a new access token, the server issues a brand-new refresh token and invalidates the old one. If an already-used (revoked) refresh token is presented again, the request is rejected with `invalid_grant`. ::: ### Error Responses - **400** - Bad Request - `invalid_grant` - Invalid authorization code, code expired, client mismatch, or redirect URI mismatch - `invalid_client` - Client authentication failed - `invalid_request` - Missing required parameters - `session_expired` - OAuth session expired (transaction lost) - **401** - Unauthorized. Client authentication failed ### Token Validation The endpoint performs the following validations: 1. **Authorization code exists** - The code must be valid and found in the database 2. **Client match** - The client_id must match the one used to obtain the code 3. **Redirect URI match** - The redirect_uri must exactly match the original request 4. **Code expiration** - The authorization code expires after 10 minutes 5. **Single use** - The authorization code is deleted after successful exchange ### JWT Token Payload The **access token** is a JWT containing: ```json { "sub": "user_id", // User identifier "aud": "client_id", // Client/Project identifier "projectId": "client_id", // Project identifier "exp": 1234567890 // Expiration timestamp (1 hour from issuance) } ``` The **refresh token** is a JWT containing: ```json { "sub": "user_id", // User identifier "aud": "client_id", // Client/Project identifier "projectId": "client_id", // Project identifier "type": "refresh", // Token type discriminator "jti": "uuid", // Unique token ID (used for rotation tracking) // No "exp" — validity is managed server-side via the database } ``` ### Examples **REST** ```bash title="Exchange authorization code for access token (HTTP Basic Auth)" POST /token Authorization: Basic YmFzZTY0X2VuY29kZWRfY3JlZGVudGlhbHM= Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=user-456&redirect_uri=https://app.example.com/callback ``` ```bash title="Exchange authorization code for access token (Body Auth)" POST /token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=user-456&redirect_uri=https://app.example.com/callback&client_id=proj-123&client_secret=secret-xyz ``` ```json title="Success Response" { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` ```json title="Error Response - Invalid Code" { "error": "invalid_grant", "error_description": "Invalid authorization code" } ``` ```json title="Error Response - Code Expired" { "error": "invalid_grant", "error_description": "Authorization code expired" } ``` **Node.js SDK** ```typescript // Exchange authorization code for access token // The client uses the configured clientId and clientSecret const tokenResponse = await client.auth.getToken(code); // The client is automatically authenticated with the new token // You can also access it later via tokenResponse.access_token or client.getConfig() credentials ``` **Python SDK** ```python # Exchange authorization code for access token # The client is automatically authenticated after get_token() token_response = client.auth.get_token(code) # Persist tokens for future requests session['access_token'] = token_response.access_token session['refresh_token'] = token_response.refresh_token ``` **PHP SDK** ```php // Exchange authorization code for access token $code = $_GET['code']; $tokenResponse = $client->auth()->getToken($code); // Store the token and authenticate the client $client->auth()->setCredentials($tokenResponse); ``` **.NET SDK** ```csharp // Exchange authorization code for access token var tokenResponse = await client.Auth.GetTokenAsync(code); // The client is automatically authenticated with the new token client.Auth.SetCredentials(tokenResponse); ``` **Java SDK** ```java // Exchange authorization code for access token // The client is automatically authenticated with the new token TokenResponse tokens = client.auth().getToken(code); // Persist tokens server-side keyed by your user httpSession.setAttribute("access_token", tokens.getAccessToken()); httpSession.setAttribute("refresh_token", tokens.getRefreshToken()); ``` **Go SDK** ```go // Exchange authorization code for access token // The client is automatically authenticated with the new token tokens, err := client.Auth().GetToken(ctx, code) if err != nil { // handle error } // Persist tokens server-side keyed by your user session.Set("access_token", tokens.AccessToken) session.Set("refresh_token", tokens.RefreshToken) ``` **Ruby SDK** ```ruby # Exchange authorization code for access token # The client is automatically authenticated with the new token tokens = client.auth.get_token(code) # Persist tokens server-side keyed by your user session[:access_token] = tokens.access_token session[:refresh_token] = tokens.refresh_token ``` ```json title="Error Response - Client Mismatch" { "error": "invalid_grant", "error_description": "Client mismatch" } ``` :::info - Authorization codes expire after 10 minutes - Authorization codes are single-use and deleted after successful exchange - Access tokens expire after 1 hour - A refresh token is always returned alongside the access token; store it securely server-side - The access token is a JWT that must be included as a Bearer token in all subsequent API requests - Client credentials can be provided via HTTP Basic authentication or in the request body - The redirect_uri must exactly match the one used in the authorization request - Revoked access tokens are rejected immediately even if the JWT signature is still valid ::: --- ## Refresh Access Token Obtains a new access token using a refresh token. Each call also issues a new refresh token and invalidates the previously used one (refresh token rotation). :::info This endpoint requires client authentication using either HTTP Basic authentication or client credentials in the request body. **Endpoint:** `POST /token` ::: ### Authentication Same as [Get Access Token](#endpoint-token) — HTTP Basic or request body credentials. ### Request Parameters Must be `"refresh_token"`. The refresh token obtained from a previous token exchange. undefined} id="refresh-client_id"> Your application's client identifier. Required if not using HTTP Basic authentication. undefined} id="refresh-client_secret"> Your application's client secret. Required if not using HTTP Basic authentication. ### Response A new access token (JWT) valid for 1 hour. Always `"Bearer"`. The lifetime in seconds of the new access token. `3600` (1 hour). A new refresh token. The previously used refresh token is immediately invalidated. Store this new token in place of the old one. :::info Scope inheritance The new access token carries the **same scope** as the original authorization. Scope cannot be upgraded through token refresh — the user must re-authorize (via [`GET /authorize`](#endpoint-authorize)) to request a different scope. ::: :::warning Persist the new refresh token before using it The moment this endpoint responds successfully, the **old** refresh token is gone. You must durably persist the new refresh token **before** discarding the old one. If your application crashes, loses the response, or fails to save the token to your database: - The old token is already invalidated — it cannot be reused. - The new token is lost — it was only ever returned in this response. - **The user must re-authorize** (complete the full OAuth flow again) to restore access. To mitigate this, persist the new token atomically (e.g. in a database transaction) before acknowledging success, and avoid concurrent refresh calls for the same user. ::: ### Error Responses - **400** - Bad Request - `invalid_grant` - Refresh token not found, already revoked, client mismatch, or wrong token type - `invalid_client` - Client authentication failed ### Examples **REST** ```bash title="Refresh access token (HTTP Basic Auth)" POST /token Authorization: Basic YmFzZTY0X2VuY29kZWRfY3JlZGVudGlhbHM= Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ```json title="Success Response" { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` ```json title="Error Response - Revoked or Invalid Token" { "error": "invalid_grant", "error_description": "Invalid refresh token" } ``` **Python SDK** ```python # Token refresh is handled automatically by the SDK. # Register a callback to persist the new tokens whenever a refresh occurs. from mobiscroll_connect import TokenResponse def persist_tokens(tokens: TokenResponse) -> None: # Persist in your database or session store session['access_token'] = tokens.access_token session['refresh_token'] = tokens.refresh_token client.on_tokens_refreshed(persist_tokens) ``` **PHP SDK** ```php // Token refresh is handled automatically by the SDK. // Register a callback to persist the new tokens whenever a refresh occurs. $client->onTokensRefreshed(function (TokenResponse $updatedTokens) { // Persist in your database or session store $_SESSION['tokens'] = $updatedTokens->toArray(); }); ``` **.NET SDK** ```csharp // Token refresh is handled automatically by the SDK. // Register a callback to persist the new tokens whenever a refresh occurs. client.OnTokensRefreshed(updatedTokens => { // Persist in your database or session store HttpContext.Session.SetString("access_token", updatedTokens.AccessToken); HttpContext.Session.SetString("refresh_token", updatedTokens.RefreshToken ?? ""); }); ``` **Java SDK** ```java // Token refresh is handled automatically by the SDK. // Register a callback to persist the new tokens whenever a refresh occurs. client.onTokensRefreshed(updatedTokens -> { // Persist in your database or session store httpSession.setAttribute("access_token", updatedTokens.getAccessToken()); httpSession.setAttribute("refresh_token", updatedTokens.getRefreshToken()); }); ``` **Go SDK** ```go // Token refresh is handled automatically by the SDK. // Register a callback to persist the new tokens whenever a refresh occurs. client.OnTokensRefreshed(func(updated *mobiscroll.TokenResponse) { // Persist in your database or session store session.Set("access_token", updated.AccessToken) session.Set("refresh_token", updated.RefreshToken) }) ``` **Ruby SDK** ```ruby # Token refresh is handled automatically by the SDK. # Register a callback to persist the new tokens whenever a refresh occurs. client.on_tokens_refreshed do |tokens| # Persist in your database or session store session[:access_token] = tokens.access_token session[:refresh_token] = tokens.refresh_token if tokens.refresh_token end ``` :::info - Always replace the stored refresh token with the new one returned in the response - Presenting a refresh token that has already been used (rotated out) is rejected with `invalid_grant` - Repeated or concurrent refresh attempts with the same token can fail once rotation has already occurred - Refresh tokens are managed server-side; they have no embedded expiry date ::: --- ## Revoke Token Revokes all active access tokens and refresh tokens for a given user/client pair. Use this endpoint when the user logs out or explicitly disconnects from Mobiscroll Connect. :::info This endpoint does not require authentication. The token itself is used to identify the user and client. **Endpoint:** `POST /revoke` ::: ### Request Body Either an access token or a refresh token. The server decodes it to extract the user and client identifiers, then revokes all active tokens for that user/client combination. ### Response Returns `200 OK` even if the token was already invalid or expired — this prevents leaking information about token state. ```json title="Success Response" { "success": true } ``` ### Error Responses - **400** - Bad Request. `token` field is missing or not a string. ### Examples **REST** ```bash title="Revoke tokens" POST /revoke Content-Type: application/json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` ```json title="Success Response" { "success": true } ``` **Python SDK** The Python SDK does not include a built-in revoke method. Use the REST endpoint directly to revoke tokens. **PHP SDK** The PHP SDK does not include a built-in revoke method. Use the REST endpoint directly to revoke tokens. **.NET SDK** The .NET SDK does not include a built-in revoke method. Use the REST endpoint directly to revoke tokens. **Java SDK** The Java SDK does not include a built-in revoke method. Use the REST endpoint directly to revoke tokens. **Go SDK** The Go SDK does not include a built-in revoke method. Use the REST endpoint directly to revoke tokens. **Ruby SDK** The Ruby SDK does not include a built-in revoke method. Use the REST endpoint directly to revoke tokens. :::info - Both access tokens and refresh tokens are accepted — the endpoint revokes the entire token family regardless of which token type is provided - The response is always `200` with `{ "success": true }` to avoid leaking information about token validity - After revocation, the user must complete the OAuth authorization flow again to obtain new tokens ::: --- ## Overview Welcome to the Mobiscroll Connect API documentation. This API enables you to integrate with multiple calendar providers (Google Calendar, Microsoft Outlook, Apple Calendar, and CalDAV) through a unified interface. ## Authentication ![Auth Flow](/connect/auth_flow.png) Most API endpoints require authentication using a Bearer token (JWT). Include the token in the `Authorization` header: ``` Authorization: Bearer YOUR_JWT_TOKEN ``` **Exception:** The [OAuth authorization endpoint](/connect/oauth#endpoint-authorize) does not require authentication as it initiates the OAuth flow. To obtain an access token: 1. Direct users to the **GET /authorize** endpoint to initiate the OAuth2 flow 2. Users will authenticate with their calendar provider(s) 3. After successful authentication, they'll be redirected to your `redirect_uri` with an authorization code 4. Exchange the authorization code for an access token using **POST /token** 5. Use the access token in the `Authorization` header for all subsequent API requests ![API Flow](/connect/api_flow.png) ## Available Endpoints ### [OAuth API](/connect/oauth) Initiate and complete the OAuth2 authorization flow to connect user calendar accounts. - **[GET /authorize](/connect/oauth#endpoint-authorize)** - Initiate OAuth2 authorization flow - **[POST /token](/connect/oauth#endpoint-token)** - Exchange authorization code for access token - **[POST /token](/connect/oauth#endpoint-refresh-token)** - Refresh access token using a refresh token - **[POST /revoke](/connect/oauth#endpoint-revoke)** - Revoke all active tokens for a user ### [Events API](/connect/events) Retrieve, create, update, and delete calendar events from connected providers with support for pagination, filtering, and recurring events. - **[GET /events](/connect/events#endpoint-get-events)** - Fetch calendar events with advanced filtering and pagination - **[POST /event](/connect/events#endpoint-create-event)** - Create a new calendar event - **[PUT /event](/connect/events#endpoint-update-event)** - Update an existing calendar event - **[DELETE /event](/connect/events#endpoint-delete-event)** - Delete a calendar event ### [Calendars API](/connect/calendars) Manage calendar lists across all connected providers. - **[GET /calendars](/connect/calendars#endpoint-get-calendars)** - Retrieve all calendars from connected providers ### [Webhooks API](/connect/webhooks) Subscribe calendars and receive normalized event change notifications from Google, Microsoft, Apple, and CalDAV (polling-based for Apple and CalDAV). - **[POST /subscribe-webhook](/connect/webhooks#endpoint-subscribe-webhook)** - Create a webhook subscription for a calendar - **[POST /unsubscribe-webhook](/connect/webhooks#endpoint-unsubscribe-webhook)** - Remove an existing webhook subscription ## Supported Providers Mobiscroll Connect supports the following calendar providers: - **Google Calendar** (`'google'`) - **Microsoft Outlook** (`'microsoft'`) - **Apple Calendar** (`'apple'`) - **CalDAV** (`'caldav'`) ## Base URL All API requests should be made to: ``` https://connect.mobiscroll.com/api ``` ## Response Format All API responses are returned in JSON format. Successful responses include the requested data, while error responses include an `error` field with a descriptive message. ## Error Handling Standard HTTP status codes are used to indicate the success or failure of requests: - **200** - Success - **202** - Accepted (async operations) - **400** - Bad Request (invalid parameters) - **401** - Unauthorized (invalid or missing token) - **404** - Not Found (resource doesn't exist) - **500** - Internal Server Error (unexpected failure) --- ## Webhooks API Use webhooks to receive near real-time event change notifications from connected calendar providers. When a user modifies an event in Google, Microsoft, Apple, or CalDAV, Connect can forward the normalized change payload to your configured project webhook URL after you subscribe the calendar via `POST /subscribe-webhook`. This API lets you: - Subscribe a calendar to provider notifications - Unsubscribe an existing webhook channel - Receive normalized webhook payloads at your application webhook URL ## Supported providers - **Google Calendar** (`google`) - **Microsoft Outlook** (`microsoft`) - **Apple Calendar** (`apple`) - **CalDAV** (`caldav`) ## Subscribe webhook Creates a webhook subscription for a specific calendar. **Endpoint:** `POST /subscribe-webhook` ### Authentication Requires Bearer token authentication: ``` Authorization: Bearer YOUR_ACCESS_TOKEN ``` ### Request parameters Provider name. Supported values: `google`, `microsoft`, `apple`, `caldav`. Calendar ID to subscribe. Optional custom subscription channel ID. Optional Unix timestamp in milliseconds. Provider-specific subscription expiration. ### Response `true` when subscription is created. Provider associated with this subscription. Provider subscription details. Unique webhook channel/subscription identifier. Provider resource identifier when available. ISO 8601 expiration timestamp when available. Provider callback URL used by the subscription. Mobiscroll Connect callback endpoint registered with the provider. Channel ID used for the subscription. ### Error responses - **400** - Missing required parameters, unsupported provider, or project webhook URL not configured - **401** - Unauthorized (invalid or missing Bearer token) - **500** - Internal server error ### Example ```bash title="Create subscription" curl -X POST "https://connect.mobiscroll.com/api/subscribe-webhook" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "provider": "google", "calendarId": "work@company.com", "channelId": "my-channel-123" }' ``` ```json title="Response" { "success": true, "provider": "google", "subscription": { "channelId": "my-channel-123", "resourceId": "resource-abc", "expiration": "2026-03-19T10:00:00.000Z", "webhookUrl": "https://your-connect-server/api/webhook-receiver/google" }, "serverWebhookUrl": "https://your-connect-server/api/webhook-receiver/google", "channelId": "my-channel-123" } ``` --- ## Unsubscribe webhook Removes an existing webhook subscription channel. **Endpoint:** `POST /unsubscribe-webhook` ### Authentication Requires Bearer token authentication. ### Request parameters Provider name. Supported values: `google`, `microsoft`, `apple`, `caldav`. Channel/subscription ID to remove. undefined} id="unsubscribe-resourceId"> Optional provider resource ID when applicable. ### Response `true` when request is accepted and local mapping cleanup is completed. Additional status detail. ### Error responses - **400** - Missing parameters or unsupported provider - **401** - Unauthorized (invalid or missing Bearer token) - **500** - Internal server error ### Example ```bash title="Unsubscribe channel" curl -X POST "https://connect.mobiscroll.com/api/unsubscribe-webhook" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "provider": "google", "channelId": "my-channel-123", "resourceId": "resource-abc" }' ``` ```json title="Response" { "success": true, "message": "Webhook unsubscribed successfully" } ``` --- ## Client webhook payload When a provider notification is processed, Mobiscroll Connect forwards a normalized payload to your configured project webhook URL. Notification source provider. User ID in your system. Calendar ID where changes were detected. Changed events list. Calendar ID where the event belongs. Event ID. Event provider. Event title. Event description/notes (optional). ISO 8601 timestamp of the last provider-side modification (optional). Event start date/time. Event end date/time. Indicates all-day event. Recurring series master ID when this event is an instance (optional). One of `created`, `updated`, `deleted`. Optional event color. Optional event location. Optional attendee list. Attendee email. Response status: `accepted`, `declined`, `tentative`, or `none`. Indicates if attendee is organizer. Optional custom key-value pairs. Optional conference metadata. Conference meeting URL. If `true`, provider may auto-generate an online meeting link. Conference provider identifier. Provider-specific conference payload. Optional availability: `busy` or `free`. Optional privacy: `public`, `private`, or `confidential`. Optional event status: `confirmed`, `tentative`, or `cancelled`. Optional provider event link. Provider-native event object. Overall change summary: `created`, `updated`, `deleted`, or `mixed`. ISO 8601 processing timestamp. Additional webhook metadata. Subscription channel ID. Number of events in this delivery. Indicates initial/sync-state delivery when applicable. ### Example delivery ```json title="Delivered to your webhook URL" { "provider": "google", "userId": "user-123", "calendarId": "work@company.com", "events": [ { "id": "event-abc", "provider": "google", "calendarId": "work@company.com", "title": "Product review", "description": "Quarterly review", "lastModified": "2026-03-10T09:00:00.000Z", "start": "2026-03-12T09:00:00.000Z", "end": "2026-03-12T10:00:00.000Z", "allDay": false, "recurringEventId": "series-master-id", "color": "#9fc6e7", "location": "Office / Meeting room", "attendees": [ { "email": "user@example.com", "status": "accepted", "organizer": true } ], "custom": { "yourCustomKey": "yourCustomValue" }, "conference": { "url": "https://meet.example.com/abc", "provider": "google-meet" }, "availability": "busy", "privacy": "private", "status": "confirmed", "link": "https://provider-event-link", "changeType": "updated", "original": {} } ], "changeType": "updated", "timestamp": "2026-03-12T09:01:12.000Z", "metadata": { "channelId": "sub-123", "eventCount": 1, "isInitialSync": false } } ``` --- ## Setup requirements 1. Configure **Webhook URL** in your Connect application settings. See [Application setup](/connect/application-setup). 2. Ensure your webhook endpoint is public, reachable, and returns `2xx` quickly. 3. Keep endpoint handling idempotent and tolerant of out-of-order notifications. --- ## Operational notes - Delivery forwarding to your webhook URL is best-effort and should be handled with idempotent processing on your side. - Provider-side notifications may be emitted for changes regardless of where the change originated. - Connect applies built-in echo/loop filtering for very recent API-originated changes (short-lived in-memory dedup window, currently 30 seconds), so webhook echoes for your own immediate `POST /event` / `PUT /event` updates are suppressed in the common case. - In distributed or multi-instance setups, add an application-level origin marker in `custom` (for example `custom.source = "my-system"`) and ignore matching webhook events as an additional loop-prevention safeguard. - For Apple, event change detection is based on periodic synchronization (polled every 5 minutes) rather than provider-native push. - For CalDAV, event change detection is based on periodic synchronization rather than provider-native push. - A single delivery may contain multiple event changes and return `changeType: "mixed"`. - When notifications are filtered out, no events may be delivered in that callback cycle. --- ## Scopes & Permissions Mobiscroll Connect uses a tiered scope system to manage access levels and protect user privacy. When authenticating a user, you define the level of access your application needs. ## Available Scopes There are three main scopes available, ranging from restricted availability-only access to full read-write capabilities. | Scope | Read Access | Calendar List | Write Access | Description | | :--- | :--- | :--- | :--- | :--- | | **`free-busy`** | ⚠️ Availability Only | ❌ Forbidden (403) | ❌ Denied | **High Privacy.** Only retrieves free/busy slots. Event details are sanitized. | | **`read`** | ✅ Full Details | ✅ Allowed | ❌ Denied (401) | **Read-Only.** Full access to view events and calendars, but cannot make changes. | | **`read-write`** | ✅ Full Details | ✅ Allowed | ✅ Allowed | **Full Access.** Can view details, list calendars, and create/update/delete events. *Default* | ### Scope Details #### `free-busy` (High Privacy) This scope is designed for privacy-first use cases where you only need to know *when* a user is busy, but not *what* they are doing. * **Read Access:** RESTRICTED. You can retrieve availability (free/busy slots). * **Event Details:** SANITIZED. Titles, descriptions, and attendees are hidden. Events appear as opaque "Busy" blocks. * **Calendar List:** BLOCKED. Calling [`GET /calendars`](../api/calendars.md#endpoint-get-calendars) returns `403 Forbidden`. * **Write Access:** DENIED. Returns `401 Unauthorized` if attempted. **Endpoint Behavior Example:** When calling [`GET /calendars`](../api/calendars.md#endpoint-get-calendars) with `free-busy` scope: ```json // GET /calendars -> 403 Forbidden { "error": "Calendar list access is forbidden for free-busy scope" } ``` :::warning Developer Action The user interface must handle this restriction by skipping the calendar list fetch or hiding calendar selection when the user connects with `free-busy` scope. ::: #### `read` (Read-Only) This scope allows full visibility into the user's schedule without the risk of accidental modification. * **Read Access:** FULL. Can read all event details (titles, descriptions, attendees, etc.). * **Calendar List:** ALLOWED. Can list all available calendars. * **Write Access:** DENIED. Returns `401 Unauthorized` if attempted. **Endpoint Behavior Example:** When attempting to [create an event](../api/events.md#endpoint-create-event) with `read` scope: ```json // POST /event -> 401 Unauthorized { "error": "Unauthorized", "message": "Write access denied for current scope" } ``` #### `read-write` (Full Access) This is the default scope if none is specified. It provides complete control over the user's calendar. * **Read Access:** FULL. * **Calendar List:** ALLOWED. * **Write Access:** ALLOWED. Can create, update, and delete events. ## Access Escalation You can upgrade an existing connection to a higher scope (e.g., from `free-busy` to `read-write`) if the user enables features that require more permissions. ### Workflow 1. **Initiate new Auth Flow:** Redirect the user to the [authorization endpoint](../api/oauth.md#endpoint-authorize) with the new `scope` query parameter. * Example: `?scope=read-write` or `?scope=read` 2. **User Consent:** The provider (Google, Outlook, etc.) will present the consent screen again, asking the user to approve the additional access. 3. **Token Update:** Connect handles the incremental authorization internally. The existing user connection is updated with the new permissions. :::tip Best Practice Start with `free-busy` or `read` to build trust, and only request `read-write` when the user explicitly enables features that require it (like 'Add to Calendar'). ::: --- ## .NET SDK The Mobiscroll Connect .NET SDK provides a convenient way to integrate Mobiscroll Connect in .NET backend applications. It targets .NET 8 and runs on .NET Core, making it compatible with Linux, macOS, and Windows. ## Setup Install the package using NuGet: **.NET CLI** ```bash dotnet add package Mobiscroll.Connect ``` **NuGet Package Manager** ```bash NuGet\Install-Package Mobiscroll.Connect ``` **PackageReference** ```xml ``` ## Client Initialization To use the SDK, initialize `MobiscrollConnectClient` with your client credentials. **Class:** `Mobiscroll.Connect.MobiscrollConnectClient` Constructor arguments. Your Client ID obtained from the Mobiscroll Connect dashboard. Your Client Secret obtained from the Mobiscroll Connect dashboard. Your application's redirect URI that matches the one configured in the Mobiscroll Connect dashboard. **Usage:** ```csharp using Mobiscroll.Connect; var client = new MobiscrollConnectClient( clientId: "YOUR_CLIENT_ID", clientSecret: "YOUR_CLIENT_SECRET", redirectUri: "YOUR_REDIRECT_URI" ); ``` ## Methods ### SetCredentials Sets the access token for the client. This is required before making any API calls that require authentication. **Method:** `client.Auth.SetCredentials(tokens)` The token response object returned by `client.Auth.GetTokenAsync(code)`. ### OnTokensRefreshed Registers a callback to be invoked whenever the SDK automatically refreshes the access token. Use this to persist the updated tokens so they survive future requests. **Method:** `client.OnTokensRefreshed(callback)` An action that receives the updated `TokenResponse` after a successful automatic token refresh. ## Token Refresh The .NET SDK handles token refresh automatically. When any API call returns a `401 Unauthorized` response and the client has a `refresh_token` stored, the SDK will silently exchange it for a new access token and retry the original request — with no action required from your application. When the refresh succeeds, the SDK invokes your `OnTokensRefreshed` callback with the updated `TokenResponse`. You must register this callback and persist the new tokens, otherwise they will be lost between requests. ```csharp client.OnTokensRefreshed(updatedTokens => { // Persist updatedTokens in your database or session store // so the new access_token and refresh_token survive future requests HttpContext.Session.SetString("access_token", updatedTokens.AccessToken); HttpContext.Session.SetString("refresh_token", updatedTokens.RefreshToken ?? ""); }); ``` If the refresh token itself is invalid or has been revoked, the SDK throws an `AuthenticationException` and the user must re-authorize. ## Error Handling All SDK methods throw exceptions that extend `Mobiscroll.Connect.Exceptions.MobiscrollConnectException`. You can catch the base exception or any of the specific subclasses. | Exception | HTTP Status | `ErrorCode` | |---|---|---| | `AuthenticationException` | 401, 403 | `AUTHENTICATION_ERROR` | | `ValidationException` | 400, 422 | `VALIDATION_ERROR` | | `NotFoundException` | 404 | `NOT_FOUND_ERROR` | | `RateLimitException` | 429 | `RATE_LIMIT_ERROR` | | `ServerException` | 5xx | `SERVER_ERROR` | | `NetworkException` | — | `NETWORK_ERROR` | `ValidationException` exposes a `Details` property with field-level validation errors. `RateLimitException` exposes `RetryAfter` (seconds) and `ServerException` exposes `StatusCode`. ```csharp using Mobiscroll.Connect.Exceptions; try { var response = await client.Events.ListAsync(new EventListParams { Start = DateTime.UtcNow, End = DateTime.UtcNow.AddMonths(1) }); } catch (AuthenticationException) { // Token expired and refresh failed — re-authorize the user } catch (ValidationException ex) { // Invalid request parameters var details = ex.Details; } catch (RateLimitException ex) { var retryAfter = ex.RetryAfter; // seconds } catch (MobiscrollConnectException) { // Catch-all for any other SDK error } ``` ## API The client exposes resources that map directly to the API endpoints. ### Auth The `client.Auth` resource handles the OAuth authorization flow, including generating authorization URLs, exchanging codes for tokens, managing connection status, and disconnecting providers. To localize the Connect pages, pass an optional `Lng` (`en`, `es`, `fr`, `ar`) to `GenerateAuthUrl`, e.g. `new AuthorizeParams { UserId = ..., Lng = "es" }`. When omitted, the UI falls back to the browser's `Accept-Language` header, then English; Arabic renders right-to-left. ### Calendars The `client.Calendars` resource allows you to list available calendars from all connected providers (Google, Outlook, etc.). It corresponds to the `/calendars` endpoints. ### Events The `client.Events` resource provides methods to create, read, update, and delete calendar events across all connected accounts. It corresponds to the `/events` endpoints. --- ## Go SDK The Mobiscroll Connect Go SDK provides a convenient way to integrate Mobiscroll Connect in Go backend applications. It requires Go 1.22 or higher and is built on the standard library `net/http`, so it works in any Go service (plain HTTP servers, Gin, Echo, Fiber, gRPC gateways, etc.). ## Setup Add the module using `go get`: ```bash go get github.com/acidb/mobiscroll-connect-sdks/sdks/go@latest ``` Then import it under the short package name `mobiscroll`: ```go ``` ## Client Initialization To use the SDK, initialize `Client` with your client credentials. **Constructor:** `mobiscroll.NewClient(clientID, clientSecret, redirectURI string, opts ...ClientOption) *Client` Your Client ID obtained from the Mobiscroll Connect dashboard. Your Client Secret obtained from the Mobiscroll Connect dashboard. Your application's redirect URI that matches the one configured in the Mobiscroll Connect dashboard. Optional functional options: `WithBaseURL`, `WithTimeout`, `WithHTTPClient`, `WithTokensRefreshedCallback`. **Usage:** ```go "time" mobiscroll "github.com/acidb/mobiscroll-connect-sdks/sdks/go" ) client := mobiscroll.NewClient( "YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "YOUR_REDIRECT_URI", ) ``` For a custom base URL, HTTP timeout, custom `*http.Client`, or a token-refresh callback for persistence, pass options to the constructor: ```go client := mobiscroll.NewClient( "YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "YOUR_REDIRECT_URI", mobiscroll.WithBaseURL("https://connect.mobiscroll.com/api"), mobiscroll.WithTimeout(60*time.Second), mobiscroll.WithTokensRefreshedCallback(func(updated *mobiscroll.TokenResponse) { // Persist updated.AccessToken / updated.RefreshToken }), ) ``` ## Methods ### SetCredentials Stores a token pair the SDK will use on subsequent requests. Typically called after `Auth().GetToken` or when restoring credentials from persistent storage. **Method:** `client.SetCredentials(tokens *TokenResponse)` The token response returned by `client.Auth().GetToken(ctx, code)`. ### OnTokensRefreshed Registers a callback to be invoked whenever the SDK automatically refreshes the access token. Use this to persist the updated tokens so they survive future requests. Overrides any callback supplied via `WithTokensRefreshedCallback`. **Method:** `client.OnTokensRefreshed(cb func(*TokenResponse))` A function that receives the updated `TokenResponse` after a successful automatic token refresh. Pass `nil` to clear the callback. ### Credentials Returns the currently stored credentials, or `nil` if none. **Method:** `client.Credentials() *TokenResponse` **Returns:** `*TokenResponse` ## Token Refresh The Go SDK handles token refresh automatically. When any API call returns a `401 Unauthorized` response and the client has a `refresh_token` stored, the SDK silently exchanges it for a new access token and retries the original request — with no action required from your application. Concurrent calls that hit the same expired token deduplicate into a single refresh via `golang.org/x/sync/singleflight`. When the refresh succeeds, the SDK invokes your `OnTokensRefreshed` callback with the updated `*TokenResponse`. You must register this callback and persist the new tokens, otherwise they will be lost between process restarts. ```go client.OnTokensRefreshed(func(updated *mobiscroll.TokenResponse) { // Persist updated in your database or session store // so the new access_token and refresh_token survive future requests session.Set("access_token", updated.AccessToken) session.Set("refresh_token", updated.RefreshToken) }) ``` If the refresh token itself is invalid or has been revoked, the SDK returns an `*AuthenticationError` and the user must re-authorize. ## Error Handling All SDK errors satisfy the `mobiscroll.MobiscrollError` interface. Use `errors.As` to extract the concrete type. | Error type | HTTP Status | Extra field | |---|---|---| | `*AuthenticationError` | 401, 403 | — (returned after refresh + retry has been exhausted) | | `*ValidationError` | 400, 422 | `Details` (`json.RawMessage`) | | `*NotFoundError` | 404 | — | | `*RateLimitError` | 429 | `RetryAfter` (`int`, seconds) | | `*ServerError` | 5xx | `StatusCode` (`int`) | | `*NetworkError` | — | wraps the underlying transport error (`Unwrap`) | ```go "context" "errors" "log" "time" mobiscroll "github.com/acidb/mobiscroll-connect-sdks/sdks/go" ) ctx := context.Background() start := time.Now() end := start.AddDate(0, 1, 0) _, err := client.Events().List(ctx, &mobiscroll.EventListParams{ Start: &start, End: &end, }) if err != nil { var authErr *mobiscroll.AuthenticationError var valErr *mobiscroll.ValidationError var rlErr *mobiscroll.RateLimitError var mErr mobiscroll.MobiscrollError switch { case errors.As(err, &authErr): // Token expired and refresh failed — re-authorize the user case errors.As(err, &valErr): log.Printf("validation: %s", valErr.Details) case errors.As(err, &rlErr): time.Sleep(time.Duration(rlErr.RetryAfter) * time.Second) case errors.As(err, &mErr): // Catch-all for any other SDK error } } ``` ## API The client exposes resources that map directly to the API endpoints. ### Auth The `client.Auth()` resource handles the OAuth authorization flow, including generating authorization URLs, exchanging codes for tokens, managing connection status, and disconnecting providers. To localize the Connect pages, pass an optional `Lng` (`en`, `es`, `fr`, `ar`) to `GenerateAuthURL`, e.g. `&mobiscroll.AuthURLParams{ UserID: ..., Lng: "es" }`. When omitted, the UI falls back to the browser's `Accept-Language` header, then English; Arabic renders right-to-left. ### Calendars The `client.Calendars()` resource allows you to list available calendars from all connected providers (Google, Outlook, etc.). It corresponds to the `/calendars` endpoints. ### Events The `client.Events()` resource provides methods to create, read, update, and delete calendar events across all connected accounts. It corresponds to the `/events` endpoints. --- ## Java SDK The Mobiscroll Connect Java SDK provides a convenient way to integrate Mobiscroll Connect in Java backend applications. It targets Java 11 and is built on top of OkHttp 4 and Jackson, so it works in any modern JVM application (plain Java, Spring Boot, Quarkus, etc.). ## Setup Add the dependency using your build tool of choice: **Maven** ```xml com.mobiscroll connect-sdk 1.1.0 ``` **Gradle (Kotlin)** ```kotlin implementation("com.mobiscroll:connect-sdk:1.0.0") ``` **Gradle (Groovy)** ```groovy implementation 'com.mobiscroll:connect-sdk:1.0.0' ``` ## Client Initialization To use the SDK, initialize `MobiscrollConnectClient` with your client credentials. **Class:** `com.mobiscroll.connect.MobiscrollConnectClient` Constructor arguments. Your Client ID obtained from the Mobiscroll Connect dashboard. Your Client Secret obtained from the Mobiscroll Connect dashboard. Your application's redirect URI that matches the one configured in the Mobiscroll Connect dashboard. **Usage:** ```java MobiscrollConnectClient client = new MobiscrollConnectClient( "YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "YOUR_REDIRECT_URI" ); ``` For a custom base URL, HTTP timeout, OkHttp client, or a token-refresh callback for persistence, use the configuration builder: ```java MobiscrollConnectClient client = new MobiscrollConnectClient( MobiscrollConnectConfig.builder() .clientId("YOUR_CLIENT_ID") .clientSecret("YOUR_CLIENT_SECRET") .redirectUri("YOUR_REDIRECT_URI") .baseUrl("https://connect.mobiscroll.com/api") .timeout(Duration.ofSeconds(60)) .onTokensRefreshed(updatedTokens -> { // Persist updatedTokens.getAccessToken() / getRefreshToken() }) .build() ); ``` ## Methods ### setCredentials Sets the access token for the client. This is required before making any API calls that require authentication. **Method:** `client.setCredentials(tokens)` The token response object returned by `client.auth().getToken(code)`. ### onTokensRefreshed Registers a callback to be invoked whenever the SDK automatically refreshes the access token. Use this to persist the updated tokens so they survive future requests. **Method:** `client.onTokensRefreshed(callback)` A consumer that receives the updated `TokenResponse` after a successful automatic token refresh. ## Token Refresh The Java SDK handles token refresh automatically. When any API call returns a `401 Unauthorized` response and the client has a `refresh_token` stored, the SDK silently exchanges it for a new access token and retries the original request — with no action required from your application. Concurrent calls that hit the same expired token share a single refresh attempt. When the refresh succeeds, the SDK invokes your `onTokensRefreshed` callback with the updated `TokenResponse`. You must register this callback and persist the new tokens, otherwise they will be lost between requests. ```java client.onTokensRefreshed(updatedTokens -> { // Persist updatedTokens in your database or session store // so the new access_token and refresh_token survive future requests httpSession.setAttribute("access_token", updatedTokens.getAccessToken()); httpSession.setAttribute("refresh_token", updatedTokens.getRefreshToken()); }); ``` If the refresh token itself is invalid or has been revoked, the SDK throws an `AuthenticationException` and the user must re-authorize. ## Error Handling All SDK methods throw exceptions that extend `com.mobiscroll.connect.exceptions.MobiscrollConnectException`. You can catch the base class or any specific subclass. | Exception | HTTP Status | Extra | |---|---|---| | `AuthenticationException` | 401, 403 | — (raised after refresh + retry has been exhausted) | | `ValidationException` | 400, 422 | `getDetails()` (`JsonNode`) | | `NotFoundException` | 404 | — | | `RateLimitException` | 429 | `getRetryAfter()` (`Integer`, seconds) | | `ServerException` | 5xx | `getStatusCode()` (`int`) | | `NetworkException` | — | wraps the underlying `IOException` cause | ```java try { var response = client.events().list(EventListParams.builder() .start(OffsetDateTime.parse("2026-01-01T00:00:00Z")) .end(OffsetDateTime.parse("2026-02-01T00:00:00Z")) .build()); } catch (AuthenticationException e) { // Token expired and refresh failed — re-authorize the user } catch (ValidationException e) { // Invalid request parameters var details = e.getDetails(); } catch (RateLimitException e) { Integer retryAfter = e.getRetryAfter(); // seconds } catch (MobiscrollConnectException e) { // Catch-all for any other SDK error } ``` ## API The client exposes resources that map directly to the API endpoints. ### Auth The `client.auth()` resource handles the OAuth authorization flow, including generating authorization URLs, exchanging codes for tokens, managing connection status, and disconnecting providers. To localize the Connect pages, pass an optional `lng` (`en`, `es`, `fr`, `ar`) to `generateAuthUrl`, e.g. `AuthUrlParams.builder().userId(...).lng("es").build()`. When omitted, the UI falls back to the browser's `Accept-Language` header, then English; Arabic renders right-to-left. ### Calendars The `client.calendars()` resource allows you to list available calendars from all connected providers (Google, Outlook, etc.). It corresponds to the `/calendars` endpoints. ### Events The `client.events()` resource provides methods to create, read, update, and delete calendar events across all connected accounts. It corresponds to the `/events` endpoints. --- ## Node.js SDK The Mobiscroll Connect Node.js SDK provides a convenient way to interact with the Mobiscroll Connect API from your Node.js applications. ## Setup Install the package using your preferred package manager: **npm** ```bash npm install @mobiscroll/connect-sdk ``` **yarn** ```bash yarn add @mobiscroll/connect-sdk ``` **pnpm** ```bash pnpm add @mobiscroll/connect-sdk ``` ## Client Initialization To use the SDK, you need to initialize the `MobiscrollConnectClient` with your client credentials. **Class:** `MobiscrollConnectClient` Configuration object. Your Client ID obtained from the Mobiscroll Connect dashboard. Your Client Secret obtained from the Mobiscroll Connect dashboard. Your application's redirect URI that matches the one configured in the Mobiscroll Connect dashboard. **Usage:** ```typescript const client = new MobiscrollConnectClient({ clientId: "YOUR_CLIENT_ID", clientSecret: "YOUR_CLIENT_SECRET", redirectUri: "YOUR_REDIRECT_URI", }); ``` ## Methods ### setCredentials Sets the access token for the client. This is required before making any API calls that require authentication (which is most of them). **Method:** `client.setCredentials(tokens)` The tokens object received from the `auth.getToken` method. ### on Registers an event listener for client events. **Method:** `client.on(event, listener)` The name of the event to listen for. The callback function to execute when the event is triggered. ### getConfig Returns the current configuration of the client. **Method:** `client.getConfig()` **Returns:** `MobiscrollConnectConfig` ## Token Refresh The Node.js SDK handles token refresh automatically. When any API call returns a `401 Unauthorized` response and the client has a `refresh_token` stored, the SDK will silently exchange it for a new access token and retry the original request — with no action required from your application. When the refresh succeeds, the SDK emits a `tokens` event with the updated `TokenResponse`. You must listen for this event and persist the new tokens, otherwise they will be lost when the process exits. ```typescript client.on('tokens', (updatedTokens) => { // Persist updatedTokens in your database or session store // so the new access_token and refresh_token survive future requests }); ``` If the refresh token itself is invalid or has been revoked, the SDK throws an `AuthenticationError` and the user must re-authorize. ## Error Handling All SDK methods throw errors that extend `MobiscrollConnectError`. You can catch the base class or any specific subclass. | Error class | HTTP status | `error.code` | |---|---|---| | `AuthenticationError` | 401, 403 | `AUTHENTICATION_ERROR` | | `ValidationError` | 400, 422 | `VALIDATION_ERROR` | | `NotFoundError` | 404 | `NOT_FOUND_ERROR` | | `RateLimitError` | 429 | `RATE_LIMIT_ERROR` | | `ServerError` | 5xx | `SERVER_ERROR` | | `NetworkError` | — | `NETWORK_ERROR` | `ValidationError` exposes a `details` property with field-level validation errors. `RateLimitError` exposes `retryAfter` (seconds) and `ServerError` exposes `status` (the actual HTTP status code). ```typescript AuthenticationError, ValidationError, RateLimitError, MobiscrollConnectError, } from '@mobiscroll/connect-sdk'; try { const response = await client.events.list({ start: '2024-01-01' }); } catch (error) { if (error instanceof AuthenticationError) { // Token expired and refresh failed — re-authorize the user } else if (error instanceof ValidationError) { console.log(error.details); } else if (error instanceof RateLimitError) { console.log(`Retry after ${error.retryAfter}s`); } else if (error instanceof MobiscrollConnectError) { // Catch-all for any other SDK error } } ``` ## API The client exposes the following resources that map directly to the API endpoints. ### Auth The `client.auth` resource handles the OAuth authorization flow, including generating authorization URLs, exchanging codes for tokens, and managing connection status. It corresponds to the `/authorize`, `/token`, and `/connection-status` endpoints. To localize the Connect pages, pass an optional `lng` (`en`, `es`, `fr`, `ar`) to `generateAuthUrl`, e.g. `generateAuthUrl({ userId, lng: 'es' })`. When omitted, the UI falls back to the browser's `Accept-Language` header, then English; Arabic renders right-to-left. ### Calendars The `client.calendars` resource allows you to list available calendars from all connected providers (Google, Outlook, etc.). It corresponds to the `/calendars` endpoints. ### Events The `client.events` resource provides methods to create, read, update, and delete calendar events across all connected accounts. It corresponds to the `/events` endpoints. --- ## PHP SDK The Mobiscroll Connect PHP SDK provides a convenient way to integrate Mobiscroll Connect in PHP backends. ## Setup Install the package using Composer: **Composer** ```bash composer require mobiscroll/connect-php ``` ## Client Initialization To use the SDK, initialize `MobiscrollConnectClient` with your client credentials. **Class:** `Mobiscroll\Connect\MobiscrollConnectClient` Constructor arguments. Your Client ID obtained from the Mobiscroll Connect dashboard. Your Client Secret obtained from the Mobiscroll Connect dashboard. Your application's redirect URI that matches the one configured in the Mobiscroll Connect dashboard. **Usage:** ```php auth()->setCredentials(tokens)` The token response object returned by `client->auth()->getToken(code)`. ### onTokensRefreshed Registers a callback to be invoked whenever the SDK automatically refreshes the access token. Use this to persist the updated tokens so they survive future requests. **Method:** `client->onTokensRefreshed(callback)` A callable that receives the updated `TokenResponse` after a successful automatic token refresh. ## Token Refresh The PHP SDK handles token refresh automatically. When any API call returns a `401 Unauthorized` response and the client has a `refresh_token` stored, the SDK will silently exchange it for a new access token and retry the original request — with no action required from your application. When the refresh succeeds, the SDK invokes your `onTokensRefreshed` callback with the updated `TokenResponse`. You must register this callback and persist the new tokens, otherwise they will be lost between requests. ```php $client->onTokensRefreshed(function (TokenResponse $updatedTokens) { // Persist $updatedTokens in your database or session store // so the new access_token and refresh_token survive future requests $_SESSION['tokens'] = $updatedTokens->toArray(); }); ``` If the refresh token itself is invalid or has been revoked, the SDK throws an `AuthenticationError` and the user must re-authorize. ## Error Handling All SDK methods throw exceptions that extend `Mobiscroll\Connect\Exceptions\MobiscrollConnectException`. You can catch the base exception or any of the specific subclasses. | Exception | HTTP Status | `getCodeString()` | |---|---|---| | `AuthenticationError` | 401, 403 | `AUTHENTICATION_ERROR` | | `ValidationError` | 400, 422 | `VALIDATION_ERROR` | | `NotFoundError` | 404 | `NOT_FOUND_ERROR` | | `RateLimitError` | 429 | `RATE_LIMIT_ERROR` | | `ServerError` | 5xx | `SERVER_ERROR` | | `NetworkError` | — | `NETWORK_ERROR` | `ValidationError` exposes a `getDetails()` method that returns field-level validation errors. `RateLimitError` exposes `getRetryAfter()` (seconds) and `ServerError` exposes `getStatusCode()`. ```php use Mobiscroll\Connect\Exceptions\{ AuthenticationError, ValidationError, RateLimitError, MobiscrollConnectException, }; try { $events = $client->events()->list(['start' => new DateTime('2024-01-01')]); } catch (AuthenticationError $e) { // Token expired or invalid — refresh manually or re-authorize the user } catch (ValidationError $e) { // Invalid request parameters $details = $e->getDetails(); } catch (RateLimitError $e) { $retryAfter = $e->getRetryAfter(); // seconds } catch (MobiscrollConnectException $e) { // Catch-all for any other SDK error } ``` ## API The client exposes resources that map directly to the API endpoints. ### Auth The `client->auth()` resource handles the OAuth authorization flow, including generating authorization URLs, exchanging codes for tokens, managing connection status, and disconnecting providers. To localize the Connect pages, pass an optional `lng` (`en`, `es`, `fr`, `ar`) to `generateAuthUrl`, e.g. `generateAuthUrl(userId: ..., lng: 'es')`. When omitted, the UI falls back to the browser's `Accept-Language` header, then English; Arabic renders right-to-left. ### Calendars The `client->calendars()` resource allows you to list available calendars from all connected providers (Google, Outlook, etc.). It corresponds to the `/calendars` endpoints. ### Events The `client->events()` resource provides methods to create, read, update, and delete calendar events across all connected accounts. It corresponds to the `/events` endpoints. --- ## Python SDK The Mobiscroll Connect Python SDK provides a convenient way to integrate Mobiscroll Connect in Python backend applications. It supports both synchronous and asynchronous usage and requires Python 3.9 or higher. ## Setup Install the package using pip: **pip** ```bash pip install mobiscroll-connect-sdk ``` **Poetry** ```bash poetry add mobiscroll-connect-sdk ``` **uv** ```bash uv add mobiscroll-connect-sdk ``` ## Client Initialization To use the SDK, initialize `MobiscrollConnectClient` with your client credentials. **Class:** `mobiscroll_connect.MobiscrollConnectClient` Constructor arguments. Your Client ID obtained from the Mobiscroll Connect dashboard. Your Client Secret obtained from the Mobiscroll Connect dashboard. Your application's redirect URI that matches the one configured in the Mobiscroll Connect dashboard. **Usage:** ```python from mobiscroll_connect import MobiscrollConnectClient client = MobiscrollConnectClient( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", redirect_uri="YOUR_REDIRECT_URI", ) ``` Use the client as a context manager to ensure the HTTP connection pool is released when done: ```python with MobiscrollConnectClient( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", redirect_uri="YOUR_REDIRECT_URI", ) as client: calendars = client.calendars.list() ``` ## Methods ### set_credentials Sets the access token for the client. This is required before making any API calls that require authentication. **Method:** `client.set_credentials(tokens)` The token response object returned by `client.auth.get_token(code)`. ### on_tokens_refreshed Registers a callback to be invoked whenever the SDK automatically refreshes the access token. Use this to persist the updated tokens so they survive future requests. **Method:** `client.on_tokens_refreshed(callback)` A callable that receives the updated `TokenResponse` after a successful automatic token refresh. The async client also accepts an async callable. ## Token Refresh The Python SDK handles token refresh automatically. When any API call returns a `401 Unauthorized` response and the client has a `refresh_token` stored, the SDK will silently exchange it for a new access token and retry the original request — with no action required from your application. When the refresh succeeds, the SDK invokes your `on_tokens_refreshed` callback with the updated `TokenResponse`. You must register this callback and persist the new tokens, otherwise they will be lost between requests. ```python from mobiscroll_connect import TokenResponse def persist_tokens(tokens: TokenResponse) -> None: # Persist tokens in your database or session store # so the new access_token and refresh_token survive future requests session["access_token"] = tokens.access_token session["refresh_token"] = tokens.refresh_token client.on_tokens_refreshed(persist_tokens) ``` If the refresh token itself is invalid or has been revoked, the SDK raises an `AuthenticationError` and the user must re-authorize. ## Async Usage The SDK ships an async client with an identical API surface. Import it from the `aio` subpackage and use `async with` to manage the connection pool: ```python from mobiscroll_connect.aio import AsyncMobiscrollConnectClient async with AsyncMobiscrollConnectClient( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", redirect_uri="YOUR_REDIRECT_URI", ) as client: client.auth.set_credentials(tokens) calendars = await client.calendars.list() ``` All resource methods on the async client are coroutines. The `on_tokens_refreshed` callback may be sync or async — both are supported. ## Error Handling All SDK methods raise exceptions that extend `MobiscrollConnectError`. You can catch the base class or any specific subclass. | Exception | HTTP Status | `error.code` | |---|---|---| | `AuthenticationError` | 401, 403 | `AUTHENTICATION_ERROR` | | `ValidationError` | 400, 422 | `VALIDATION_ERROR` | | `NotFoundError` | 404 | `NOT_FOUND_ERROR` | | `RateLimitError` | 429 | `RATE_LIMIT_ERROR` | | `ServerError` | 5xx | `SERVER_ERROR` | | `NetworkError` | — | `NETWORK_ERROR` | `ValidationError` exposes a `details` property with field-level validation errors. `RateLimitError` exposes `retry_after` (seconds) and `ServerError` exposes `status_code`. ```python from mobiscroll_connect.exceptions import ( AuthenticationError, ValidationError, RateLimitError, MobiscrollConnectError, ) try: response = client.events.list(start="2024-01-01") except AuthenticationError: # Token expired and refresh failed — re-authorize the user pass except ValidationError as e: print(e.details) except RateLimitError as e: print(f"Retry after {e.retry_after}s") except MobiscrollConnectError as e: # Catch-all for any other SDK error pass ``` ## API The client exposes resources that map directly to the API endpoints. ### Auth The `client.auth` resource handles the OAuth authorization flow, including generating authorization URLs, exchanging codes for tokens, managing connection status, and disconnecting providers. To localize the Connect pages, pass an optional `lng` (`en`, `es`, `fr`, `ar`) to `generate_auth_url`, e.g. `generate_auth_url(user_id=..., lng='es')`. When omitted, the UI falls back to the browser's `Accept-Language` header, then English; Arabic renders right-to-left. ### Calendars The `client.calendars` resource allows you to list available calendars from all connected providers (Google, Outlook, etc.). It corresponds to the `/calendars` endpoints. ### Events The `client.events` resource provides methods to create, read, update, and delete calendar events across all connected accounts. It corresponds to the `/events` endpoints. --- ## REST API Integration Guide This guide demonstrates how to integrate Mobiscroll Connect using direct HTTP requests to the REST API endpoints. If you prefer a more streamlined approach, consider using the [Node.js SDK](/connect/node-sdk). ## Base URL All API requests should be made to: ``` https://connect.mobiscroll.com/api ``` ## Authentication Most API requests require an access token in the `Authorization` header: ``` Authorization: Bearer YOUR_ACCESS_TOKEN ``` To obtain an access token, follow the OAuth flow described in the [Auth API section](#auth) below. ## API The REST API exposes the following endpoints for integrating with Mobiscroll Connect. ### Auth The OAuth endpoints handle the authorization flow, including initiating user authorization, exchanging codes for access tokens, and managing connection status. Start here to authenticate users and obtain access tokens. **Example: Initiate Authorization** ```javascript const params = new URLSearchParams({ response_type: 'code', client_id: 'YOUR_CLIENT_ID', user_id: 'user-123', scope: 'read-write' // lng: 'es' // optional: localize the Connect pages (en | es | fr | ar) }); const authUrl = `https://connect.mobiscroll.com/api/oauth/authorize?${params.toString()}`; window.location.href = authUrl; ``` The Connect pages are localized in English (`en`), Spanish (`es`), French (`fr`), and Arabic (`ar`). Pass an optional `lng` parameter to choose the language; when omitted it falls back to the browser's `Accept-Language` header, then English. Arabic renders right-to-left. **Example: Exchange Code for Token** ```javascript const response = await fetch('https://connect.mobiscroll.com/api/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'authorization_code', code: 'AUTH_CODE_FROM_CALLBACK', client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' }) }); const { access_token, refresh_token } = await response.json(); // Store both tokens securely on the server side ``` **Example: Refresh Access Token** ```javascript const response = await fetch('https://connect.mobiscroll.com/api/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: 'STORED_REFRESH_TOKEN', client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' }).toString() }); const { access_token, refresh_token: newRefreshToken } = await response.json(); // Replace the stored refresh token with newRefreshToken ``` **Example: Revoke Tokens** ```javascript await fetch('https://connect.mobiscroll.com/api/oauth/revoke', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: access_token }) }); ``` ### Calendars The Calendars endpoints allow you to retrieve calendar lists from all connected providers (Google, Outlook, Apple, CalDAV). Use these to display available calendars to users. **Example: List Calendars** ```javascript const response = await fetch('https://connect.mobiscroll.com/api/calendars', { headers: { 'Authorization': `Bearer ${accessToken}` } }); const calendars = await response.json(); ``` ### Events The Events endpoints provide full CRUD operations for calendar events across all connected accounts. Create, read, update, and delete events with support for pagination, filtering, and recurring events. **Example: List Events** ```javascript const params = new URLSearchParams({ start: '2024-01-01T00:00:00Z', end: '2024-01-31T23:59:59Z', pageSize: '50' }); const response = await fetch( `https://connect.mobiscroll.com/api/events?${params.toString()}`, { headers: { 'Authorization': `Bearer ${accessToken}` } } ); const { events } = await response.json(); ``` **Example: Create Event** ```javascript const response = await fetch('https://connect.mobiscroll.com/api/event', { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ provider: 'google', calendarId: 'primary', title: 'Team Meeting', start: '2024-02-15T10:00:00Z', end: '2024-02-15T11:00:00Z' }) }); const event = await response.json(); ``` ## Best Practices 1. **Store tokens securely** - Keep access tokens and refresh tokens in secure, server-side storage; never expose them in client-side code 2. **Handle token expiration** - Access tokens expire after 1 hour; use the `POST /token` refresh flow to get a new one automatically before making API calls 3. **Rotate refresh tokens** - Always replace the stored refresh token with the new one returned after each refresh; the old token is immediately invalidated 4. **Revoke on logout** - Call `POST /revoke` when a user logs out to invalidate all active tokens 5. **Validate state parameter** - Always validate the `state` parameter in OAuth callbacks to prevent CSRF attacks 6. **Use HTTPS** - Always use HTTPS for API requests and callbacks 7. **Handle rate limits** - Implement exponential backoff for rate-limited requests --- ## Ruby SDK The Mobiscroll Connect Ruby SDK provides a convenient way to integrate Mobiscroll Connect in Ruby backend applications. It requires Ruby 3.1 or higher and is built on Faraday, so it works in any Ruby application (plain Rack, Sinatra, Rails, Hanami, etc.). ## Setup Install the gem with Bundler: **Bundler** Add to your `Gemfile`: ```ruby gem 'mobiscroll-connect', '~> 1.0' ``` Then run: ```bash bundle install ``` **gem** ```bash gem install mobiscroll-connect ``` Then require it: ```ruby require 'mobiscroll-connect' ``` ## Client Initialization To use the SDK, initialize `Mobiscroll::Connect::Client` with your client credentials. **Class:** `Mobiscroll::Connect::Client` Constructor keyword arguments. Your Client ID obtained from the Mobiscroll Connect dashboard. Your Client Secret obtained from the Mobiscroll Connect dashboard. Your application's redirect URI that matches the one configured in the Mobiscroll Connect dashboard. Override the API base URL. Defaults to `https://connect.mobiscroll.com/api`. HTTP timeout in seconds. Defaults to `30`. **Usage:** ```ruby require 'mobiscroll-connect' client = Mobiscroll::Connect::Client.new( client_id: ENV['MOBISCROLL_CLIENT_ID'], client_secret: ENV['MOBISCROLL_CLIENT_SECRET'], redirect_uri: 'https://yourapp.com/oauth/callback' ) ``` For a custom base URL or HTTP timeout: ```ruby client = Mobiscroll::Connect::Client.new( client_id: ENV['MOBISCROLL_CLIENT_ID'], client_secret: ENV['MOBISCROLL_CLIENT_SECRET'], redirect_uri: 'https://yourapp.com/oauth/callback', base_url: 'https://connect.mobiscroll.com/api', timeout: 60 ) ``` ## Methods ### set_credentials Stores a token pair the SDK will use on subsequent requests. Typically called after `client.auth.get_token` or when restoring credentials from persistent storage. **Method:** `client.set_credentials(tokens)` The token response returned by `client.auth.get_token(code)`. ### on_tokens_refreshed Registers a callback to be invoked whenever the SDK automatically refreshes the access token. Use this to persist the updated tokens so they survive future requests. **Method:** `client.on_tokens_refreshed { |tokens| ... }` A block that receives the updated `TokenResponse` after a successful automatic token refresh. ### credentials Returns the currently stored credentials, or `nil` if none. **Method:** `client.credentials` **Returns:** `TokenResponse` or `nil` ## Token Refresh The Ruby SDK handles token refresh automatically. When any API call returns a `401 Unauthorized` response and the client has a `refresh_token` stored, the SDK silently exchanges it for a new access token and retries the original request — with no action required from your application. Concurrent calls that hit the same expired token deduplicate into a single refresh via a `Monitor` and condition variable. When the refresh succeeds, the SDK invokes your `on_tokens_refreshed` callback with the updated `TokenResponse`. You must register this callback and persist the new tokens, otherwise they will be lost between process restarts. ```ruby client.on_tokens_refreshed do |tokens| # Persist tokens in your database or session store # so the new access_token and refresh_token survive future requests session[:access_token] = tokens.access_token session[:refresh_token] = tokens.refresh_token if tokens.refresh_token end ``` If the refresh token itself is invalid or has been revoked, the SDK raises `Mobiscroll::Connect::AuthenticationError` and the user must re-authorize. ## Error Handling All SDK errors are subclasses of `Mobiscroll::Connect::Error`. Rescue the specific subclass to handle each case: | Error class | HTTP Status | Extra attribute | |---|---|---| | `AuthenticationError` | 401, 403 | — (raised after refresh + retry has been exhausted) | | `ValidationError` | 400, 422 | `details` | | `NotFoundError` | 404 | — | | `RateLimitError` | 429 | `retry_after` (seconds) | | `ServerError` | 5xx | `status_code` | | `NetworkError` | — | `cause` (wraps the underlying Faraday error) | ```ruby begin client.events.list( start: '2025-10-01T00:00:00Z', end: '2025-10-31T23:59:59Z' ) rescue Mobiscroll::Connect::AuthenticationError # Token expired and refresh failed — re-authorize the user rescue Mobiscroll::Connect::ValidationError => e puts "Validation failed: #{e.details}" rescue Mobiscroll::Connect::RateLimitError => e sleep(e.retry_after) rescue Mobiscroll::Connect::Error => e # Catch-all for any other SDK error puts "SDK error: #{e.message} (#{e.code})" end ``` ## API The client exposes resources that map directly to the API endpoints. ### Auth The `client.auth` resource handles the OAuth authorization flow, including generating authorization URLs, exchanging codes for tokens, managing connection status, and disconnecting providers. To localize the Connect pages, pass an optional `lng` (`en`, `es`, `fr`, `ar`) to `generate_auth_url`, e.g. `generate_auth_url(user_id: ..., lng: 'es')`. When omitted, the UI falls back to the browser's `Accept-Language` header, then English; Arabic renders right-to-left. ### Calendars The `client.calendars` resource allows you to list available calendars from all connected providers (Google, Outlook, etc.). It corresponds to the `/calendars` endpoints. ### Events The `client.events` resource provides methods to create, read, update, and delete calendar events across all connected accounts. It corresponds to the `/events` endpoints. --- ## Branding The Mobiscroll Connect screens your users see while linking their calendars - the authorization (consent) screen, the provider login screens, and the error screen - can carry your own logo, color, and theme instead of the default Mobiscroll appearance. Branding is configured per application and applied automatically, so every user who connects through your app sees a consistent set of screens. ## Plan availability Branding is available on the **Scale** plan or higher. On lower plans the Connect screens always render the default Mobiscroll branding, and the **Branding** page in the dashboard shows an upgrade notice instead of the editor. Upgrading to the Scale plan or higher enables the branding fields and applies your saved values to the Connect screens. ## What you can customize Set these from the **Branding** menu of your application in the [Connect dashboard](https://app.mobiscroll.com/connect): - **Logo**: a square logo (recommended 128x128) shown on the Connect screens. Upload a file (PNG, JPG, WebP, or SVG - optimized and hosted for you) or paste your own hosted image URL. Defaults to the Mobiscroll logo. - **Dark mode logo** (optional): an alternate logo used when the dark theme is active. Falls back to the main logo when not set. - **Primary color**: a hex color (for example `#5DB4D8`) used for primary buttons and accents. Defaults to the Mobiscroll color. - **Theme**: the default color scheme - `auto`, `light`, or `dark`. See [Theme precedence](#theme-precedence) below. - **Hide footer**: hide the "Powered by Mobiscroll Connect" footer. All fields are optional. Anything left empty falls back to the Mobiscroll default. ![Branding page in the Connect dashboard](/connect/branding_dashboard.png) ## How branding is applied Branding is resolved from the `client_id` in the authorization request and injected into the Connect screens automatically. You do not pass any branding parameters and you do not change your integration - once branding is saved for an application, every authorization flow started with that `client_id` uses it. The **enable branding** switch on the Branding page controls whether your saved customization is actually applied - turning it off reverts the same screen to the default Mobiscroll appearance without discarding your settings:
Branding on
Branding off (default appearance)
## Theme precedence The **Theme** field sets the *default* color scheme for your screens: `auto` follows each user's device setting, while `light` and `dark` force a fixed scheme. The default is not the only input. The resolved scheme is chosen in this order, highest priority first: 1. The `theme` query parameter on the authorize URL (`?theme=light`, `?theme=dark`, or `?theme=auto`), sent by the calling application. 2. The application's **Theme** default, when it is set to `light` or `dark`. 3. The user's remembered choice on the screen - applied only when the application default is `auto`. 4. `auto` (the device setting), as the final fallback.
Light theme
Dark theme
## Display name and support contact The application's **display name** and its **support email / support URL** are shown on the same Connect screens, but they are not branding fields - they are configured on the **Application Settings** page and are available on every plan. - The display name identifies your app to the user on the authorization and connection-management screens, and on the "Back to" return button. - The support email or URL appears as a "Need help?" contact on the error screen. When neither is set, the error screen tells the user to contact their system administrator. See the [Application Setup Guide](/connect/application-setup) for where to set these. --- ## Localization The Mobiscroll Connect pages — where the user selects a calendar provider, signs in, and approves the consent screen — are localized. You choose the language by passing the `lng` parameter when you start the authorization flow, so the pages render in the same language as the application that sent the user there. Localization applies to the **user-facing Connect pages only**. API JSON responses and webhook payloads always remain in English. ## Supported languages | Code | Language | Direction | | :--- | :--- | :--- | | **`en`** | English | Left-to-right | | **`es`** | Spanish | Left-to-right | | **`fr`** | French | Left-to-right | | **`ar`** | Arabic | Right-to-left | ## Setting the language The language is selected with the `lng` query parameter on the authorize URL — the same URL that opens the Connect pages. This is the recommended way for a calling application to pass a locale. When you use one of the SDKs, pass `lng` to the auth-URL builder instead of constructing the query string by hand. **REST** ```bash GET /authorize?client_id=proj-123&user_id=user-456&response_type=code&lng=es ``` **Node.js SDK** ```typescript const authUrl = client.auth.generateAuthUrl({ userId: 'user-456', lng: 'es' }); ``` **Python SDK** ```python auth_url = client.auth.generate_auth_url(user_id='user-456', lng='es') ``` **PHP SDK** ```php $authUrl = $client->auth()->generateAuthUrl(userId: 'user-456', lng: 'es'); ``` **.NET SDK** ```csharp var authUrl = client.Auth.GenerateAuthUrl(new AuthorizeParams { UserId = "user-456", Lng = "es" }); ``` **Java SDK** ```java String authUrl = client.auth().generateAuthUrl( AuthUrlParams.builder().userId("user-456").lng("es").build()); ``` **Go SDK** ```go authURL := client.Auth().GenerateAuthURL(&mobiscroll.AuthURLParams{UserID: "user-456", Lng: "es"}) ``` **Ruby SDK** ```ruby auth_url = client.auth.generate_auth_url(user_id: 'user-456', lng: 'es') ``` See the [`lng` request parameter](../api/oauth.md#authorize-lng) on the [Authorize endpoint](../api/oauth.md#endpoint-authorize) for the full API reference, and your SDK's [integration guide](../integration/node-sdk.md) for the exact syntax. ## Language fallback If `lng` is not provided, Connect resolves the language in the following order: 1. The `lng` query parameter, when present. 2. The user's browser `Accept-Language` header. 3. English (`en`) as the final default. Existing integrations that do not pass `lng` continue to work without changes — users whose browser language is supported now see the Connect pages in that language automatically. To force English regardless of the browser, pass `lng=en` explicitly. ## Right-to-left (RTL) Arabic (`ar`) is the first right-to-left locale. When `ar` is active, the Connect UI direction switches to right-to-left automatically — no extra configuration is required. :::info What gets localized The localized pages are the provider selection (`authorize`), consent, Apple and CalDAV login, error, and session-lost pages. Anything your application consumes programmatically — API responses and webhook notifications — stays in English regardless of `lng`. ::: :::tip Related - [Authorize API reference](../api/oauth.md#endpoint-authorize) — the `lng` parameter and the full authorization request. - [Scopes & Permissions](./scopes.md) — the other core concept that shapes the authorization request. :::