RAPI (REST API)

RAPI enables you to deploy auto-generated REST APIs based on your dcupl data models. Get OpenAPI-compliant endpoints with authentication, filtering, sorting, and pagination out of the box.

Overview

flowchart LR
  subgraph Console
    T[API Template]
    D[Deploy]
  end

  subgraph Runner
    I[RAPI Instance]
    E[Endpoints]
  end

  subgraph Client["Client Apps"]
    W[Web App]
    M[Mobile App]
    B[Backend]
  end

  T --> D
  D --> I
  I --> E
  E --> W
  E --> M
  E --> B

RAPI provides:

  • Auto-generated endpoints for all dcupl models
  • OpenAPI/Swagger documentation for API exploration
  • API key authentication with multiple keys support
  • v-Guard caching for version-based cache invalidation
  • Real-time monitoring of status and performance

Setting Up RAPI

Step 1: Create an API Template

  1. Navigate to REST API in your project
  2. Click Create RAPI
  3. Fill in the configuration:
customer-api.json
{
  "key": "customer-api",
  "name": "Customer API",
  "description": "REST API for customer data",
  "vGuardEnabled": false,
  "auth": [
    {
      "type": "api-key",
      "value": "your-secure-api-key-here"
    }
  ],
  "dcuplConfig": {
    "projectId": "your-project-id",
    "apiKey": "your-sdk-readonly-key",
    "processOptions": {
      "applicationKey": "production",
      "environmentKeys": ["prod"]
    }
  }
}

Step 2: Deploy to Runner

  1. Select your API template
  2. Click Deploy
  3. Choose target runner instance
  4. Wait for status to show Ready

Step 3: Access Your API

Your API is now available at:

{runner-url}/instance/{api-key}/

Authentication

API Key Authentication

RAPI supports API key authentication via query parameter or header:

curl 'https://runner.example.com/instance/customer-api/customer?api-key=your-api-key'
curl 'https://runner.example.com/instance/customer-api/customer' \
  -H 'api-key: your-api-key'

Best Practice: Use header-based authentication in production as it's more secure and doesn't expose keys in URLs or logs.

Multiple API Keys

Configure multiple keys for different consumers:

multi-key-config.json
{
  "auth": [
    { "type": "api-key", "value": "key-for-web-app" },
    { "type": "api-key", "value": "key-for-mobile-app" },
    { "type": "api-key", "value": "key-for-partner-integration" }
  ]
}

Security Best Practices

  • Generate strong, random keys (32+ characters)
  • Rotate keys periodically
  • Use different keys per environment
  • Store keys in environment variables, never in code
  • Monitor key usage for anomalies

API Endpoints

Status Endpoint

Check if the RAPI instance is ready:

status-request.sh
GET /instance/customer-api/status?api-key=your-key
status-response.json
{
  "status": "ready",
  "version": "abc123",
  "memory": {
    "used": 50,
    "max": 512
  }
}

Status values:

Status Description
waiting Instance starting up
processing Loading data
ready Accepting requests
updating Reloading configuration
error Initialization failed

Overview Endpoint

Get available models and endpoints:

overview-request.sh
GET /instance/customer-api/overview?api-key=your-key
overview-response.json
{
  "key": "customer-api",
  "name": "Customer API",
  "models": ["customer", "order", "product"],
  "endpoints": ["/customer", "/customer/:id", "/order", "/product"]
}

Swagger Documentation

Access interactive API documentation:

GET /instance/customer-api/swagger?api-key=your-key

Returns OpenAPI 3.0 specification. Open in browser for interactive Swagger UI.

Model Endpoints

For each dcupl model, RAPI generates REST endpoints:

List Items

list-customers.sh
GET /instance/customer-api/customer?api-key=your-key
list-response.json
[
  { "key": "c1", "name": "John Doe", "email": "john@example.com" },
  { "key": "c2", "name": "Jane Smith", "email": "jane@example.com" }
]

Get Single Item

get-customer.sh
GET /instance/customer-api/customer/c1?api-key=your-key
get-response.json
{
  "key": "c1",
  "name": "John Doe",
  "email": "john@example.com",
  "orders": [{ "key": "o1", "total": 150.0 }]
}

Filtering

Filter results using query parameters:

filter-examples.sh
# Simple equality
GET /customer?filter[status]=active

# Greater than
GET /customer?filter[age][$gt]=25

# Less than
GET /customer?filter[createdAt][$lt]=2025-01-01

# In array
GET /customer?filter[country][$in]=US,UK,DE

# Contains (string search)
GET /customer?filter[name][$contains]=john

Sorting

sort-examples.sh
# Sort ascending
GET /customer?sort=name

# Sort descending
GET /customer?sort=-createdAt

# Multi-field sort
GET /customer?sort=country,-name

Pagination

pagination-examples.sh
# Limit results
GET /customer?limit=10

# With offset
GET /customer?limit=10&offset=20

# Page-based
GET /customer?page=3&limit=10

v-Guard Caching

Enable version-based cache invalidation for improved performance:

vguard-config.json
{
  "vGuardEnabled": true
}

How v-Guard Works

sequenceDiagram
  participant Client
  participant RAPI
  participant Cache

  Client->>RAPI: GET /customer?v=abc123
  RAPI->>Cache: Check version
  alt Version matches
    Cache-->>Client: Return cached data
  else Version mismatch
    RAPI->>RAPI: Reload data
    RAPI-->>Client: Return fresh data
  end

