Runtime Data API
Authenticate your app's end users and make authenticated record API calls against your Knack application from a custom frontend.
Overview
The Knack Runtime Data API lets your custom frontend authenticate end users and perform create, read, update, and delete operations on Knack data on their behalf. When a user logs in through your frontend, Knack issues a short-lived Bearer token (lao_ prefix) that your app attaches to every subsequent API request.
This is the primary API for developers building custom frontends — React apps, mobile apps, third-party tools, or AI-generated UIs — that use Knack as their backend database. All data lives in Knack; your frontend is simply the interface.
Note: This guide covers runtime record access from custom frontends. For building the underlying data model (tables, fields, relationships), see the Knack MCP Server guide.
Using the Knack MCP Server? Context is loaded automatically.
If you're building your custom frontend through an AI coding tool connected to the Knack MCP Server, you don't need to manually provide this documentation to your AI agent. The MCP server includes a loadSkill tool that automatically supplies the AI with precise, up-to-date implementation guides — including the complete OAuth flow, token handling, record API patterns, and login screen construction — exactly when needed.
This means your AI agent already knows how to:
- Implement the full OAuth 2.0 + PKCE authorization flow
- Manage access tokens, refresh tokens, and session expiry
- Make authenticated record API calls on behalf of your users
- Build login screens and handle token storage securely
Just connect the Knack MCP Server to your AI tool and describe what you want to build — the context is handled for you.
How it works
Each API request is scoped to an authenticated user, which means:
- Data access is enforced per user — if your Knack app has Data Access Control (DAC) enabled, each user only sees the records they're permitted to access.
- System fields are populated automatically —
Created By,Updated By, andOwned Byreflect the identity of the user making the request, giving you accurate ownership and audit trails. - Tokens are app-scoped — a token issued for one Knack application cannot be used against a different application.
Token types
| Token | Prefix | Lifetime | Purpose |
|---|---|---|---|
| Access token | lao_ | 1 hour | Authenticates runtime API calls |
| Refresh token | lrt_ | 30 days | Obtains a new access token without re-login |
Table of Contents
- Overview
- Step 1 — Register an OAuth Client
- Step 2 — Authorization Flow (PKCE)
- Step 3 — Exchange Code for Tokens
- Step 4 — Make Authenticated Record API Calls
- Step 5 — Retrieve the Signed-In User's Session (Account ID & Role)
- Step 6 — Refresh the Access Token
- Step 7 — Revoke Tokens
- Error Responses
- Notes for Implementation
Base URL
https://<your-knack-api-domain>
Step 1 — Register an OAuth Client
Before your frontend can authenticate users, register it as an OAuth client. Registration is idempotent — the same client_name + redirect_uris + application_id always returns the same client_id, so it's safe to call this at app startup or build time.
POST /v1/oauth/register
Content-Type: application/json
{
"client_name": "My Custom Frontend",
"redirect_uris": ["https://myapp.example.com/callback"],
"application_id": "<knack_app_id>"
}Response 201 (new) or 200 (existing):
{
"client_id": "<client_id>",
"redirect_uris": ["https://myapp.example.com/callback"],
"client_name": "My Custom Frontend",
"application_id": "<knack_app_id>"
}Fields:
| Field | Type | Notes |
|---|---|---|
client_name | string | Required. Displayed on the login screen your users see. |
redirect_uris | string[] | Required. Must use https. Any host is accepted — Knack does not restrict or allowlist specific domains. You can register https://localhost:<port>/callback for local development or desktop/MCP clients, exactly as you would a production URL. |
application_id | string | Required. The 24-char hex ID of your Knack application. |
Note: You fully control
redirect_urisfor your own client — Knack does not block or pre-approve specific redirect URLs. AnyhttpsURI works, includinglocalhost, so there's no separate "development" redirect URL to request from Knack.
Step 2 — Authorization Flow (PKCE)
Your frontend initiates a PKCE-based OAuth flow to authenticate each user. PKCE (Proof Key for Code Exchange) ensures the flow is secure even in environments where a client secret cannot be stored safely, such as browser apps or mobile clients.
2a. Generate PKCE parameters
Generate a fresh code_verifier and code_challenge for every authorization attempt — these are single-use.
const crypto = require('crypto');
const codeVerifier = crypto.randomBytes(48).toString('base64url');
const codeChallenge = crypto.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
const state = crypto.randomBytes(16).toString('hex');2b. Redirect the user to the authorization endpoint
Send the user to Knack's authorization endpoint. Knack renders a login form where your user enters their credentials.
GET /v1/oauth/authorize
?client_id=<client_id>
&redirect_uri=<redirect_uri>
&response_type=code
&code_challenge=<code_challenge>
&code_challenge_method=S256
&state=<state>
Required parameters:
| Parameter | Value |
|---|---|
client_id | From Step 1 |
redirect_uri | Must exactly match a registered URI |
response_type | code |
code_challenge | base64url(sha256(code_verifier)) |
code_challenge_method | S256 |
state | Random hex string for CSRF protection |
2c. User logs in and is redirected back
After the user authenticates successfully, Knack redirects back to your redirect_uri with a short-lived authorization code:
<redirect_uri>?code=<auth_code>&state=<state>
Always validate state before proceeding — reject mismatches to prevent CSRF attacks. The authorization code is valid for 5 minutes and is single-use.
2d. Live-app clients skip the consent screen
If your client was registered with an application_id (a live-app client — i.e., one registered via the Knack MCP Server's registerLiveAppOAuthClient tool, or the equivalent registration call tied to your Knack app), Knack treats it as first-party:
- If the user isn't already signed in, Knack first redirects them to
/v1/oauth/live-app/login?authRequestId=...to authenticate with their Knack account credentials. - Once signed in, authorization happens automatically — there is no separate consent screen — and Knack redirects immediately to your
redirect_uriwithcodeandstate, as shown above.
Step 3 — Exchange Code for Tokens
Exchange the authorization code for an access token and refresh token. Your frontend attaches the access token to every runtime API call made on behalf of the user.
POST /v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<auth_code>
&redirect_uri=<redirect_uri>
&client_id=<client_id>
&code_verifier=<code_verifier>Response 200:
{
"access_token": "lao_<hex>",
"refresh_token": "lrt_<hex>",
"token_type": "Bearer",
"expires_in": 3599
}Store these tokens securely — see the Notes for Implementation section for guidance.
No client secret: Runtime API OAuth clients are public clients — Knack does not issue a client secret, and none is accepted at the token endpoint (
token_endpoint_auth_methods_supportedis effectivelynone). Security relies on PKCE (code_verifier) rather than a shared secret, which is why this flow is safe to use from browser-based or mobile apps.
Step 4 — Make Authenticated Record API Calls
Attach the access token to every request your frontend makes to the Runtime Data API. Knack uses the token to identify the user, enforce data access rules, and populate system fields.
GET /v1/objects/<object_key>/records
X-Knack-Application-Id: <knack_app_id>
Authorization: Bearer lao_<access_token>Supported endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/objects/<key>/records | List records |
GET | /v1/objects/<key>/records/<id> | Get record |
POST | /v1/objects/<key>/records | Create record |
PUT | /v1/objects/<key>/records/<id> | Update record |
DELETE | /v1/objects/<key>/records/<id> | Delete record |
Data access and ownership
- Data Access Control (DAC): If
settings.accessControlEnforced: trueis set on your app, every endpoint in the Supported endpoints table above (list, get, create, update, delete) automatically filters and authorizes records to what the authenticated user is permitted to see and do. Your frontend does not need to implement this logic — Knack enforces it server-side at runtime, scoped to the user identified by thelao_access token on the request. - System fields:
Created By,Updated By, andOwned Byare automatically populated with the authenticated user's identity on every write operation.
Step 5 — Retrieve the Signed-In User's Session (Account ID & Role)
Your frontend can look up the identity behind the current access token — including the user's Account ID and role record ID — without decoding the token yourself.
GET /:api_version/live-app/:app_id/session
Authorization: Bearer lao_<access_token>This endpoint also accepts the session via cookie, using the same resolution path as a browser-based Knack session — so it works whether your frontend sends the token as a bearer header or relies on the session cookie set during the OAuth redirect.
Response 200 (authenticated):
{
"session": {
"user": {
"id": "<account_record_id>",
"email": "[email protected]",
"name": "Jane Doe",
"profile_keys": ["profile_x"]
}
}
}Response 200 (not authenticated):
{ "session": null }Account ID vs. role record ID
Every user who signs in has a record in the built-in Accounts object — that record's ID is the Account ID, returned as session.user.id above. It answers "which person is this?" and is also the value Knack uses to populate Created By / Updated By / Owned By on records the user creates or edits.
Each user role (e.g., Admin, Staff, Member) is a separate Knack object. A user holding a role has their own record in that role's object, connected back to their Accounts record via a connection field. For example, a user with the "Staff" role has a record in the Staff object, and that Staff record's ID is the role record ID — a distinct ID, living in a different table, from the Account ID.
To resolve a role record ID from the session response:
- Use
session.user.id(the Account ID) to query the relevant role object (e.g.,Staff) filtered on its connection field back to Accounts. - The matching role record's
idis the role record ID.
Step 6 — Refresh the Access Token
Access tokens expire after 1 hour. Use the refresh token to obtain a new access token without requiring the user to log in again. Each refresh rotates both tokens — the old refresh token is immediately invalidated.
POST /v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=lrt_<refresh_token>
&client_id=<client_id>There is no separate "refresh" endpoint — /v1/oauth/token handles both the initial code exchange and subsequent refreshes, distinguished by grant_type.
Step 7 — Revoke Tokens
When a user logs out of your frontend, revoke their tokens to invalidate the session on Knack's side.
POST /v1/oauth/revoke
Content-Type: application/x-www-form-urlencoded
token=lao_<access_token>&client_id=<client_id>Notes:
client_idis required — thelao_/lrt_prefix ontokentells Knack which token type you're revoking, andclient_idis needed to look up that client's token tables.- Revoking either token (access or refresh) cascades: Knack deletes all access and refresh tokens for that user + client pairing, not just the one you passed.
- The endpoint always returns
200, even if the token you passed was already invalid or didn't exist — don't treat the response as a signal of whether the token was previously valid.
Error Responses
| HTTP | error | Cause |
|---|---|---|
| 400 | invalid_request | Missing or malformed parameter |
| 400 | invalid_grant | Code expired, already used, or PKCE mismatch |
| 400 | invalid_client | Unknown client_id or application not found |
| 401 | invalid_token | Access token missing, expired, or invalid |
| 403 | login_disactivated | User account is inactive |
| 403 | login_pending_approval | User account is pending approval |
| 429 | — | Too many failed login attempts |
Notes for Implementation
- Client registration is one-time: store
client_idafter the first call; re-registering returns the same ID. - PKCE is required: generate a fresh
code_verifierandcode_challengefor every authorization attempt. Never reuse them. - Session cookies are required for the authorize/login flow. A browser handles this automatically; ensure your frontend does not strip cookies during the redirect.
statemust be validated: compare it to what was sent before exchanging the code. Reject mismatches.- Token storage: store tokens in
httpOnlycookies or a secure server-side session. AvoidlocalStorage— it is accessible to JavaScript and vulnerable to XSS. - Proactive token refresh: refresh the access token a few minutes before it expires (e.g., at 55 minutes) rather than waiting for a
401. This avoids interrupting your user's session. Content-Type: application/x-www-form-urlencodedis required for the token and revoke endpoints — notapplication/json.
Updated 9 days ago

