LendWorksLendWorksDocs

App Data Store

Per-app, per-org key-value storage for configuration, state, and preferences.

Overview

The App Data Store provides persistent key-value storage scoped to your app and the installing organization. Use it to store configuration, user preferences, sync state, and any other data your app needs without maintaining your own database.

This API requires the app-data:read scope for read operations and app-data:write scope for write operations.

Data Model

Data is organized by namespace and key:

App (your app)
 └── Org (installing organization)
      └── Namespace (logical grouping)
           ├── key1 → value
           ├── key2 → value
           └── key3 → value
  • Namespace — A logical grouping for related keys (e.g., settings, sync-state, cache)
  • Key — A unique identifier within a namespace
  • Value — Any JSON-serializable data (objects, arrays, strings, numbers, booleans)

Limits

LimitValue
Max value size10 KB per value
Max keys per namespace1,000
Max namespaces per app per org50
Key length1–256 characters
Namespace length1–64 characters

Set a Value

PUT /v1/app-data/:namespace/:key
await fetch('https://api.lend.works/v1/app-data/settings/sync-config', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer lw_at_xxxxxxxxxxxxxxxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    value: {
      syncInterval: 3600,
      enabledModules: ['leads', 'funded-deals'],
      lastSyncAt: '2026-03-15T10:00:00Z',
    },
  }),
})

Response

{
  "data": {
    "namespace": "settings",
    "key": "sync-config",
    "value": {
      "syncInterval": 3600,
      "enabledModules": ["leads", "funded-deals"],
      "lastSyncAt": "2026-03-15T10:00:00Z"
    },
    "updatedAt": "2026-03-15T10:00:00Z"
  }
}

If the key already exists, its value is overwritten.

Get a Value

GET /v1/app-data/:namespace/:key
const response = await fetch(
  'https://api.lend.works/v1/app-data/settings/sync-config',
  {
    headers: {
      Authorization: 'Bearer lw_at_xxxxxxxxxxxxxxxx',
    },
  }
)

const { data } = await response.json()
console.log(data.value.syncInterval) // 3600

Returns 404 if the key does not exist.

List Keys in a Namespace

GET /v1/app-data/:namespace
curl -H "Authorization: Bearer lw_at_xxxxxxxxxxxxxxxx" \
     https://api.lend.works/v1/app-data/settings

Response

{
  "data": [
    {
      "namespace": "settings",
      "key": "sync-config",
      "updatedAt": "2026-03-15T10:00:00Z"
    },
    {
      "namespace": "settings",
      "key": "notification-prefs",
      "updatedAt": "2026-03-14T08:30:00Z"
    }
  ],
  "meta": {
    "total": 2
  }
}

Values are not included in the list response — fetch individual keys to retrieve values.

Delete a Value

DELETE /v1/app-data/:namespace/:key
curl -X DELETE \
     -H "Authorization: Bearer lw_at_xxxxxxxxxxxxxxxx" \
     https://api.lend.works/v1/app-data/settings/sync-config

Returns 204 No Content on success.

Common Patterns

Storing Configuration

// Save the user's configuration for your app
await fetch('https://api.lend.works/v1/app-data/config/main', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    value: {
      crmEndpoint: 'https://crm.example.com/api',
      fieldMappings: {
        businessName: 'company_name',
        email: 'primary_email',
      },
      autoSync: true,
    },
  }),
})

Tracking Sync State

// After a successful sync, record the cursor
await fetch('https://api.lend.works/v1/app-data/sync-state/leads', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    value: {
      lastSyncAt: new Date().toISOString(),
      cursor: 'eyJpZCI6IjEyMyJ9',
      recordsSynced: 847,
    },
  }),
})

Per-User Preferences

// Store preferences keyed by user ID
const userId = '550e8400-e29b-41d4-a716-446655440000'
await fetch(`https://api.lend.works/v1/app-data/user-prefs/${userId}`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    value: {
      dashboardLayout: 'compact',
      notifications: { email: true, slack: false },
    },
  }),
})

Best Practices

  • Use namespaces to organize related data (e.g., config, sync-state, cache)
  • Keep values small — The 10 KB limit is per value. For larger data, store references to external storage.
  • Don't store secrets — Use environment variables or a secrets manager for API keys and credentials. The data store is not encrypted at rest.
  • Handle 404 gracefully — Initialize default values when a key doesn't exist
  • Avoid high-frequency writes — The data store is designed for configuration, not as a real-time database