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.
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 — Refresh the Access Token
- Step 6 — 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. |
application_id | string | Required. The 24-char hex ID of your Knack application. |
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.
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.
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, records are automatically filtered to what the authenticated user is permitted to see. Your frontend does not need to implement this logic — Knack handles it server-side at runtime. - System fields:
Created By,Updated By, andOwned Byare automatically populated with the authenticated user's identity on every write operation.
Step 5 — 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>Step 6 — Revoke Tokens
When a user logs out of your frontend, revoke their tokens to invalidate the session on Knack's side. Revoking either token type invalidates all tokens (access + refresh) for that user and client pair.
POST /v1/oauth/revoke
Content-Type: application/x-www-form-urlencoded
token=lao_<access_token>&client_id=<client_id>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.

