Your application name β users see it on the consent screen
Your redirect URI(s): the URL OnePageCRM will redirect users to after they approve your app β https, or http://localhost / http://127.0.0.1 for development
Client type: public (browser/mobile app) or confidential (server-side app)
Registration is handled manually and can take a few days β request it
before you plan to build. Youβll receive your client_id (and
client_secret for confidential clients).
Step 2: Generate PKCE values
Before redirecting the user, generate a code_verifier and code_challenge. These are single-use values that tie the token exchange to this specific login attempt.
const array = new Uint8Array(64)crypto.getRandomValues(array)const codeVerifier = btoa(String.fromCharCode(...array)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')const encoded = new TextEncoder().encode(codeVerifier)const digest = await crypto.subtle.digest('SHA-256', encoded)const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')// Store for use in the callbacksessionStorage.setItem('code_verifier', codeVerifier)
// Uses AppAuth for Android β recommended for secure mobile OAuth// Add to build.gradle: implementation 'net.openid:appauth:0.11.1'import android.net.Uriimport net.openid.appauth.*val serviceConfig = AuthorizationServiceConfiguration( Uri.parse("https://secure.onepagecrm.com/oauth/authorize"), Uri.parse("https://secure.onepagecrm.com/oauth/token"))val authRequest = AuthorizationRequest.Builder( serviceConfig, "YOUR_CLIENT_ID", ResponseTypeValues.CODE, // Must be https (Android App Link) β custom schemes like // myapp:// are rejected at registration. Uri.parse("https://myapp.example.com/oauth/callback")) .setScope("crm.readonly") .setCodeVerifier(codeVerifier, codeChallenge, "S256") .build()val authService = AuthorizationService(this)val intent = authService.getAuthorizationRequestIntent(authRequest)startActivityForResult(intent, RC_AUTH)
// Uses ASWebAuthenticationSession β Apple's secure OAuth browserimport AuthenticationServiceslet state = UUID().uuidStringvar components = URLComponents(string: "https://secure.onepagecrm.com/oauth/authorize")!components.queryItems = [ .init(name: "response_type", value: "code"), .init(name: "client_id", value: "YOUR_CLIENT_ID"), // Must be https (universal link) β custom schemes like // myapp:// are rejected at registration. .init(name: "redirect_uri", value: "https://myapp.example.com/oauth/callback"), .init(name: "scope", value: "crm.readonly"), .init(name: "state", value: state), .init(name: "code_challenge", value: codeChallenge), .init(name: "code_challenge_method", value: "S256"),]let session = ASWebAuthenticationSession( url: components.url!, callback: .https(host: "myapp.example.com", path: "/oauth/callback") // iOS 17.4+) { callbackURL, error in guard let url = callbackURL, error == nil else { return } // Extract code and state from url, then exchange for tokens}session.presentationContextProvider = selfsession.start()
Step 4: Exchange the code for tokens
After the user approves, OnePageCRM redirects back with ?code=...&state=.... Verify the state, then exchange the code immediately. It expires in 10 minutes and is single-use.
import requests# Verify state matches what you stored before the redirectassert request.args['state'] == session['oauth_state'], 'State mismatch'response = requests.post( 'https://secure.onepagecrm.com/oauth/token', data={ 'grant_type': 'authorization_code', 'code': request.args['code'], 'redirect_uri': 'https://myapp.example.com/callback', 'client_id': 'YOUR_CLIENT_ID', 'code_verifier': session['code_verifier'], })tokens = response.json()
require 'net/http'require 'json'# Verify state matches what you stored before the redirectraise 'State mismatch' unless params[:state] == session[:oauth_state]uri = URI('https://secure.onepagecrm.com/oauth/token')response = Net::HTTP.post_form(uri, {grant_type: 'authorization_code',code: params[:code],redirect_uri: 'https://myapp.example.com/callback',client_id: 'YOUR_CLIENT_ID',code_verifier: session[:code_verifier]})tokens = JSON.parse(response.body)
import android.net.Uriimport net.openid.appauth.*import okhttp3.*// AppAuth handles the callback and code exchangeoverride fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == RC_AUTH) { val resp = AuthorizationResponse.fromIntent(data!!) val ex = AuthorizationException.fromIntent(data) if (resp != null) { val authService = AuthorizationService(this) authService.performTokenRequest(resp.createTokenExchangeRequest()) { tokenResp, ex -> if (tokenResp != null) { val accessToken = tokenResp.accessToken val refreshToken = tokenResp.refreshToken } } } }}
The aud field is the CRM API base URL for this userβs account β it may be a regional host rather than app.onepagecrm.com. Always build API URLs from aud instead of hard-coding a host. Store access_token in memory and refresh_token securely.
Step 5: Make an API request
# Base URL comes from the token response's aud fieldcurl https://app.onepagecrm.com/api/v3/contacts.json \-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
const response = await fetch('https://secure.onepagecrm.com/oauth/token', {method: 'POST',headers: { 'Content-Type': 'application/x-www-form-urlencoded' },body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: storedRefreshToken, client_id: 'YOUR_CLIENT_ID',}),})const tokens = await response.json()// Always save the new refresh token β the old one is now invalidstoredRefreshToken = tokens.refresh_token
response = requests.post( 'https://secure.onepagecrm.com/oauth/token', data={ 'grant_type': 'refresh_token', 'refresh_token': stored_refresh_token, 'client_id': 'YOUR_CLIENT_ID', })tokens = response.json()stored_refresh_token = tokens['refresh_token'] # always save the new one
response = Net::HTTP.post_form(URI('https://secure.onepagecrm.com/oauth/token'),grant_type: 'refresh_token',refresh_token: stored_refresh_token,client_id: 'YOUR_CLIENT_ID')tokens = JSON.parse(response.body)stored_refresh_token = tokens['refresh_token'] # always save the new one
val body = FormBody.Builder() .add("grant_type", "refresh_token") .add("refresh_token", storedRefreshToken) .add("client_id", "YOUR_CLIENT_ID") .build()val request = Request.Builder() .url("https://secure.onepagecrm.com/oauth/token") .post(body) .build()val response = OkHttpClient().newCall(request).execute()val tokens = JSONObject(response.body!!.string())storedRefreshToken = tokens.getString("refresh_token") // always save the new one
var request = URLRequest(url: URL(string: "https://secure.onepagecrm.com/oauth/token")!)request.httpMethod = "POST"request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")request.httpBody = "grant_type=refresh_token&refresh_token=\(storedRefreshToken)&client_id=YOUR_CLIENT_ID" .data(using: .utf8)let (data, _) = try await URLSession.shared.data(for: request)let tokens = try JSONDecoder().decode(TokenResponse.self, from: data)storedRefreshToken = tokens.refreshToken // always save the new one
Public clients: Every refresh response includes a new refresh token. Always replace the old one. Reusing a rotated token revokes the entire session.
Next steps
Reference: every endpoint, parameter, error code, and security detail.