orkasa

Authentication

OAuth 2.0

So a third-party app can act on a broker's behalf without ever seeing their password.

Orkasa is an OAuth 2.0 authorization server (RFC 6749). It is how Zapier connects, and how the MCP connector connects.

URL
Authorizationhttps://orkasa.app/oauth/authorize
Tokenhttps://orkasa.app/api/oauth/token
Refreshhttps://orkasa.app/api/oauth/token

Grants: authorization_code (with PKCE S256) and refresh_token.

The flow

Send the broker to authorize

https://orkasa.app/oauth/authorize
  ?response_type=code
  &client_id=ork_client_…
  &redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
  &scope=leads%3Aread%20leads%3Awrite%20viewings%3Awrite
  &state=<random anti-CSRF value>
  &code_challenge=…&code_challenge_method=S256

The consent screen names the app, the brokerage it will reach, and each permission in plain language. If the broker is not signed in they log in first and come back to this exact URL.

Orkasa redirects back with a code

…/callback?code=ork_ac_…&state=<your state>

On refusal: ?error=access_denied&state=…. Your state comes back verbatim.

Exchange the code for tokens

curl -X POST https://orkasa.app/api/oauth/token \
  -d grant_type=authorization_code \
  -d client_id=ork_client_… \
  -d client_secret=ork_cs_… \
  -d code=ork_ac_… \
  -d code_verifier=… \
  --data-urlencode "redirect_uri=https://example.com/callback"
{
  "access_token": "ork_at_…",
  "refresh_token": "ork_rt_…",
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "leads:read leads:write viewings:write"
}

Client credentials may also be sent as HTTP Basic.

Call the API

Authorization: Bearer ork_at_…, on every endpoint, unchanged.

Refresh after an hour

curl -X POST https://orkasa.app/api/oauth/token \
  -u "ork_client_…:ork_cs_…" \
  -d grant_type=refresh_token \
  -d refresh_token=ork_rt_…

Refresh tokens rotate: each refresh returns a new pair and revokes the old one. Store the new refresh_token every time. They last 60 days.

Errors

The OAuth endpoints speak RFC 6749, not the /api/v1 envelope:

{ "error": "invalid_grant", "error_description": "…" }

invalid_client (401) · invalid_grant, invalid_request, invalid_scope, unsupported_grant_type (400).

Every authorization-code failure — unknown, expired, already used, wrong client, wrong redirect_uri, bad PKCE verifier — returns the same invalid_grant, so a caller cannot probe which it was.

What holds the flow up

  • Codes are single-use, expire in 5 minutes, and are bound to the issuing client_id and the exact redirect_uri. Redemption is atomic: a race cannot redeem one twice.
  • redirect_uri is matched by exact string equality against the registered whitelist. Never by prefix, never wildcarded. When a client or redirect URI fails validation, Orkasa renders the error on its own origin and redirects nowhere.
  • PKCE is verified whenever a code_challenge was registered, and only S256 is accepted.
  • Tokens, codes and secrets are all stored as SHA-256 digests. Nothing is logged in the clear, and nothing is recoverable after the response that issued it.
  • Revoking a token row kills the access token and its refresh token together — which is also how rotation retires the previous pair.