Skip to main content
The Lunacal API uses the OAuth 2.0 authorization code flow. The user grants consent once in the Lunacal web app. Your client then receives a short-lived access token and a long-lived refresh token, so the user does not have to consent again while the refresh token is valid.

Tokens

Authorization flow

1

Redirect the user to the consent page

Open the user’s browser at the Lunacal authorization page:
  • redirect_uri — required. Must be registered for your client.
  • state — recommended. An opaque random string; it is returned unchanged so you can guard against CSRF.
  • client_id — optional. Your OAuth client ID.
The consent screen lists the read and write permissions your client is requesting.
2

The user approves access

The user signs in (if needed) and clicks Allow. Lunacal redirects the browser to your redirect_uri:
Verify that state matches the value you sent.
3

Exchange the code for tokens

Within 10 minutes, send the code to POST https://app.lunacal.ai/api/mcp/token. The code goes in the Authorization header and your client credentials go in the body:
The response contains accessToken, expiresAt, refreshToken and refreshTokenExpiresAt. The code is single-use; a missing, invalid, used or expired code or secret returns 400.
4

Call the API

Include the access token on every request:
5

Refresh before expiry

When the access token expires (after 1 hour), call POST https://app.lunacal.ai/api/mcp/refresh to get a new token pair:
The response has the same fields as the token exchange.

Token rotation

Every call to /api/mcp/refresh rotates both tokens. The refresh token you submit is invalidated immediately.
Always store the new refresh token returned by /api/mcp/refresh. Reusing an old refresh token returns 400, and the user will need to grant consent again.

Security best practices

  • Keep your client secret on your server. Never ship it in browser or mobile code.
  • Store access and refresh tokens encrypted at rest.
  • Always send and verify the state parameter.
  • Refresh tokens proactively using the expiresAt value instead of waiting for a 401.