Skip to main content

Integration Guide

This guide covers best practices for integrating the Ownli API into your application. Whether you're building with React Native, Flutter, or any other framework, these patterns will help you ship a secure and reliable integration.

Architecture Overview

Your integration uses two types of tokens — admin tokens for backend operations and user tokens for your mobile app:

┌─────────────┐        ┌─────────────────┐        ┌─────────────┐
│ Mobile App │──(1)──▶│ Your Backend │──(2)──▶│ Ownli API │
│ │◀──(3)──│ │◀───────│ │
│ │──(4)──▶│ │ │ │
│ │────────┼─────────────────┼──(5)──▶│ │
└──────────────┘ └─────────────────┘ └─────────────┘
  1. App requests an Ownli token from your backend
  2. Your backend authenticates with Ownli using scope: "admin" for backend operations, or scope: "user" for the app
  3. Your backend returns the user-scoped token to the app
  4. Your backend calls admin endpoints directly (create users, list users, bulk operations) using the admin token
  5. App calls Ownli API directly with the user-scoped token for check-ins, rewards, photos, etc.
Never store credentials in your app

Your clientId, clientSecret, and partnerId must live on your server, never in the mobile app. These values can be extracted from app bundles regardless of platform (React Native, Flutter, native, etc.).

Why two token types?

A user-scoped token cannot list all users, create users, or access admin endpoints — even if someone intercepts it from the app. This limits exposure to only what that user session needs. Admin tokens stay on your server where credentials are already protected.

1. Set Up Your Backend Token Management

Your backend manages two tokens:

  • Admin token (scope: "admin") — used by your backend for admin operations (create users, list users). Cache this and refresh when it expires.
  • User token (scope: "user") — requested on behalf of your app users and returned to the mobile app. Restricted access only.

Create lightweight endpoints on your backend. This can be as simple as a couple of serverless functions (AWS Lambda, Google Cloud Function, etc.).

const express = require('express');
const app = express();

const OWNLI_API = 'https://api.sandbox.ownli.app';
const CREDENTIALS = {
clientId: process.env.OWNLI_CLIENT_ID,
clientSecret: process.env.OWNLI_CLIENT_SECRET,
partnerId: process.env.OWNLI_PARTNER_ID,
};

// Internal: get an admin token for backend operations
let adminToken = null;
let adminTokenExpiresAt = 0;

async function getAdminToken() {
if (adminToken && Date.now() < adminTokenExpiresAt - 5 * 60 * 1000) {
return adminToken;
}
const response = await fetch(`${OWNLI_API}/api/auth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...CREDENTIALS, scope: 'admin' }),
});
const data = await response.json();
adminToken = data.access_token;
adminTokenExpiresAt = Date.now() + data.expires_in * 1000;
return adminToken;
}

// Endpoint for your mobile app: returns a user-scoped token
// Protect this with your own auth (e.g. Firebase, Auth0, etc.)
app.post('/api/ownli/token', async (req, res) => {
const response = await fetch(`${OWNLI_API}/api/auth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...CREDENTIALS, scope: 'user' }),
});

const data = await response.json();
res.json({
access_token: data.access_token,
expires_in: data.expires_in,
scope: data.scope,
});
});
tip

Protect your token relay endpoint with your own authentication. Only authenticated users of your app should be able to request an Ownli token.

2. Create Users When They Opt In

Create an Ownli user when the user accepts the Ownli terms of service in your app. This ensures you only create records for users who have consented to data sharing.

Admin token required

User creation requires an admin token (scope: "admin"). Your backend should call this endpoint — never the mobile app directly.

The flow:

  1. User navigates to the Ownli data-sharing screen in your app
  2. User accepts the Ownli Terms of Service
  3. Your app calls your backend, which calls POST /api/users using the admin token
Recommended consent copy

On the screen where the user opts in, place a line like this next to the primary action button:

By continuing, you agree to Ownli's Terms of Service and Privacy Policy.

