# OAuth redirect target (public; authenticated by the signed state)

> oauth callback: reference page for this CustomDomain™ API operation in the connections group. Covers parameters, request body, responses and error codes.

Source: https://docs.customdomain.ai/docs/api-reference/connections/oauthCallback

## `GET https://api.customdomain.ai/v1/connect/oauth/callback`

The provider's browser redirect target, **not** a bearer-authenticated endpoint, and the Redirect URL of the CustomDomain™ Vercel integration. It consumes the signed single-use `state`, turns what the provider sent into a credential (exchanges the `code`; for Porkbun retrieves the approved key pair with the PKCE verifier; for OVHcloud uses the validated consumer key), writes the connection's records with it (dropped after, unless kept for managed mode), and returns an HTML page that postMessages `{ type: "customdomain:oauth", payload }` to the vetted return origin. After `porkbun_dnsconnect` there is no credential: Porkbun added the records itself and says so in `status` (`applied`, `partial`, `denied` or `error` with its `error`), which is not signed, so the connection only moves to `propagating` and public DNS decides; `payload.partial` is true when Porkbun added only some of them. `payload` carries `ok`, `managed`, and on failure a `code` and `message`: besides the provider's own `error` (for example `access_denied`) and `ProviderAuthenticationError`, `apply_failed`, `apex_not_supported`, `forwarding_not_supported`, the rail specific codes are `porkbun_api_access_disabled` (turn on API access for the domain at Porkbun), `porkbun_domain_not_in_account` and `porkbun_domain_not_active` (after `porkbun_dnsconnect`: the domain is not in the Porkbun account that signed in, or not active there), `vercel_team_scope` (install on the team that owns the domain), `ovh_zone_not_found` (pick the OVHcloud region the account lives in), `oauth_scope_not_granted` (a permission was left unticked on the provider's consent screen; sign in again and allow them all), for Google Cloud DNS `gcpdns_zone_not_found` (no public Cloud DNS zone for the domain in a Google Cloud project the account can edit), `gcpdns_zone_ambiguous` (more than one) and `gcpdns_search_incomplete` (the account can see more projects than the search reaches, so the one zone found is not written to), and for Azure DNS `azuredns_zone_not_found` (no public Azure DNS zone for the domain in a subscription the Microsoft account can list zones in), `azuredns_zone_ambiguous` (more than one), `azuredns_search_incomplete` (the account can see more subscriptions or zones than the search reaches, so the one zone found is not written to) and `azuredns_mfa_required` (Azure requires multifactor authentication for the change and the sign-in did not use it). With the not found and ambiguous codes, `payload.account` names the Google or Microsoft account that signed in, when the provider said which; the connection's stored `error_message` never does. After an `mcp_oauth` sign-in, `apply_failed` also reports a write refused before anything changed because a record the provider's DNS tools may not remove is in the way (a Namecheap URL redirect, a record a provider's product manages; the `message` names it and where to remove it), and `ProviderAuthenticationError` a DNS tool the user did not approve at the provider (sign in again and approve DNS access). A failure is also stored as the connection's `error_code`.

### Authentication

None.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `code` | query | string | no |  |
| `state` | query | string | yes |  |
| `teamId` | query | string | no | Vercel install flow: the team the integration was installed on. |
| `configurationId` | query | string | no | Vercel install flow: the installation id. |
| `status` | query | `approved`, `denied` | no | Porkbun key handoff: the account holder's decision. |
| `requestToken` | query | string | no | Porkbun key handoff: echoed by Porkbun; must match the request the server started. |

### Responses

| Status | Description |
| --- | --- |
| `200` | HTML finish page (postMessages the outcome to the vetted return origin) |
| `400` | Invalid, expired, or replayed state |

The full specification is served at `GET https://api.customdomain.ai/v1/openapi.json`.
