> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reelevant.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Reelevant as API (JSON)

> Integrate Reelevant personalisation into your mobile app using the headless JSON API

## Why use JSON for mobile?

Mobile apps render content with **native UI components** — there's no HTML injection, no iframes, no webviews needed. The JSON API approach is the natural fit:

<CardGroup cols={2}>
  <Card title="Native rendering" icon="mobile">
    Receive structured data and render it with SwiftUI, Jetpack Compose, Flutter widgets, or React Native components.
  </Card>

  <Card title="Lightweight" icon="feather">
    A single HTTP call returns exactly the fields your app needs — no SDK to install, no JavaScript runtime overhead.
  </Card>

  <Card title="Offline-friendly" icon="wifi-slash">
    Cache JSON responses locally for offline display. Refresh when connectivity returns.
  </Card>

  <Card title="Cross-platform" icon="layer-group">
    Same endpoint works for iOS, Android, Flutter, React Native, KMP — any client that can make HTTP requests.
  </Card>
</CardGroup>

## Runner endpoint

```
GET https://reelevant.run/{workflowId}/{entrypointId}?rlvt-u={userId}
```

| Parameter      | Type  | Description                                                                 |
| -------------- | ----- | --------------------------------------------------------------------------- |
| `workflowId`   | path  | The workflow ID (visible in the workflow editor URL or integration modal)   |
| `entrypointId` | path  | The entrypoint ID within the workflow                                       |
| `rlvt-u`       | query | User identifier for personalisation — email, internal user ID, or device ID |

## Response format

The response body is the resolved template data directly — no wrapper envelope. Static fields are returned as-is and dependency fields are replaced with real values from datasources:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "headline": "Recommended for you",
  "productName": "Running Shoes Pro",
  "productPrice": 129.99,
  "productImageUrl": "https://cdn.example.com/shoes-pro.jpg"
}
```

### HTTP status codes

| Status  | Meaning                                                                  |
| ------- | ------------------------------------------------------------------------ |
| **200** | Workflow executed successfully. Body contains the JSON payload.          |
| **204** | Workflow executed but no content was produced (all branches were empty). |
| **404** | Workflow not found or not published.                                     |

## Code examples

### Swift (iOS)

```swift theme={"theme":{"light":"github-light","dark":"github-dark"}}
import Foundation

struct Recommendation: Codable {
    let headline: String
    let productName: String
    let productPrice: Double
    let productImageUrl: String
    let productUrl: String
}

func fetchRecommendation(workflowId: String, userId: String) async throws -> Recommendation? {
    let url = URL(string: "https://reelevant.run/\(workflowId)/0?rlvt-u=\(userId)")!
    let (data, response) = try await URLSession.shared.data(from: url)
    
    guard let httpResponse = response as? HTTPURLResponse else { return nil }
    guard httpResponse.statusCode == 200 else { return nil }
    
    return try JSONDecoder().decode(Recommendation.self, from: data)
}
```

### Kotlin (Android)

```kotlin theme={"theme":{"light":"github-light","dark":"github-dark"}}
import kotlinx.serialization.Serializable
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*

@Serializable
data class Recommendation(
    val headline: String,
    val productName: String,
    val productPrice: Double,
    val productImageUrl: String,
    val productUrl: String
)

suspend fun fetchRecommendation(workflowId: String, userId: String): Recommendation? {
    val client = HttpClient()
    val response = client.get("https://reelevant.run/$workflowId/0") {
        parameter("rlvt-u", userId)
    }
    return if (response.status.value == 200) response.body() else null
}
```

### Flutter (Dart)

```dart theme={"theme":{"light":"github-light","dark":"github-dark"}}
import 'dart:convert';
import 'package:http/http.dart' as http;

class Recommendation {
  final String headline;
  final String productName;
  final double productPrice;
  final String productImageUrl;
  final String productUrl;

  Recommendation.fromJson(Map<String, dynamic> json)
      : headline = json['headline'],
        productName = json['productName'],
        productPrice = (json['productPrice'] as num).toDouble(),
        productImageUrl = json['productImageUrl'],
        productUrl = json['productUrl'];
}

Future<Recommendation?> fetchRecommendation(String workflowId, String userId) async {
  final uri = Uri.parse(
    'https://reelevant.run/$workflowId/0?rlvt-u=$userId',
  );
  final response = await http.get(uri);

  if (response.statusCode == 200) {
    return Recommendation.fromJson(jsonDecode(response.body));
  }
  return null;
}
```

### React Native

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { useEffect, useState } from 'react'
import { View, Text, Image } from 'react-native'

type Recommendation = {
  headline: string
  productName: string
  productPrice: number
  productImageUrl: string
  productUrl: string
}

function RecommendationCard({ workflowId, userId }: { workflowId: string; userId: string }) {
  const [data, setData] = useState<Recommendation | null>(null)

  useEffect(() => {
    fetch(`https://reelevant.run/${workflowId}/0?rlvt-u=${userId}`)
      .then(res => res.ok ? res.json() : null)
      .then(setData)
  }, [workflowId, userId])

  if (!data) return null

  return (
    <View>
      <Text style={{ fontSize: 18, fontWeight: 'bold' }}>{data.headline}</Text>
      <Image source={{ uri: data.productImageUrl }} style={{ width: 200, height: 200 }} />
      <Text>{data.productName} — ${data.productPrice}</Text>
    </View>
  )
}
```

## Identity management

Pass a stable user identifier in the `rlvt-u` query parameter:

| Identifier type              | When to use                                       |
| ---------------------------- | ------------------------------------------------- |
| Internal user ID or email    | User is logged in — best personalisation accuracy |
| Device ID (IDFV, Android ID) | Anonymous users — maintains session continuity    |
| Custom ID                    | Any stable identifier your app generates          |

The same identity system powers all Reelevant channels (web, email, push), so personalisation is consistent across touchpoints.

## Best practices

<AccordionGroup>
  <Accordion title="Cache responses for offline use">
    Store the last successful JSON response locally (e.g., UserDefaults, SharedPreferences, or a local database). Display cached data when offline and refresh when connectivity returns.
  </Accordion>

  <Accordion title="Handle empty responses gracefully">
    A `204` response means no personalised content is available. Display a sensible default or hide the personalisation zone entirely.
  </Accordion>

  <Accordion title="Call early, render when ready">
    Initiate the API call as early as possible (e.g., on app launch or screen appear) so data is ready by the time the user scrolls to the personalised zone.
  </Accordion>

  <Accordion title="Use typed models">
    Define a struct/class matching your JSON Template schema. This catches schema mismatches at compile time and makes refactoring safe.
  </Accordion>
</AccordionGroup>

## JSON Template configuration

JSON Templates are configured in the Reelevant platform. See the [Websites > API (JSON) > Technical Integration](/developer-docs/web-integration/api-json/integration) page for full details on:

* Template schema definition
* Variable types (scalar vs array)
* Template management API
* Publish-time validation
