# 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

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

## 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.mobiscrollconnect-sdk1.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.

## 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 onBranding 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 themeDark 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.
:::