If you are building an MCP server, AI connector, or integrating from a third-party platform like ChatGPT, you can set up OAuth access to Formify without contacting us. This guide covers two self-service registration methods — pick the one that fits your client.

This page covers self-service registration — both public clients (no secret) and confidential clients (with client_secret) via DCR. If you need a manually provisioned confidential client, see Getting Access.

Choose your registration method

Formify supports two self-service registration methods. Public clients use PKCE (S256) instead of a client_secret. DCR also supports confidential clients with client_secret_basic.

Option A: Metadata DocumentOption B: Dynamic Client Registration (DCR)
client_id formatHTTPS URLGUID
How it worksYou host a JSON document at a public HTTPS URL. That URL is your client_id.You call POST /oauth/register and receive a GUID client_id.
Requires hostingYes — a public HTTPS endpointNo
RegistrationAutomatic on first useExplicit API call (RFC 7591)
Client typesPublic onlyPublic or confidential
Best forMCP servers, self-hosted toolsThird-party platforms, clients that cannot host a metadata document

Option A: Client ID Metadata Document

Host a JSON file at a publicly accessible HTTPS URL, for example https://mcp.example.com/.well-known/oauth-client.json. That URL becomes your client_id.

How it works

  1. You host a Client ID Metadata Document (JSON) at an HTTPS URL.
  2. That URL is your client_id — no GUID, no secret.
  3. Your client uses PKCE (Proof Key for Code Exchange) instead of a client_secret.
  4. Formify fetches your metadata document to learn your app name, logo, and allowed redirect URIs.

Example metadata document

{
  "client_id": "https://mcp.example.com/.well-known/oauth-client.json",
  "client_name": "My MCP Connector",
  "client_uri": "https://mcp.example.com",
  "logo_uri": "https://mcp.example.com/logo.png",
  "redirect_uris": [
    "https://mcp.example.com/oauth/callback"
  ],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "read write"
}

Metadata fields

FieldRequiredDescription
client_idYesMust exactly match the URL where this document is hosted.
client_nameYesHuman-readable name shown to the user during authorization.
redirect_urisYesOne or more allowed callback URLs (must be absolute HTTPS URLs).
client_uriRecommendedLink to the application's homepage.
logo_uriRecommendedURL to the application's logo, displayed during authorization consent.
token_endpoint_auth_methodRecommendedMust be "none" for public clients.
scopeOptionalSpace-separated list of scopes the client intends to request.
contactsOptionalContact email addresses.
tos_uriOptionalLink to Terms of Service.
policy_uriOptionalLink to Privacy Policy.

Hosting requirements

Caching: Formify caches metadata documents. If you change redirect URIs or other fields, allow time for the cache to expire before the changes take effect.

Option B: Dynamic Client Registration (DCR)

If you cannot host a metadata document (e.g. ChatGPT, other third-party platforms), you can register your client programmatically via the DCR endpoint (RFC 7591). You make a single API call and receive a GUID-based client_id.

Endpoint

POST https://docs-api.formify.eu/v1/oauth/register
Content-Type: application/json

Request fields

FieldRequiredDescription
client_nameYesHuman-readable name shown to users during authorization.
redirect_urisYesArray of allowed callback URLs. Must be absolute HTTPS URLs without fragments. Max 10 URIs.
token_endpoint_auth_methodRecommended"none" for public clients or "client_secret_basic" for confidential clients. Defaults to "none".
grant_typesOptionalInformational. Array of grant types the client intends to use. Supported values: "authorization_code", "refresh_token". Defaults to ["authorization_code"].
response_typesOptionalArray of response types. Supported: "code". Defaults to ["code"].
client_uriOptionalURL to the application's homepage.
logo_uriOptionalURL to the application's logo, displayed during authorization consent.
tos_uriOptionalLink to Terms of Service.
policy_uriOptionalLink to Privacy Policy.
scopeOptionalSpace-separated list of scopes the client intends to request. Max 1024 characters.
contactsOptionalArray of contact email addresses. Max 5 entries.

Validation rules

Rate limiting

Registration is rate limited. If you exceed the limit, the API returns 429 Too Many Requests.

Example: Register a public client

curl -X POST "https://docs-api.formify.eu/v1/oauth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My AI Assistant",
    "redirect_uris": ["https://app.example.com/oauth/callback"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "scope": "read write",
    "client_uri": "https://app.example.com",
    "contacts": ["dev@example.com"]
  }'

Response (public client)

