{"info":{"_postman_id":"bd168430-ecf9-43c8-af88-c557e66e8e88","name":"OneHQ — SSO API-Key Login","description":"<html><head></head><body><h1 id=\"onehq-sso-api-key-login\">OneHQ — SSO API-Key Login</h1>\n<p>This collection demonstrates organization-level Single Sign-On for external clients. It enables an external system (for example, a WordPress site) to sign a user into a OneHQ application without prompting for a password or a second factor. A single GraphQL mutation, authenticated with your organization's API key, returns a one-time URL; directing the user's browser to that URL establishes their session.</p>\n<h2 id=\"1-obtain-an-api-key\">1. Obtain an API key</h2>\n<p>API keys are created and managed in the OneHQ administration interface, not in Postman:</p>\n<ol>\n<li>Navigate to <strong>Organization → Edit → Security</strong>.</li>\n<li>Under <strong>SSO API Keys</strong>, select <strong>+</strong> to add a key.</li>\n<li>Provide a <strong>Name</strong>, the target <strong>Application</strong> (the application the user will be signed in to), a <strong>Permission</strong>, and an <strong>Alert email</strong>. The <strong>Permission</strong> controls how an unknown email is handled:<ul>\n<li><code>auth_only</code> — only existing users may sign in; an unknown email is rejected (<code>user_not_found</code>), no account is created.</li>\n<li><code>auth_or_create</code> — an unknown email automatically provisions a new account (on the bound application, with the key's role) and signs in.</li>\n</ul>\n</li>\n<li>Select <strong>Save</strong>. The <strong>client secret</strong> (<code>sk_live_...</code>) is displayed only once. Record it immediately, as it cannot be retrieved afterward. This secret is the credential you provide to the collection (see step 2).</li>\n</ol>\n<h2 id=\"2-configure-the-collection-variables\">2. Configure the collection variables</h2>\n<p>In the collection's <strong>Variables</strong> tab, set the following:</p>\n<ul>\n<li><code>baseUrl</code>: the OneHQ SSO endpoint (<code>https://sso.onehq.com</code>).</li>\n<li><code>apiKey</code>: the <code>client_secret</code> (<code>sk_live_...</code>) obtained in step 1.</li>\n<li><code>email</code>: the user to authenticate. For <code>auth_only</code> keys, the user must belong to the key's organization.</li>\n</ul>\n<h2 id=\"3-execute-the-flow\">3. Execute the flow</h2>\n<ol>\n<li>Send the <strong>ssoLogin</strong> request. It returns a one-time <code>redirectUrl</code>, valid for approximately five minutes and usable once.</li>\n<li>Open the <code>redirectUrl</code> in a browser. The user is signed in without a password or second factor.</li>\n</ol>\n<h2 id=\"security\">Security</h2>\n<p>The API key must be provided in the <code>Authorization: Bearer &lt;client_secret&gt;</code> header, which is already configured on the request. It must never be placed in the request body, where it could be recorded in server logs.</p>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[{"content":"OneHQ — SSO API-Key Login","slug":"onehq-sso-api-key-login"}],"owner":"13507213","collectionId":"bd168430-ecf9-43c8-af88-c557e66e8e88","publishedId":"2sBXwwn87v","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"},"publishDate":"2026-06-23T17:49:33.000Z"},"item":[{"name":"ssoLogin — get one-time redirect URL","event":[{"listen":"test","script":{"type":"text/javascript","exec":["const res = pm.response.json();","const payload = res && res.data && res.data.ssoLogin;","","pm.test('HTTP 200', () => pm.response.to.have.status(200));","pm.test('returns an ssoLogin payload', () => pm.expect(payload, JSON.stringify(res)).to.be.an('object'));","","if (payload && payload.redirectUrl) {","  pm.collectionVariables.set('redirectUrl', payload.redirectUrl);","  const st = payload.redirectUrl.match(/st=([^&]+)/);","  if (st) pm.collectionVariables.set('st', decodeURIComponent(st[1]));","  const ticket = payload.redirectUrl.match(/ticket=([^&]+)/);","  if (ticket) pm.collectionVariables.set('ticket', decodeURIComponent(ticket[1]));","  pm.test('expiresIn is 300', () => pm.expect(payload.expiresIn).to.eql(300));","} else {","  console.log('ssoLogin error:', payload && payload.error, payload && payload.message);","}"],"id":"5822a84a-1351-4dc8-a9a1-263de696d913"}}],"id":"336f8d6f-b30a-4690-8305-f14d3f57d373","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"Bearer sk_live_REPLACE_ME"}],"body":{"mode":"raw","raw":"{\n  \"operationName\": \"SsoLogin\",\n  \"query\": \"mutation SsoLogin($email: String!) { ssoLogin(email: $email) { redirectUrl expiresIn error statusCode message } }\",\n  \"variables\": {\n    \"email\": \"user@your-org.com\"\n  }\n}","options":{"raw":{"language":"json"}}},"url":"https://sso.onehq.com/gql","description":"<p>Validates the API key, resolves (or creates) the user, and returns a single-use <code>redirectUrl</code>.</p>\n<h2 id=\"authentication\">Authentication</h2>\n<p>The API key is sent in the <code>Authorization: Bearer sk_live_REPLACE_ME</code> header — never in the body. <code>operationName</code> must be <code>SsoLogin</code> (this operation is allowed pre-authentication based on the operationName).</p>\n<h2 id=\"permission-modes-configured-per-key\">Permission modes (configured per key)</h2>\n<ul>\n<li><code>auth_only</code> — only existing users may sign in; an unknown email returns <code>user_not_found</code> (404), no account is created.</li>\n<li><code>auth_or_create</code> — an unknown email provisions a new account (on the key's bound application, with the key's role) and signs in.</li>\n</ul>\n<h2 id=\"redirect-url-form\">Redirect URL form</h2>\n<p>Determined by the application the key is bound to:</p>\n<ul>\n<li>HQAdmin → <code>&lt;host&gt;/sso/consume?st=&lt;JWT&gt;</code> (self-consumed)</li>\n<li>Any other application → <code>&lt;application-url&gt;/users/service?ticket=&lt;ST&gt;</code> (the user lands in that application)</li>\n</ul>\n<p>To chain the user onward after sign-in, append <code>&amp;redirect_to=&lt;path&gt;</code> to the returned URL.</p>\n<h2 id=\"response-datassologin\">Response (<code>data.ssoLogin</code>)</h2>\n<p>This is a GraphQL mutation: a well-formed request returns HTTP 200. The outcome is carried in the body under <code>data.ssoLogin</code>. The <code>statusCode</code> field is an application-level code in the payload — NOT the HTTP status.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>redirectUrl</code></td>\n<td>String</td>\n<td>one-time URL on success; null on error</td>\n</tr>\n<tr>\n<td><code>expiresIn</code></td>\n<td>Int</td>\n<td>seconds to expiry (300) on success; null on error</td>\n</tr>\n<tr>\n<td><code>error</code></td>\n<td>String</td>\n<td>error code; null on success</td>\n</tr>\n<tr>\n<td><code>statusCode</code></td>\n<td>Int</td>\n<td>application status for certain errors; null otherwise</td>\n</tr>\n<tr>\n<td><code>message</code></td>\n<td>String</td>\n<td>human-readable description</td>\n</tr>\n</tbody>\n</table>\n</div><h2 id=\"error-codes\">Error codes</h2>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><code>error</code></th>\n<th><code>statusCode</code></th>\n<th>Condition</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>invalid_api_key</code></td>\n<td>null</td>\n<td>missing, malformed, or revoked/rotated key</td>\n</tr>\n<tr>\n<td><code>user_not_found</code></td>\n<td>404</td>\n<td><code>auth_only</code> key and the email has no account in the organization</td>\n</tr>\n<tr>\n<td><code>no_app_access</code></td>\n<td>403</td>\n<td>the user cannot access the key's bound application</td>\n</tr>\n<tr>\n<td><code>login_not_allowed</code></td>\n<td>403</td>\n<td>the user is locked, disabled, or not permitted to sign in</td>\n</tr>\n<tr>\n<td><code>rate_limited</code></td>\n<td>429</td>\n<td>more than 60 requests per minute for the key</td>\n</tr>\n<tr>\n<td><code>sso_login_error</code></td>\n<td>null</td>\n<td>unexpected server-side error</td>\n</tr>\n</tbody>\n</table>\n</div><p>A revoked or rotated key returns <code>invalid_api_key</code> on the next call (observable on the mint call, so revocation can be detected proactively).</p>\n<h2 id=\"alerts\">Alerts</h2>\n<p>If the key has an alert email configured, an alert is sent when the key exceeds its rate limit (more than 60 requests per minute).</p>\n","urlObject":{"path":["gql"],"host":["https://sso.onehq.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"336f8d6f-b30a-4690-8305-f14d3f57d373"},{"name":"Consume ticket (browser only)","id":"8f74f762-c3ca-4009-a3b0-b7293734b6da","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://sso.onehq.com/sso/consume?st=","description":"<p>The final step is meant for a real BROWSER, not Postman: opening the <code>redirectUrl</code> verifies the single-use ticket, establishes the session cookie and redirects the user into the UI.</p>\n<p>This request is here for reference. Calling it from Postman will consume the ticket (single-use) and set a cookie, but won't render the app. Use it only to inspect the 302 redirect.</p>\n<p>HQAdmin path only — other apps consume their own <code>?ticket=</code> via CAS.</p>\n","urlObject":{"path":["sso","consume"],"host":["https://sso.onehq.com"],"query":[{"key":"st","value":""}],"variable":[]}},"response":[],"_postman_id":"8f74f762-c3ca-4009-a3b0-b7293734b6da"}],"variable":[{"key":"baseUrl","value":"https://sso.onehq.com","description":"The OneHQ SSO endpoint. All requests in this collection are sent to this host (for example, POST https://sso.onehq.com/gql)."},{"key":"apiKey","value":"sk_live_REPLACE_ME","description":"The organization's API key client secret (sk_live_...). It is created in the OneHQ administration interface: Organization → Edit → Security → SSO API Keys → add a key → copy the secret shown once. It is provided in the Authorization: Bearer header, never in the request body."},{"key":"email","value":"user@your-org.com","description":"The email of the user to log in. Must exist in the key's organization for auth_only keys."},{"key":"redirectUrl","value":"","description":"Captured from the last ssoLogin response."},{"key":"st","value":"","description":"Captured service-ticket JWT (HQAdmin self-consume path)."},{"key":"ticket","value":"","description":"Captured CAS service ticket (other apps)."}]}