Linking both documents at the moment of consent is what authorizes you to create the Ownli user and share their data — so keep the links exactly as shown and don't call POST /api/users until the user has proceeded past this screen.

curl -X POST https://api.sandbox.ownli.app/api/users?returnExistingUserIfExists=true \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"phone": "+15556667777",
"address": {
"state": "CA"
}
}'

Key points:

  • Always use returnExistingUserIfExists=true — This makes the call idempotent. If the user already exists (e.g. they reinstalled the app), you'll get the existing user back instead of a 409 error.
  • Store the returned id — Save the Ownli user id in your own database, mapped to your internal user. You'll need it for subsequent API calls.
  • User creation should happen server-side — Your backend should call the Ownli API to create the user, not the mobile app directly. This keeps your credentials secure and lets you reliably store the user mapping.

3. Handle Token Refresh

Ownli JWTs expire after 1 hour. Your app should handle the user-scoped token refresh gracefully. Your backend manages its own admin token refresh internally (see the code samples in Step 1).

Proactive refresh — Track when the token was issued and request a new one before it expires.

class OwnliTokenManager {
constructor(fetchTokenFromBackend) {
this.fetchToken = fetchTokenFromBackend;
this.token = null;
this.expiresAt = 0;
}

async getToken() {
// Refresh if token expires within 5 minutes
if (!this.token || Date.now() >= this.expiresAt - 5 * 60 * 1000) {
const { access_token, expires_in } = await this.fetchToken();
this.token = access_token;
this.expiresAt = Date.now() + expires_in * 1000;
}
return this.token;
}
}

// Usage — fetches a user-scoped token from your backend
const tokenManager = new OwnliTokenManager(async () => {
const res = await fetch('https://your-backend.com/api/ownli/token', {
method: 'POST',
headers: { /* your own auth headers */ },
});
return res.json();
});

// Before any Ownli API call from the app
const token = await tokenManager.getToken();

401 fallback — If a request returns 401 Unauthorized, request a fresh token and retry once. If you receive 403 Forbidden with INSUFFICIENT_SCOPE, the endpoint requires an admin token and should be called from your backend instead.

async function callOwnliApi(path, options = {}) {
let token = await tokenManager.getToken();

let response = await fetch(`https://api.sandbox.ownli.app${path}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});

// If unauthorized, refresh token and retry once
if (response.status === 401) {
tokenManager.token = null; // force refresh
token = await tokenManager.getToken();
response = await fetch(`https://api.sandbox.ownli.app${path}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
}

return response;
}

Putting It All Together

Here's the typical integration flow from start to finish:

StepWhereTokenWhat happens
1Your backendStore Ownli credentials (clientId, clientSecret, partnerId) as environment variables
2Your backendAdminFetch an admin token (scope: "admin") and cache it for backend operations
3Your backendCreate a /api/ownli/token endpoint that returns user-scoped tokens to your app
4Your backendAdminWhen user accepts Ownli TOS, create the Ownli user using the admin token
5Your backendStore the Ownli user id mapped to your internal user
6Mobile appUserRequest a user-scoped token via your backend's token endpoint
7Mobile appUserCall Ownli API directly for check-ins, rewards, photos, vehicle lookups
8Mobile appUserRefresh the token proactively or on 401

What each token can do

OperationAdmin tokenUser token
Create usersYesNo
List all usersYesNo
Create vehiclesYesNo
List all vehicles / rewards / payoutsYesNo
Look up a user by ID, phone, emailYesYes
Submit check-ins (mileage, condition, etc.)YesYes
View rewards for a userYesYes
Upload photos / filesYesYes
Claim rewardsYesYes

Endpoints that require an admin token are marked "Admin Token Required" in the API reference.

Sandbox vs. Production

SandboxProduction
Base URLhttps://api.sandbox.ownli.apphttps://api.ownli.app
CredentialsTest credentialsProduction credentials
DataTest data onlyReal user data

Start with the sandbox environment. When you're ready to go live, swap the base URL and credentials — no code changes needed.