{
  "client_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "client_name": "My AI Assistant",
  "redirect_uris": ["https://app.example.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "client_id_issued_at": 1713700000
}

Response (confidential client)

If token_endpoint_auth_method is "client_secret_basic", the response also includes:

{
  "client_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "client_name": "My Integration",
  "redirect_uris": ["https://app.example.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "client_secret_basic",
  "client_id_issued_at": 1713700000,
  "client_secret": "super-secret-value",
  "client_secret_expires_at": 0
}

Store credentials securely. The client_secret is only returned once at registration time. If you lose it, you must register a new client. client_secret_expires_at: 0 means the secret does not expire.

Response fields

FieldTypeDescription
client_idString (GUID)The registered client identifier.
client_nameStringThe registered client name.
redirect_urisArrayThe registered redirect URIs.
grant_typesArrayThe registered grant types.
response_typesArrayThe registered response types.
token_endpoint_auth_methodString"none" or "client_secret_basic".
client_id_issued_atIntegerUnix timestamp when the client was registered.
client_secretStringOnly for confidential clients. The client secret.
client_secret_expires_atIntegerOnly for confidential clients. 0 means no expiration.

Implement PKCE

PKCE is required for all public clients (both metadata document and DCR). Generate a fresh code_verifier for every authorization request.

  1. Generate a random code_verifier (43–128 characters, using [A-Za-z0-9\-._~]).
  2. Compute the code_challenge: BASE64URL(SHA256(code_verifier))
  3. Send code_challenge and code_challenge_method=S256 in the authorization request.
  4. Send the original code_verifier when exchanging the code for tokens.

JavaScript

const codeVerifier = generateRandomString(64); // 64 URL-safe random chars

async function generateCodeChallenge(verifier) {
  const data = new TextEncoder().encode(verifier);
  const digest = await crypto.subtle.digest("SHA-256", data);
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

const codeChallenge = await generateCodeChallenge(codeVerifier);

Python

import hashlib, base64, secrets

code_verifier = secrets.token_urlsafe(64)
code_challenge = (
    base64.urlsafe_b64encode(
        hashlib.sha256(code_verifier.encode("ascii")).digest()
    )
    .rstrip(b"=")
    .decode("ascii")
)

Request authorization

Redirect the user to Formify's authorization endpoint. The user signs in and approves access. Formify validates your client, verifies the redirect URI, and redirects the user back with an authorization code.

Authorize URL:

https://app.formify.eu/oauth
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://example.com/oauth/callback
  &response_type=code
  &scope=read%20write
  &state=RANDOM_STATE_STRING
  &code_challenge=BASE64URL_SHA256_HASH
  &code_challenge_method=S256

client_id is either your metadata document URL (Option A) or your GUID (Option B).

On approval, the user is redirected to your callback URL:

https://example.com/oauth/callback?code=AUTH_CODE&state=RANDOM_STATE_STRING

Authorization codes expire after 10 minutes and are single-use. Always verify the state parameter matches what you sent.

Exchange code for tokens

Public clients (PKCE)

No client_secret is needed — send the code_verifier instead.

curl -X POST "https://docs-api.formify.eu/v1/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE_HERE" \
  -d "redirect_uri=https://example.com/oauth/callback" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "code_verifier=YOUR_CODE_VERIFIER"

Confidential clients (DCR)

Authenticate with HTTP Basic using your client_id and client_secret.

curl -X POST "https://docs-api.formify.eu/v1/oauth/token" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE_HERE" \
  -d "redirect_uri=https://example.com/oauth/callback"

Response:

{
  "access_token": "<jwt_token>",
  "expires_in": 3600,
  "token_type": "Bearer",
  "refresh_token": "<refresh_token>",
  "scope": "read write"
}

Use the access token

Include the token in the Authorization header for all API requests:

Authorization: Bearer <access_token>

See the API Reference for available endpoints.

Refresh tokens

Access tokens expire after 1 hour. Use the refresh token to get a new one. Refresh tokens are valid for 60 days.

Public clients

curl -X POST "https://docs-api.formify.eu/v1/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN" \
  -d "client_id=YOUR_CLIENT_ID"

Confidential clients

curl -X POST "https://docs-api.formify.eu/v1/oauth/token" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN"

Revoke tokens

Public clients can revoke refresh tokens when access is no longer needed:

curl -X POST "https://docs-api.formify.eu/v1/oauth/revoke" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "token=YOUR_REFRESH_TOKEN" \
  -d "token_type_hint=refresh_token"

Server metadata discovery

Clients that support OAuth Authorization Server Metadata (RFC 8414) can discover Formify's endpoints automatically:

GET https://docs-api.formify.eu/.well-known/oauth-authorization-server

Response:

{
  "issuer": "https://docs-api.formify.eu",
  "authorization_endpoint": "https://app.formify.eu/oauth",
  "token_endpoint": "https://docs-api.formify.eu/v1/oauth/token",
  "revocation_endpoint": "https://docs-api.formify.eu/v1/oauth/revoke",
  "registration_endpoint": "https://docs-api.formify.eu/v1/oauth/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
  "code_challenge_methods_supported": ["S256"],
  "client_id_metadata_document_supported": true
}

Production checklist

All public clients

Metadata document clients (Option A)

DCR-registered clients (Option B)