Client Integration

vguard-client.ts
class RAPIClient {
  private version: string | null = null;

  async fetch(endpoint: string) {
    const params = new URLSearchParams();
    if (this.version) {
      params.set('v', this.version);
    }

    const response = await fetch(`${this.baseUrl}/${endpoint}?${params}`, {
      headers: { 'api-key': this.apiKey },
    });

    // Update version from response
    const newVersion = response.headers.get('x-dcupl-version');
    if (newVersion) {
      this.version = newVersion;
    }

    return response.json();
  }
}

When to enable v-Guard:

  • Data changes infrequently
  • Performance is critical
  • Client can track version

When to disable:

  • Real-time data required
  • Per-request authentication needed

Client Integration Examples

JavaScript/TypeScript SDK

rapi-client.ts
class RAPIClient {
  constructor(
    private baseUrl: string,
    private instanceKey: string,
    private apiKey: string
  ) {}

  private async request<T>(path: string, options?: RequestInit): Promise<T> {
    const response = await fetch(`${this.baseUrl}/instance/${this.instanceKey}/${path}`, {
      ...options,
      headers: {
        'api-key': this.apiKey,
        'Content-Type': 'application/json',
        ...options?.headers,
      },
    });

    if (!response.ok) {
      throw new Error(`RAPI Error: ${response.status} ${response.statusText}`);
    }

    return response.json();
  }

  // List items with optional filtering
  async list<T>(
    model: string,
    options?: {
      filter?: Record<string, any>;
      sort?: string;
      limit?: number;
      offset?: number;
    }
  ): Promise<T[]> {
    const params = new URLSearchParams();

    if (options?.filter) {
      Object.entries(options.filter).forEach(([key, value]) => {
        params.append(`filter[${key}]`, String(value));
      });
    }
    if (options?.sort) params.append('sort', options.sort);
    if (options?.limit) params.append('limit', String(options.limit));
    if (options?.offset) params.append('offset', String(options.offset));

    const query = params.toString();
    return this.request(`${model}${query ? `?${query}` : ''}`);
  }

  // Get single item
  async get<T>(model: string, id: string): Promise<T> {
    return this.request(`${model}/${id}`);
  }

  // Check status
  async status(): Promise<{ status: string; version: string }> {
    return this.request('status');
  }
}

// Usage
const client = new RAPIClient(
  'https://runner.example.com',
  'customer-api',
  process.env.RAPI_API_KEY!
);

const customers = await client.list('customer', {
  filter: { status: 'active' },
  sort: '-createdAt',
  limit: 10,
});

React Hook

use-rapi.ts
import { useState, useEffect } from 'react';

function useRAPI<T>(model: string, options?: {
  filter?: Record<string, any>;
  sort?: string;
}) {
  const [data, setData] = useState<T[] | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const params = new URLSearchParams();

        if (options?.filter) {
          Object.entries(options.filter).forEach(([key, value]) => {
            params.append(`filter[${key}]`, String(value));
          });
        }
        if (options?.sort) params.append('sort', options.sort);

        const response = await fetch(
          `${process.env.REACT_APP_RAPI_URL}/${model}?${params}`,
          { headers: { 'api-key': process.env.REACT_APP_RAPI_KEY! } }
        );

        if (!response.ok) throw new Error('Failed to fetch');

        setData(await response.json());
      } catch (err) {
        setError(err instanceof Error ? err : new Error('Unknown error'));
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [model, JSON.stringify(options)]);

  return { data, loading, error };
}

// Usage in component
function CustomerList() {
  const { data, loading, error } = useRAPI<Customer>('customer', {
    filter: { status: 'active' },
    sort: 'name'
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {data?.map(customer => (
        <li key={customer.key}>{customer.name}</li>
      ))}
    </ul>
  );
}

Configuration Reference

Template Structure

template-types.ts
interface RAPITemplate {
  key: string; // Unique identifier (alphanumeric, -, _)
  name: string; // Display name
  description?: string; // Optional description
  version?: string; // Optional version string
  vGuardEnabled: boolean; // Enable version-based caching
  auth: AuthConfig[]; // Authentication configuration
  dcuplConfig: DcuplConfig; // dcupl integration config
}

interface AuthConfig {
  type: 'api-key';
  value: string;
}

interface DcuplConfig {
  projectId: string;
  apiKey: string; // SDK readonly key
  processOptions: {
    applicationKey?: string; // Select application preset
    environmentKeys?: string[]; // Select environments
    resourceTags?: string[][]; // Filter by resource tags
    variables?: Record<string, any>;
  };
  loaderFetchOptions?: {
    baseUrl?: string; // Custom CDN URL
    loaderFileName?: string; // Custom loader filename
  };
}

Troubleshooting

Instance Status: Error

Causes:

  • Invalid SDK API key
  • Project not accessible
  • Data loading failed

Solutions:

  1. Verify SDK API key in template
  2. Check project permissions
  3. Review runner logs

Endpoints Not Responding

Causes:

  • Instance not deployed
  • Runner offline
  • Incorrect API key

Solutions:

  1. Check instance status
  2. Verify runner connectivity
  3. Test API key authentication

High Memory Usage

Causes:

  • Large datasets
  • Too many models loaded

Solutions:

  1. Filter resources by tags
  2. Use specific application presets
  3. Increase runner memory limits

What's Next?