# Add your own OAuth app (/docs/api-reference/scheduling-calendars/create-scheduling-oauth-app)

New consents in this environment use it, so the consent screen shows your brand. Connections made before it stay on coss.com's app until their host consents again. On the Scale plan and above. Secrets are never returned.

`POST https://api.coss.com/v1/scheduling/oauth_apps`

Authenticate with your API key as a bearer token (`Authorization: Bearer coss_test_…`). Responses are JSON with snake_case fields; errors are `{ error: { type, code, message, param?, issues?, request_id } }` (https://coss.com/docs/api-reference/errors.md).

## Request body

- One of (1):
  - `provider` ("google", required)
  - `client_id` (string, required, 1-500 characters): The app's client ID at the provider.
  - `client_secret` (string, required, 1-1000 characters): The app's client secret. Never returned.
  - `redirect_uri` (string, at most 2000 characters): A page on your own domain, registered with the provider, that forwards `code` and `state` to `coss_callback_url`. Google requires this for an external app, since it has to verify the redirect's domain as yours. Leave it out to use `coss_callback_url` directly.
  - `completion_url` (string, at most 2000 characters): Where the browser lands after a consent that didn't name a `return_url`, with `coss_calendar` set to `connected`, `denied` or `invalid`. Leave it out for a plain page saying what happened.
  - `secret_expires_at` (string, date-time): When the client secret expires. Owners and admins are warned 14 days before.
- One of (2):
  - `provider` ("microsoft", required)
  - `client_id` (string, required, 1-500 characters): The app's client ID at the provider.
  - `client_secret` (string, required, 1-1000 characters): The app's client secret. Never returned.
  - `tenant_id` (string): The Directory (tenant) ID, for an app that's only for your own tenant. Leave it out for a multi-tenant app.
  - `redirect_uri` (string, at most 2000 characters): A page on your own domain, registered with the provider, that forwards `code` and `state` to `coss_callback_url`. Google requires this for an external app, since it has to verify the redirect's domain as yours. Leave it out to use `coss_callback_url` directly.
  - `completion_url` (string, at most 2000 characters): Where the browser lands after a consent that didn't name a `return_url`, with `coss_calendar` set to `connected`, `denied` or `invalid`. Leave it out for a plain page saying what happened.
  - `secret_expires_at` (string, date-time): When the client secret expires. Owners and admins are warned 14 days before.
- One of (3):
  - `provider` ("zoom", required)
  - `client_id` (string, required, 1-500 characters): The app's client ID at the provider.
  - `client_secret` (string, required, 1-1000 characters): The app's client secret. Never returned.
  - `webhook_secret` (string, 1-1000 characters): The Secret Token of your Zoom app, which signs its events at `zoom_event_url`. Never returned.
  - `redirect_uri` (string, at most 2000 characters): A page on your own domain, registered with the provider, that forwards `code` and `state` to `coss_callback_url`. Google requires this for an external app, since it has to verify the redirect's domain as yours. Leave it out to use `coss_callback_url` directly.
  - `completion_url` (string, at most 2000 characters): Where the browser lands after a consent that didn't name a `return_url`, with `coss_calendar` set to `connected`, `denied` or `invalid`. Leave it out for a plain page saying what happened.
  - `secret_expires_at` (string, date-time): When the client secret expires. Owners and admins are warned 14 days before.

## Example request

TypeScript (the `coss` package):

```ts
import { Coss } from "coss";

const coss = new Coss({
  apiKey: process.env.COSS_API_KEY,
  baseUrl: "https://api.coss.com",
});

const { data, error } = await coss.scheduling.oauthApps.create({
  provider: "google",
  client_id: "1234567890-abc.apps.googleusercontent.com",
  client_secret: "GOCSPX-abcdefghijklmnopqrstuvwxyz",
  redirect_uri: "https://example.com/oauth/google",
  completion_url: "https://example.com/calendar-connected",
});
```

HTTP:

```sh
curl -X POST "https://api.coss.com/v1/scheduling/oauth_apps" \
  -H "Authorization: Bearer $COSS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "provider": "google",
  "client_id": "1234567890-abc.apps.googleusercontent.com",
  "client_secret": "GOCSPX-abcdefghijklmnopqrstuvwxyz",
  "redirect_uri": "https://example.com/oauth/google",
  "completion_url": "https://example.com/calendar-connected"
}'
```

## Response 201

The app.

- `object` ("scheduling.oauth_app", required)
- `id` (string, required)
- `provider` ("google" | "microsoft" | "zoom", required)
- `client_id` (string, required)
- `tenant_id` (string | null, required)
- `redirect_uri` (string | null, required)
- `coss_callback_url` (string, required): Register this with the provider if `redirect_uri` isn't set. If it is, that page forwards `code` and `state` here.
- `completion_url` (string | null, required)
- `zoom_event_url` (string | null, required): Zoom only: the Event notification endpoint URL for your app's deauthorization events.
- `scopes` (string[], required): What to enable on your app at the provider.
- `status` ("active" | "failing", required): `failing`: the provider refused the app's credentials. New consents are refused, and calendars connected through it can't be read until they work again.
- `last_error_code` (string | null, required)
- `last_error_at` (string | null, required)
- `secret_expires_at` (string | null, required)
- `verified_at` (string | null, required): When a consent first completed through this app, which proves its secret.
- `connections` (integer, required): Calendar connections using this app. It can't be deleted while there are any.
- `created_at` (string, required)
- `updated_at` (string, required)

Example:

```json
{
  "object": "scheduling.oauth_app",
  "id": "calapp_01kd2m9a0b1c2d3e4f5g6h7j8m",
  "provider": "google",
  "client_id": "1234567890-abc.apps.googleusercontent.com",
  "tenant_id": null,
  "redirect_uri": "https://example.com/oauth/google",
  "coss_callback_url": "https://api.coss.com/scheduling/oauth/google/callback",
  "completion_url": "https://example.com/calendar-connected",
  "zoom_event_url": null,
  "scopes": [
    "https://www.googleapis.com/auth/calendar.events.freebusy"
  ],
  "status": "active",
  "last_error_code": null,
  "last_error_at": null,
  "secret_expires_at": null,
  "verified_at": "2027-01-01T10:00:00.000Z",
  "connections": 12,
  "created_at": "2027-01-01T10:00:00.000Z",
  "updated_at": "2027-01-01T10:00:00.000Z"
}
```

## Errors

- `400`: The body is not valid JSON (`invalid_json`). Also when the `Coss-Version` header names an unknown API version (`invalid_api_version`).
- `401`: The API key is missing, invalid, revoked or expired (`authentication_error`).
- `402`: The plan doesn't include this (`plan_feature_unavailable`).
- `403`: The API key lacks the permission this endpoint needs (`permission_error`), or its organization is suspended (`organization_suspended`).
- `415`: The body is not sent as `application/json` (`unsupported_media_type`).
- `422`: The app isn't valid (`invalid_oauth_app`), or the provider refused its client ID and secret (`oauth_app_refused`), and nothing was stored.
- `429`: Too many requests with this API key (`rate_limited`), or too many requests with invalid keys from this address (`too_many_failed_authentications`). Retry after `Retry-After` seconds. Successful responses carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds until the limit is fully restored).
- `500`: Unexpected server error (`api_error` / `internal_error`).
- `503`: The API key could not be verified right now; retry (`service_unavailable`).