# OAuth

Authenticate your users with Emailable using OAuth 2.

## Overview

These endpoints are used to authenticate to OAuth Apps you've created in
Emailable. OAuth apps allow you to authenticate your users with Emailable and
make API requests on their behalf.

Emailable supports two OAuth 2 grant flows:

* The [Authorization Code](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1)
  flow for browser-based integrations that can receive a redirect.
* The [Device Authorization Grant](https://datatracker.ietf.org/doc/html/rfc8628)
  flow for CLIs, CI environments, SSH sessions, and other input-constrained
  clients that cannot easily open a browser or receive a redirect.

Both flows issue standard Bearer access tokens and refresh tokens. Once issued,
the resulting tokens are interchangeable and API requests authenticate the same
way regardless of which flow created them.

The device authorization grant must be enabled on each OAuth App from the app's
settings page. The Authorization Code grant is enabled by default, provided the
app is configured with a redirect URI.

Errors are handled as specified by the
[OAuth 2 spec](https://datatracker.ietf.org/doc/html/rfc6749).

## Authorize

To initiate the OAuth flow, your application should link to the authorize
The Authorize endpoint starts the Authorization Code grant flow. To initiate
this flow, your application should link to the authorize endpoint with the
necessary parameters.

If any parameters are incorrect or if a required parameter is missing, the
rendered page will not prompt the user to authorize. Instead, it will display
an error page containing an error description.

### Endpoint

`GET https://app.emailable.com/oauth/authorize`

### Parameters

Parameter               | Required    | Description
----------------------- | ----------- | -----------
`response_type`         | Yes         | The string `code`
`client_id`             | Yes         | Your OAuth App's client ID
`redirect_uri`          | Yes         | Your OAuth App's redirect URI
`state`                 | No          | An arbitrary string included in the request that the server returns unchanged, allowing the client to verify the response and prevent CSRF.
`code_challenge`        | No          | A [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) code challenge derived from a verifier you keep secret on the client. Recommended for public clients.
`code_challenge_method` | Conditional | The PKCE transformation used to produce the challenge. Required whenever `code_challenge` is sent. Either `S256` (recommended) or `plain`.

For manual testing during development, you can use the special redirect URI
`urn:ietf:wg:oauth:2.0:oob`. After approval, the authorization code is
displayed in the browser instead of being sent to a redirect URI, which is
useful when running a CLI or curl-based test.

### Response

If the request succeeds, the user will be redirected to your redirect URI with
an authorization code. You will also receive a state string if you provided
one. The authorization code expires 10 minutes after it is issued.

If you sent a `code_challenge` in the authorization request, you must include
the matching `code_verifier` when [exchanging the code for an access token](#access-token).

Attribute   | Description
----------- | -----------
`code`      | The authorization code generated by the server.
`state`     | The state string, if one was provided in the request.

## Device Code

```shell
curl -X POST "https://app.emailable.com/oauth/device/code" \
-d "client_id=example-client-id"
```

```ruby
# Not supported by the client library. See the curl example.
```

```javascript
// Not supported by the client library. See the curl example.
```

```python
# Not supported by the client library. See the curl example.
```

> Example JSON response:

```json
{
  "device_code": "example-device-code",
  "user_code": "ABCD-WXYZ",
  "verification_uri": "https://app.emailable.com/oauth/device",
  "verification_uri_complete": "https://app.emailable.com/oauth/device?user_code=ABCD-WXYZ",
  "expires_in": 900,
  "interval": 5
}
```

This endpoint starts the device authorization grant flow. It returns a
`device_code` that your client uses to poll for an access token, and a
`user_code` that the user enters in their browser to authorize the request.

Your OAuth App must have the device authorization grant enabled to use this
endpoint. If it is not enabled, the request fails with `invalid_client`.

### Endpoint

`POST https://app.emailable.com/oauth/device/code`

### Parameters

Parameter   | Required | Description
----------- | -------- | -----------
`client_id` | Yes      | Your OAuth App's client ID

### Response

If the request succeeds, you will receive a JSON response containing the device
code and the information needed to prompt the user to authorize the request.

Attribute                   | Description
--------------------------- | -----------
`device_code`               | The device verification code, used by the client to poll the token endpoint.
`user_code`                 | The short, human-readable code that the user enters in their browser.
`verification_uri`          | The URL where the user enters the `user_code` to authorize the request.
`verification_uri_complete` | The verification URL with the `user_code` pre-filled. Display this as a QR code or clickable link when possible.
`expires_in`                | An integer representing the number of seconds before the `device_code` and `user_code` expire.
`interval`                  | The minimum number of seconds that the client should wait between polls to the token endpoint.

### Prompting the User

After receiving a device code response, direct the user to the verification
URL to authorize the request. There are two URLs in the response:

* `verification_uri_complete` is the preferred URL to present to the user. It
  pre-fills the `user_code`, so the user only needs to sign in and approve.
  Render it as a clickable link or QR code when possible.
* `verification_uri` is the fallback for clients that cannot present a
  clickable link, such as terminals on systems without a URL launcher. Display
  it alongside the `user_code` so the user can open the URL and type the code
  manually.

Always display the `user_code` to the user as well, so they can confirm the
code on the consent screen matches what your client showed them. This protects
against a malicious device tricking the user into approving a different
request.

While the user authorizes the request, your client polls the
[Access Token](#access-token) endpoint to obtain an access token.

## Access Token

This endpoint exchanges an authorization grant for an access token and refresh
token. The same endpoint serves both flows; the request differs only in the
`grant_type` value and the grant credential you pass.

### Endpoint

`POST https://app.emailable.com/oauth/token`

### Authorization Code Grant

```shell
curl -X POST "https://app.emailable.com/oauth/token" \
-d "grant_type=authorization_code" \
-d "code=example-code" \
-d "client_id=example-client-id" \
-d "client_secret=example-client-secret" \
-d "redirect_uri=https://example.com/oauth/callback"
```

```ruby
# Not supported by the client library. See the curl example.
```

```javascript
// Not supported by the client library. See the curl example.
```

```python
# Not supported by the client library. See the curl example.
```

Use this exchange after a successful [Authorize](#authorize) request. The
authorization code is single-use and expires 10 minutes after it is issued.

Parameter       | Required    | Description
--------------- | ----------- | -----------
`grant_type`    | Yes         | The string `authorization_code`
`code`          | Yes         | The authorization code received from the authorization request
`client_id`     | Yes         | Your OAuth App's client ID
`client_secret` | Yes         | Your OAuth App's client secret
`redirect_uri`  | Yes         | Your OAuth App's redirect URI
`code_verifier` | Conditional | The PKCE verifier. Required if a `code_challenge` was sent in the authorization request.

### Device Authorization Grant

Use this exchange when polling for a [Device Code](#device-code) issued for
the device authorization flow. Send only the parameters listed below. Device
authorization grant token requests do not use a `client_secret`, even when the
OAuth App also has one for authorization-code requests.

Your client should poll no faster than the `interval` returned by the Device
Code endpoint.

Parameter     | Required | Description
------------- | -------- | -----------
`grant_type`  | Yes      | The string `urn:ietf:params:oauth:grant-type:device_code`
`device_code` | Yes      | The device code returned by the [Device Code](#device-code) endpoint
`client_id`   | Yes      | Your OAuth App's client ID

While the user has not yet approved or denied the request, the endpoint
returns OAuth errors that your client should expect and handle as part of the
polling loop. See [Errors](#errors) for the full list and the action to take
for each.

### Response

> Example JSON response:

```json
{
  "access_token": "example-access-token",
  "token_type": "Bearer",
  "expires_in": 86400,
  "refresh_token": "example-refresh-token",
  "scope": "all",
  "created_at": 1743861442
}
```

If the request succeeds, you will receive a JSON response containing an access
token and a refresh token. The access token is what you will use to
authenticate requests to the Emailable API, as described in
[Authentication](/docs/api/authentication/).

Tokens issued by the two grants are interchangeable. The response shape is the
same regardless of which `grant_type` you used.

Attribute       | Description
--------------- | -----------
`access_token`  | The access token used to authenticate the user
`refresh_token` | The refresh token that can be used to request new access tokens
`token_type`    | The string `Bearer`
`expires_in`    | An integer representing the number of seconds before the access token expires.
`scope`         | The string `all`. We do not currently implement scopes, so you can disregard this parameter.
`created_at`    | An integer timestamp indicating when the access token was created.

## Refresh Token

```shell
curl -X POST "https://app.emailable.com/oauth/token" \
-d "grant_type=refresh_token" \
-d "refresh_token=example-refresh-token" \
-d "client_id=example-client-id" \
-d "client_secret=example-client-secret"
```

```ruby
# Not supported by the client library. See the curl example.
```

```javascript
// Not supported by the client library. See the curl example.
```

```python
# Not supported by the client library. See the curl example.
```

> Example JSON response:

```json
{
  "access_token": "example-access-token",
  "token_type": "Bearer",
  "expires_in": 86400,
  "refresh_token": "example-refresh-token",
  "scope": "all",
  "created_at": 1746745624
}
```

This endpoint is used to request a new access token using a refresh token and
your OAuth client credentials.

### Endpoint

`POST https://app.emailable.com/oauth/token`

### Parameters

Parameter       | Required | Description
--------------- | -------- | -----------
`grant_type`    | Yes      | The string `refresh_token`
`refresh_token` | Yes      | The refresh token issued to the client
`client_id`     | Yes      | Your OAuth App's client ID
`client_secret` | Yes      | Your OAuth App's client secret

### Response

If the request succeeds, you will receive a JSON response containing a new
access token and a new refresh token.

Attribute       | Description
--------------- | -----------
`access_token`  | The access token used to authenticate the user
`refresh_token` | The refresh token that can be used to request new access tokens
`token_type`    | The string `Bearer`
`expires_in`    | An integer representing the number of seconds before the access token expires.
`scope`         | The string `all`. We do not currently implement scopes, so you can disregard this parameter.
`created_at`    | An integer timestamp indicating when the access token was created.

## Revoke

```shell
curl -X POST "https://app.emailable.com/oauth/revoke" \
-d "token=example-access-token" \
-d "client_id=example-client-id" \
-d "client_secret=example-client-secret"
```

```ruby
# Not supported by the client library. See the curl example.
```

```javascript
// Not supported by the client library. See the curl example.
```

```python
# Not supported by the client library. See the curl example.
```

> Example JSON response:

```json
{}
```

You can use this endpoint to revoke an access token, making it so it can no
longer be used to authenticate requests.

### Endpoint

`POST https://app.emailable.com/oauth/revoke`

### Parameters

Parameter       | Required | Description
--------------- | -------- | -----------
`token`         | Yes      | The access token to revoke
`client_id`     | Yes      | Your OAuth App's client ID
`client_secret` | Yes      | Your OAuth App's client secret

### Response

If the request succeeds, you will receive an empty JSON response.

## Errors

OAuth error responses depend on the endpoint. The token endpoint, including
Access Token and Refresh Token requests, the Device Code endpoint, and the
Revoke endpoint return JSON with `error` and `error_description` fields, as
specified by the OAuth 2 and Device Authorization Grant RFCs.

The Authorize endpoint is browser-facing. If a request is invalid before a user
can approve it, Emailable renders an error page with a description. If the user
denies a valid authorization code request, Emailable redirects back to the
registered redirect URI with an OAuth `access_denied` error response.

Error                    | Applies to              | Description
------------------------ | ----------------------- | -----------
`invalid_request`        | Both                    | The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.
`invalid_redirect_uri`   | Authorization code      | The supplied redirect URI is malformed or doesn't match the redirect URI registered for the client.
`invalid_client`         | Both                    | Client authentication failed, the client is unknown, or the requested grant flow is not enabled for the client.
`invalid_grant`          | Both                    | The provided authorization grant is invalid, expired, revoked, does not match the redirect URI, or was issued to another client.
`invalid_scope`          | Both                    | The requested scope is invalid, unknown, or malformed.
`unauthorized_client`    | Both                    | The client is not authorized to use the requested grant type.
`unsupported_grant_type` | Both                    | The grant type is not supported by the authorization server.
`access_denied`          | Both                    | The user denied the authorization request. For the device flow, stop polling.
`authorization_pending`  | Device authorization    | The user has not yet approved or denied the request. Continue polling at the current interval.
`slow_down`              | Device authorization    | Your client is polling too quickly. Increase your polling interval by 5 seconds and continue polling.
`expired_token`          | Device authorization    | The `device_code` has expired before the user approved the request. Stop polling and request a new device code.

