Skip to main content
Use Connect with Meradomo when your app ships a separate client — a mobile app, a CLI, or a web client — that connects into the user’s Meradomo from another device. It’s the OAuth 2.0 authorization-code flow with PKCE (S256).
There is intentionally no SDK in v1. The OAuth endpoints below and the local management API are the integration surface; remote-notes-client is a full reference implementation.

Registering a client

Self-serve client registration is coming to the developer portal. Until then, request an OAuth client for your app. You’ll receive a clientId and — for confidential clients (ones that can keep a secret) — a clientSecret shown once. Public clients (no secret) are on the roadmap; confidential clients must send the secret on every token request. You register one or more exact redirectUris.

Authorization flow

1

Generate PKCE

const verifier  = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier, 'ascii').digest('base64url');
2

Send the user to /oauth/authorize

GET https://account.meradomo.com/oauth/authorize
  ?client_id=<clientId>
  &redirect_uri=<exact registered URI>
  &response_type=code
  &state=<random>
  &code_challenge=<challenge>
  &code_challenge_method=S256
  &scope=connect
If the user isn’t signed in, they’re bounced to sign-in and returned here afterward. They see a consent page — “<App name> wants to connect to your Meradomo.” On approval the browser redirects to redirect_uri?code=…&state=…; on denial, ?error=access_denied.
3

Exchange the code for tokens

POST https://account.meradomo.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<code>
&redirect_uri=<same as step 2>
&client_id=<clientId>
&client_secret=<clientSecret>   (confidential clients only)
&code_verifier=<verifier>
Response 200:
{
  "access_token":  "eyJ…",
  "token_type":    "Bearer",
  "expires_in":    600,
  "refresh_token": "…",
  "scope":         "connect",
  "address":       "https://you.meradomo.com",
  "host":          "you.meradomo.com"
}
host is the user’s root address; a published app lives at <app-name>.<host>.

Access token claims

Access tokens are EdDSA (Ed25519) compact JWS. Payload:
ClaimValue
issmeradomo-control
subAccount id (stable, opaque)
emailVerified email address
audRoot host (e.g. you.meradomo.com)
client_idYour OAuth client id
grantGrant id (used for revocation)
scopeconnect
iatIssued-at (unix seconds)
expExpiry: iat + 600 (10 minutes)
jtiUnique token identifier
The signing public key is at GET https://account.meradomo.com/auth/pubkey (JSON with a publicKey PEM field). The agent fetches and caches this key.

Refresh tokens

Refresh tokens are opaque, single-use, and stored hashed.
POST https://account.meradomo.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=<token>
Same response shape as the code exchange. The old refresh token is invalidated immediately — replaying it returns 400 invalid_grant. Refreshing after the grant was revoked returns 400 access_denied.

Revocation

The user can revoke your app from the menu-bar app or their account dashboard. Revocation is asynchronous within a short poll window:
  • New refresh-token requests fail immediately.
  • Existing access tokens stop working within the next agent poll cycle (a few seconds in production).
The agent does not make a per-request call to validate tokens — revocation lands within the poll window.

CORS

POST /oauth/token and GET /auth/pubkey include permissive CORS headers so browser-based clients can call them directly.

Error codes

CodeMeaning
invalid_clientUnknown or incorrect client
invalid_grantCode invalid, expired, already used, or PKCE mismatch
access_deniedGrant revoked, or user denied consent
unsupported_grant_typeGrant type not supported
invalid_redirect_uriRedirect URI not registered
unsupported_response_typeOnly code is supported