> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xquik.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth 2.1 for MCP & X API agent authorization

> Authorize MCP clients and agents with OAuth 2.1, PKCE, claimed service identities, interactive refresh tokens, discovery metadata, and revocation examples.

<blockquote className="agent-llms-directive">
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

Xquik supports 2 MCP authorization paths. Interactive clients use
[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1)
Authorization Code with S256 PKCE. Agents can use claimed `service_auth`
registration. Both paths require the user to sign in and approve access.

<Info>
  Prefer OAuth for compatible remote MCP clients. Check the [client compatibility
  matrix](/mcp/overview#client-compatibility) before using an API key fallback.
</Info>

* **Client ID Metadata Documents (CIMD).** Recommended for modern clients. The client's HTTPS metadata URL becomes its `client_id`. It needs no registration request or client secret.
* **Dynamic Client Registration (DCR).** Fallback for clients without CIMD. The client registers once at `/api/oauth/register`.

## Discovery

MCP clients configure endpoints from these standard discovery documents.

### Authorization server metadata

```bash theme={null}
curl https://xquik.com/.well-known/oauth-authorization-server
```

```json Response theme={null}
{
  "issuer": "https://xquik.com",
  "authorization_endpoint": "https://xquik.com/api/oauth/authorize",
  "token_endpoint": "https://xquik.com/api/oauth/token",
  "registration_endpoint": "https://xquik.com/api/oauth/register",
  "scopes_supported": ["mcp:read", "mcp:tools"],
  "response_types_supported": ["code"],
  "grant_types_supported": [
    "authorization_code",
    "refresh_token",
    "urn:workos:agent-auth:grant-type:claim",
    "urn:ietf:params:oauth:grant-type:jwt-bearer"
  ],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_post", "private_key_jwt"],
  "token_endpoint_auth_signing_alg_values_supported": ["RS256"],
  "revocation_endpoint": "https://xquik.com/api/oauth/revoke",
  "revocation_endpoint_auth_methods_supported": ["none", "client_secret_post", "private_key_jwt"],
  "revocation_endpoint_auth_signing_alg_values_supported": ["RS256"],
  "protected_resources": [
    "https://xquik.com",
    "https://xquik.com/en",
    "https://xquik.com/es",
    "https://xquik.com/tr",
    "https://xquik.com/mcp"
  ],
  "service_documentation": "https://docs.xquik.com/oauth/overview",
  "authorization_response_iss_parameter_supported": true,
  "response_modes_supported": ["query"],
  "client_id_metadata_document_supported": true,
  "agent_auth": {
    "claim_endpoint": "https://xquik.com/agent/identity/claim",
    "claim_uri": "https://xquik.com/agent/identity/claim",
    "identity_endpoint": "https://xquik.com/agent/identity",
    "identity_types_supported": ["service_auth"],
    "register_uri": "https://xquik.com/agent/identity",
    "revocation_uri": "https://xquik.com/api/oauth/revoke",
    "skill": "https://xquik.com/auth.md"
  }
}
```

The endpoint fields follow the current Auth.md profile. The URI fields are
working compatibility aliases. Generic OAuth clients may ignore this object.

### Protected resource metadata

```bash theme={null}
curl https://xquik.com/.well-known/oauth-protected-resource/mcp
```

```json Response theme={null}
{
  "resource": "https://xquik.com/mcp",
  "resource_name": "Xquik MCP Server",
  "authorization_servers": ["https://xquik.com"],
  "bearer_methods_supported": ["header"],
  "resource_documentation": "https://docs.xquik.com/mcp/overview",
  "scopes_supported": ["mcp:read", "mcp:tools"]
}
```

Localized site resources publish the same RFC 9728 contract under
`/.well-known/oauth-protected-resource/en`, `/es`, and `/tr`. Request
`https://xquik.com` for direct REST calls. Use `/mcp` tokens only with MCP.

The MCP endpoint also returns this metadata URL in its unauthenticated
`WWW-Authenticate` challenge:

```text theme={null}
Bearer realm="OAuth", resource_metadata="https://xquik.com/.well-known/oauth-protected-resource/mcp", scope="mcp:tools"
```

The ChatGPT app's `https://xquik.com/mcp?app=chatgpt` asks for `mcp:read`,
and its `resource_metadata` URL, ending in `?app=chatgpt`, lists only that.

## Client registration choices

### Client ID metadata document

Publish JSON at a stable HTTPS URL with an explicit path. A trailing `/` is
sufficient. Use that exact URL as the `client_id`:

```json theme={null}
{
  "client_id": "https://client.example/oauth/client.json",
  "client_name": "Example MCP Client",
  "redirect_uris": ["https://client.example/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}
```

The HTTPS `client_id` must include an explicit path. Exclude user information,
queries, fragments, and dot segments. Xquik requires a direct `200` JSON
response. Redirects fail. Keep the document within 5 KiB. Repeat the exact
`client_id`. Include `client_name`, `redirect_uris`, and
`token_endpoint_auth_method: "none"`. List every allowed redirect URI.

### Dynamic client registration

Clients without CIMD may register at `POST /api/oauth/register`. DCR supports
public clients with `none` and confidential clients with
`client_secret_post`. The manual flow below uses a DCR-issued UUID so each step
can show a concrete `client_id`.

## Manual implementation

<Steps>
  <Step title="Register a DCR client">
    Skip this step when the client uses CIMD. For DCR, register once to get a
    UUID `client_id`.

    ```bash theme={null}
    curl -X POST https://xquik.com/api/oauth/register \
      -H "Content-Type: application/json" \
      -d '{
        "client_name": "My MCP Client",
        "redirect_uris": ["https://myapp.example.com/callback"]
      }'
    ```

    ```json Response theme={null}
    {
      "client_id": "550e8400-e29b-41d4-a716-446655440000",
      "client_name": "My MCP Client",
      "redirect_uris": ["https://myapp.example.com/callback"],
      "grant_types": ["authorization_code", "refresh_token"],
      "response_types": ["code"],
      "token_endpoint_auth_method": "none"
    }
    ```

    **Redirect URI requirements.**

    * Production web callbacks: HTTPS only
    * Development: HTTP loopback callbacks may use `localhost`, `127.0.0.1`, or `::1`
    * HTTPS and supported native callbacks require exact matching
    * HTTP loopback callbacks may change only the ephemeral port. Scheme, host, path, and query must match
    * Xquik does not support wildcards or subpath matching

    **Client types.**

    * **Public** (`token_endpoint_auth_method: "none"`): Default. No client secret. Browser apps and MCP clients use it.
    * **Confidential** (`token_endpoint_auth_method: "client_secret_post"`): Returns a `client_secret` in the registration response. Server-side apps use it.

    <Warning>
      If you register a confidential client, the registration response returns the `client_secret` once. Store it in a secret manager.
    </Warning>
  </Step>

  <Step title="Generate PKCE parameters">
    Generate a cryptographically random `code_verifier` and derive the `code_challenge` from it.

    <CodeGroup>
      ```javascript Node.js theme={null}
      import { randomBytes, createHash } from "node:crypto";

      const codeVerifier = randomBytes(32).toString("hex");
      const codeChallenge = createHash("sha256")
        .update(codeVerifier)
        .digest("base64url");
      ```

      ```python Python theme={null}
      import base64
      import hashlib
      import secrets

      code_verifier = secrets.token_hex(32)
      code_challenge = base64.urlsafe_b64encode(
          hashlib.sha256(code_verifier.encode()).digest()
      ).rstrip(b"=").decode()
      ```

      ```go Go theme={null}
      package main

      import (
      	"crypto/rand"
      	"crypto/sha256"
      	"encoding/base64"
      	"encoding/hex"
      )

      func generatePKCE() (string, string) {
      	b := make([]byte, 32)
      	rand.Read(b)
      	codeVerifier := hex.EncodeToString(b)

      	hash := sha256.Sum256([]byte(codeVerifier))
      	codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:])

      	return codeVerifier, codeChallenge
      }
      ```
    </CodeGroup>

    <Warning>
      Use at least 32 cryptographically random bytes (64 hex characters) for the `code_verifier`. Store the verifier securely on the client. You need it for the token exchange in step 5.
    </Warning>
  </Step>

  <Step title="Redirect to authorization">
    Redirect the user to the Xquik authorization endpoint with the required query parameters.

    ```text theme={null}
    GET https://xquik.com/api/oauth/authorize
      ?response_type=code
      &client_id=550e8400-e29b-41d4-a716-446655440000
      &redirect_uri=https://myapp.example.com/callback
      &code_challenge=a1b2c3d4e5f6...
      &code_challenge_method=S256
      &scope=mcp:tools
      &state=random_csrf_token
      &resource=https://xquik.com/mcp
    ```

    **Required parameters.**

    | Parameter | Value |
    | - | - |
    | `response_type` | `code` |
    | `client_id` | UUID from client registration |
    | `redirect_uri` | Must match a registered URI exactly |
    | `code_challenge` | Base64url-encoded SHA256 digest of the `code_verifier` |
    | `code_challenge_method` | `S256` |
    | `resource` | `https://xquik.com/mcp` for MCP or `https://xquik.com` for REST |

    **Optional parameters.**

    | Parameter | Default | Description |
    | - | - | - |
    | `scope` | `mcp:tools` | `mcp:tools`, `mcp:read`, or both, separated by a space |
    | `state` | | Opaque value for CSRF protection |

    These examples use `/mcp`. Direct REST clients use `https://xquik.com`.
    Send the same value in every authorization, token, and refresh request.

    The user sees a login page (Google OAuth or email magic link) followed by a consent screen. After approval, Xquik redirects back to your `redirect_uri`.
  </Step>

  <Step title="Receive the authorization code">
    After the user approves, Xquik redirects to your `redirect_uri` with a `code` parameter:

    ```text theme={null}
    https://myapp.example.com/callback?code=AUTH_CODE_HERE&state=random_csrf_token&iss=https%3A%2F%2Fxquik.com
    ```

    Verify `state` against the value sent in step 3. Verify `iss` exactly equals
    the discovered issuer, `https://xquik.com`, before exchanging the code. The
    authorization code expires in 60 seconds and is single-use.
  </Step>

  <Step title="Exchange code for tokens">
    Exchange the authorization code and your `code_verifier` for an access token and refresh token.

    ```bash theme={null}
    curl -X POST https://xquik.com/api/oauth/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "grant_type=authorization_code\
    &code=AUTH_CODE_HERE\
    &code_verifier=YOUR_CODE_VERIFIER\
    &client_id=550e8400-e29b-41d4-a716-446655440000\
    &redirect_uri=https://myapp.example.com/callback\
    &resource=https://xquik.com/mcp"
    ```

    ```json Response theme={null}
    {
      "access_token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
      "token_type": "Bearer",
      "expires_in": 3600,
      "refresh_token": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1",
      "scope": "mcp:tools"
    }
    ```
  </Step>

  <Step title="Use the access token">
    Pass the token as a Bearer credential to its selected resource. This example calls MCP.

    ```bash theme={null}
    curl https://xquik.com/mcp \
      -H "Authorization: Bearer ${XQUIK_ACCESS_TOKEN}"
    ```
  </Step>
</Steps>

## Token lifetimes

| Token | Lifetime | Notes |
| - | - | - |
| Access token | 1 hour | Use the refresh token to get a new one |
| Refresh token | 30 days | Single-use. Each refresh issues a new pair |
| Authorization code | 60 seconds | Single-use. Exchange immediately |
| Claim token | 24 hours | Registers one claimed service identity |
| User code | 10 minutes | Refresh it while the claim token remains active |
| Identity assertion | 1 hour | Reuse it to mint agent access tokens |

## Claimed agent registration

Use `service_auth` when an agent knows the user's verified email. Xquik never
issues anonymous agent credentials.

<Steps>
  <Step title="Register the service identity">
    Send the verified email as `login_hint`.

    ```bash theme={null}
    curl -X POST https://xquik.com/agent/identity \
      -H "Content-Type: application/json" \
      -d '{"type":"service_auth","login_hint":"user@example.com"}'
    ```

    The response includes `claim_token`, `registration_id`, and a `claim`
    object. Show `claim.verification_uri` and `claim.user_code` to the user.
    Keep every token secret.
  </Step>

  <Step title="Complete the claim">
    The user opens `claim.verification_uri`, signs in, and enters the code.
    The signed-in email must match `login_hint`.

    If the code expires, refresh it before the claim token expires:

    ```bash theme={null}
    curl -X POST https://xquik.com/agent/identity/claim \
      -H "Content-Type: application/json" \
      -d '{"claim_token":"CLAIM_TOKEN","email":"user@example.com"}'
    ```
  </Step>

  <Step title="Poll for tokens">
    Honor the returned `claim.interval`. Poll the token endpoint with the
    static public client ID.

    ```bash theme={null}
    curl -X POST https://xquik.com/api/oauth/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      --data-urlencode "grant_type=urn:workos:agent-auth:grant-type:claim" \
      --data-urlencode "client_id=urn:xquik:agent-auth" \
      --data-urlencode "claim_token=CLAIM_TOKEN"
    ```

    Handle `authorization_pending`, `slow_down`, `access_denied`, and
    `expired_token`. Success returns an access token and
    `identity_assertion`. It never returns a refresh token.
  </Step>

  <Step title="Exchange an identity assertion">
    Reuse a current assertion when a fresh access token is required.

    ```bash theme={null}
    curl -X POST https://xquik.com/api/oauth/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
      --data-urlencode "client_id=urn:xquik:agent-auth" \
      --data-urlencode "assertion=IDENTITY_ASSERTION"
    ```

    Xquik accepts service-signed assertions for active registrations. Restart registration after expiry or `invalid_grant`.
  </Step>
</Steps>

## Refresh tokens

Interactive access tokens last 1 hour. Refresh them without another login.

```bash theme={null}
curl -X POST https://xquik.com/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token\
&refresh_token=f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1\
&client_id=550e8400-e29b-41d4-a716-446655440000\
&resource=https://xquik.com/mcp"
```

```json Response theme={null}
{
  "access_token": "NEW_ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "NEW_REFRESH_TOKEN",
  "scope": "mcp:tools"
}
```

<Warning>
  Refresh tokens are single-use. Always store the latest returned token.
</Warning>

## Token revocation

Revoke a token when a user disconnects or your app no longer needs it.

```bash theme={null}
curl -X POST https://xquik.com/api/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=ACCESS_OR_REFRESH_TOKEN\
&client_id=550e8400-e29b-41d4-a716-446655440000\
&token_type_hint=access_token"
```

| Parameter | Required | Description |
| - | - | - |
| `token` | Yes | The token to revoke |
| `client_id` | Yes | The client ID that owns the token |
| `token_type_hint` | No | `access_token` or `refresh_token`. Helps the server locate the token faster |

It returns `200` with an empty body, even for a revoked or invalid token (RFC 7009).

Revoke a claimed agent with its `identity_assertion`, `client_id=urn:xquik:agent-auth`, and `token_type_hint=urn:ietf:params:oauth:token-type:id-jag`.
This revokes the registration & every access token. Revoking 1 access token keeps the registration.

**Revocation errors.**

| Status | Error | When |
| - | - | - |
| 400 | `invalid_request` | `token` parameter is empty or missing |
| 400 | `invalid_request` | `client_id` parameter is empty or missing |
| 401 | `invalid_client` | `client_id` does not match a registered client |

## Scopes

| Scope | Description |
| - | - |
| `mcp:tools` | Full access to all MCP tools, such as tweet search, monitors, extractions, and draws |
| `mcp:read` | Only the ChatGPT app's 8 reads of public posts, profiles & trends, paid with credits |

Consent lists only what the requested scopes allow, and the token gets exactly
those. A refresh keeps them; asking for others gets `invalid_scope`. Without
`mcp:tools`, MCP shows only the ChatGPT app's 8 read tools.

## Client registration

### Request

```
POST /api/oauth/register
Content-Type: application/json
```

| Field | Type | Required | Description |
| - | - | - | - |
| `client_name` | string | No | Display name shown on the consent screen. Defaults to `MCP Client` |
| `redirect_uris` | string\[] | Yes | Allowed redirect URIs (1 or more) |
| `token_endpoint_auth_method` | string | No | `none` (default) or `client_secret_post` |
| `grant_types` | string\[] | No | Defaults to `["authorization_code", "refresh_token"]` |
| `response_types` | string\[] | No | Defaults to `["code"]` |

### Response

| Field | Type | Description |
| - | - | - |
| `client_id` | string | UUID. Use this in all later OAuth requests |
| `client_name` | string | Resolved display name. Defaults to `MCP Client` |
| `redirect_uris` | string\[] | Echoed from request |
| `grant_types` | string\[] | Resolved grant types |
| `response_types` | string\[] | Resolved response types |
| `token_endpoint_auth_method` | string | Resolved auth method |
| `client_secret` | string | Only present for confidential clients (`client_secret_post`) |
| `client_id_issued_at` | number | Unix timestamp when the client ID was issued |
| `client_secret_expires_at` | number | Always `0` (non-expiring). Only present for confidential clients |

## Error responses

Token, registration, and revocation endpoint errors use the standard OAuth JSON
format:

```json theme={null}
{
  "error": "error_code",
  "error_description": "Human-readable description."
}
```

Authorization errors use 2 transports. Xquik returns an HTML error page when it
cannot safely trust the client or redirect URI. After validating both, Xquik
redirects errors to the registered callback with `error`, `error_description`,
optional `state`, and `iss=https://xquik.com` query parameters.

### Authorization errors

| Error | When |
| - | - |
| `unsupported_response_type` | `response_type` is not `code` |
| `invalid_request` | Missing or repeated parameters, or missing S256 PKCE data |
| `invalid_scope` | Scope is neither `mcp:tools` nor `mcp:read` |
| `invalid_target` | Resource is not a published protected resource |
| `access_denied` | User denied the authorization request |

### Token errors

| Error | When |
| - | - |
| `invalid_request` | Missing `code`, `code_verifier`, `client_id`, or `refresh_token` |
| `invalid_grant` | The code or token is invalid, expired, or already used. It also covers a `client_id` mismatch, a `redirect_uri` mismatch, or failed PKCE verification |
| `authorization_pending` | The user has not completed the agent claim |
| `slow_down` | Claim polling exceeded the returned interval |
| `access_denied` | The agent claim was denied or revoked |
| `expired_token` | The claim token or user code expired |
| `unsupported_grant_type` | Grant type is not advertised by discovery |
| `invalid_target` | Resource is not a published protected resource |

### Registration errors

| Error | When |
| - | - |
| `invalid_client_metadata` | The JSON body, client name, token authentication method, grant types, or response types are malformed or unsupported |
| `invalid_redirect_uri` | `redirect_uris` is missing, malformed, duplicated, too long, unsupported, or exceeds the 10 URI limit |
| `temporarily_unavailable` | Registration is rate limited. Honor `Retry-After` before retrying |

Read `error_description` for the specific validation failure. Missing or blank
`client_name` values default to `MCP Client`.

## Where to go next

<CardGroup cols={2}>
  <Card title="MCP server" icon="server" href="/mcp/overview">
    Connect AI agents to Xquik via MCP.
  </Card>

  <Card title="MCP tools reference" icon="wrench" href="/mcp/tools">
    Read every MCP tool with input and output schemas.
  </Card>

  <Card title="API key auth" icon="key" href="/api-reference/authentication">
    Use API key authentication for the REST API and MCP.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/x-api-quickstart">
    Get your API key and make your first request.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.