API Platform

Production docs for integrating RelayForge into tourism websites: tours, availability, bookings, content, forms, media delivery, and multilingual translations.

WordPress plugin

Prefer a no-code WordPress install? Download the RelayForge WordPress plugin, add your API key, and publish tours, packages, reviews, availability, and forms with shortcodes.

Download WordPress plugin

Quickstart

  1. Generate tenant API key in RelayForge admin.
  2. In /dashboard/settings, set Public website URL (used by Translate website now crawler).
  3. Fetch tours and render cards on the website.
  4. Fetch availability for tour/date selectors.
  5. Submit inquiry or booking-intent via forms endpoint.
  6. Load tenant locales and generate per-page translations before render.
const headers = { "X-API-Key": "rf_live_your_api_key" };
const localeConfig = await fetch("https://relay.forgelabspro.com/api/v1/translations/locales", { headers }).then(r => r.json());
const tours = await fetch("https://relay.forgelabspro.com/api/v1/tours?limit=6", { headers }).then(r => r.json());
const availability = await fetch("https://relay.forgelabspro.com/api/v1/availability?tourId=tour_1", { headers }).then(r => r.json());
const translatedHome = await fetch("https://relay.forgelabspro.com/api/v1/translations/generate", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ pageId: "home", pageSlug: "home", sourceText: "Welcome", sourceLocale: "en", sourceVersion: "v1" }),
}).then(r => r.json());
await fetch("https://relay.forgelabspro.com/api/v1/forms/submit", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ formSlug: "inquiry-form", response: { fullName: "Amina" } }),
});

Localization flow (full integration)

Use this exact sequence on tenant websites to show the right languages and render translated content.

Admin note: Translate website now crawls from the tenant Public website URL saved in/dashboard/settings. If that URL is not set or crawl fails, RelayForge falls back to internal published content (blogs, tours, destinations).

  1. Call GET /api/v1/translations/locales once on app startup.
  2. Build the language switcher from enabledLocales.
  3. If user-selected locale is not enabled, fallback to defaultLocale.
  4. For each page, call POST /api/v1/translations/generate with page source text and version.
  5. Render data.translations[currentLocale]; fallback to source text when missing.
  6. Bump sourceVersion when source content changes to force fresh translations.

Copy-paste loader example

const API_BASE = "https://relay.forgelabspro.com";
const headers = { "X-API-Key": "rf_live_your_api_key" };

export async function getTenantLocales() {
  const res = await fetch(API_BASE + "/api/v1/translations/locales", { headers });
  const json = await res.json();
  if (!res.ok || !json.ok) throw new Error(json.error || "Failed to load locales");
  return json.data;
}

export async function getTranslatedPageContent({
  pageId,
  pageSlug,
  sourceText,
  sourceLocale = "en",
  sourceVersion,
  locale,
}) {
  const localeConfig = await getTenantLocales();
  const enabled = localeConfig.enabledLocales || [];
  const currentLocale = enabled.includes(locale) ? locale : localeConfig.defaultLocale || sourceLocale;

  const res = await fetch(API_BASE + "/api/v1/translations/generate", {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({
      pageId,
      pageSlug,
      sourceText,
      sourceLocale,
      sourceVersion,
      targetLocales: [currentLocale],
    }),
  });

  const json = await res.json();
  if (!res.ok || !json.ok) throw new Error(json.error || "Translation failed");

  return {
    locale: currentLocale,
    content: json.data?.translations?.[currentLocale] || sourceText,
    enabledLocales: enabled,
    defaultLocale: localeConfig.defaultLocale,
  };
}

Authentication

Supported auth inputs: `X-API-Key`, `Authorization: Bearer`, or `?apiKey=` query.

curl "https://relay.forgelabspro.com/api/v1/tours?limit=10" \
  -H "X-API-Key: rf_live_your_api_key"

Error handling

Common API errors

FieldTypeNotes
400Bad RequestInvalid query/body (status, date, malformed JSON)
401UnauthorizedMissing, invalid, or revoked API key
404Not FoundResource/file not found
500Server ErrorUnexpected backend failure
if (!res.ok) {
  const err = await res.json().catch(() => ({ error: "Request failed" }));
  throw new Error(err.error || "Request failed");
}
GET

/api/v1/tours

Returns active published tours with package metadata, pricing, and language info.

Query params

FieldTypeNotes
limitnumber1-100, default 50
offsetnumber>= 0, default 0

Response schema

FieldTypeNotes
okbooleanTrue on success
data.tours[]arrayList of published tours
data.tours[].idstringTour ID
data.tours[].titlestringTour title
data.tours[].pricenumberBase price
data.tours[].packageMetaobject|nullEnriched package details
data.totalnumberTotal matching records

Endpoint errors

FieldTypeNotes
401ErrorInvalid or revoked API key
500ErrorFailed to fetch tours

curl example

curl "https://relay.forgelabspro.com/api/v1/tours?limit=6&offset=0" \
  -H "X-API-Key: rf_live_your_api_key"

JavaScript example

const res = await fetch("https://relay.forgelabspro.com/api/v1/tours?limit=6&offset=0", {
  headers: { "X-API-Key": "rf_live_your_api_key" },
});
const payload = await res.json();
const tours = payload.data?.tours || [];

Sample response

{
  "ok": true,
  "data": {
    "tours": [{ "id": "tour_1", "title": "Masai Mara 3 Days", "price": 1200 }],
    "total": 42,
    "limit": 6,
    "offset": 0
  }
}
GET

/api/v1/availability

Returns per-date inventory with remaining seats for selected tours/date windows.

Query params

FieldTypeNotes
tourIdstringOptional tour filter
fromstringOptional YYYY-MM-DD
tostringOptional YYYY-MM-DD
limitnumber1-100
offsetnumber>= 0

Response schema

FieldTypeNotes
data.availability[]arrayAvailability rows
data.availability[].datestringISO date
data.availability[].totalSlotsnumberTotal seats
data.availability[].bookedSlotsnumberBooked seats
data.availability[].remainingSlotsnumberComputed remaining seats

Endpoint errors

FieldTypeNotes
400ErrorInvalid date format (use YYYY-MM-DD)
401ErrorInvalid or revoked API key
500ErrorFailed to fetch availability

curl example

curl "https://relay.forgelabspro.com/api/v1/availability?tourId=tour_1&from=2026-09-01&to=2026-09-30" \
  -H "Authorization: Bearer rf_live_your_api_key"

JavaScript example

const url = "https://relay.forgelabspro.com/api/v1/availability?tourId=tour_1&from=2026-09-01&to=2026-09-30";
const res = await fetch(url, { headers: { "X-API-Key": "rf_live_your_api_key" } });
const data = await res.json();

Sample response

{
  "ok": true,
  "data": {
    "availability": [{ "date": "2026-09-12", "remainingSlots": 8 }]
  }
}
GET

/api/v1/bookings

Returns customer bookings for CRM syncs and operations dashboards.

Query params

FieldTypeNotes
statusstringpending | confirmed | cancelled (optional)
limitnumber1-100
offsetnumber>= 0

Response schema

FieldTypeNotes
data.bookings[]arrayBooking list
data.bookings[].idstringBooking ID
data.bookings[].customerNamestringLead traveler name
data.bookings[].numberOfPeoplenumberParty size
data.bookings[].statusstringBooking status

Endpoint errors

FieldTypeNotes
400ErrorInvalid status filter
401ErrorInvalid or revoked API key
500ErrorFailed to fetch bookings

curl example

curl "https://relay.forgelabspro.com/api/v1/bookings?status=confirmed&limit=20" \
  -H "X-API-Key: rf_live_your_api_key"

JavaScript example

const res = await fetch("https://relay.forgelabspro.com/api/v1/bookings?status=confirmed&limit=20", {
  headers: { "X-API-Key": "rf_live_your_api_key" },
});
const bookings = (await res.json()).data?.bookings || [];

Sample response

{
  "ok": true,
  "data": {
    "bookings": [{ "id": "bk_1", "customerName": "Amina", "status": "confirmed" }]
  }
}
GET

/api/v1/destinations | /blogs | /reviews

Website content feeds for destination pages, SEO blogs, and approved reviews.

Query params

FieldTypeNotes
limitnumberFor list endpoints
offsetnumberFor list endpoints
tourIdstringOptional on /reviews

Response schema

FieldTypeNotes
data.destinations[]arrayDestination records
data.blogs[]arrayPublished blog records
data.reviews[]arrayApproved review records

Endpoint errors

FieldTypeNotes
401ErrorInvalid or revoked API key
500ErrorFailed to fetch content

curl example

curl "https://relay.forgelabspro.com/api/v1/destinations?limit=12" -H "X-API-Key: rf_live_your_api_key"
curl "https://relay.forgelabspro.com/api/v1/blogs?limit=5" -H "X-API-Key: rf_live_your_api_key"
curl "https://relay.forgelabspro.com/api/v1/reviews?tourId=tour_1&limit=10" -H "X-API-Key: rf_live_your_api_key"

JavaScript example

const headers = { "X-API-Key": "rf_live_your_api_key" };
const [destinations, blogs, reviews] = await Promise.all([
  fetch("https://relay.forgelabspro.com/api/v1/destinations?limit=12", { headers }).then(r => r.json()),
  fetch("https://relay.forgelabspro.com/api/v1/blogs?limit=5", { headers }).then(r => r.json()),
  fetch("https://relay.forgelabspro.com/api/v1/reviews?limit=10", { headers }).then(r => r.json()),
]);

Sample response

{
  "ok": true,
  "data": {
    "destinations": [{ "slug": "masai-mara" }],
    "blogs": [{ "slug": "best-time" }],
    "reviews": [{ "rating": 5 }]
  }
}
POST

/api/v1/forms + /api/v1/forms/submit

List published forms and submit inquiries/lead payloads.

Query params

FieldTypeNotes
limit, offsetnumberFor GET /api/v1/forms

Request body

FieldTypeNotes
formIdstringOptional when formSlug is present
formSlugstringOptional when formId is present
responseobjectForm answer payload

Response schema

FieldTypeNotes
GET data.forms[]arrayPublished forms + embed config
POST data.submissionIdstringCreated submission ID

Endpoint errors

FieldTypeNotes
400ErrorInvalid JSON or missing required form payload
401ErrorInvalid or revoked API key

curl example

curl "https://relay.forgelabspro.com/api/v1/forms?limit=10" -H "X-API-Key: rf_live_your_api_key"

curl -X POST "https://relay.forgelabspro.com/api/v1/forms/submit" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: rf_live_your_api_key" \
  -d '{
    "formSlug": "inquiry-form",
    "response": { "fullName": "Amina Kariuki", "email": "[email protected]" }
  }'

JavaScript example

await fetch("https://relay.forgelabspro.com/api/v1/forms/submit", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "rf_live_your_api_key",
  },
  body: JSON.stringify({
    formSlug: "inquiry-form",
    response: { fullName: "Amina Kariuki", email: "[email protected]" },
  }),
});

Sample response

{
  "ok": true,
  "data": {
    "submissionId": "sub_123"
  }
}
GET

/api/v1/translations/locales

Returns tenant language configuration for locale switchers and fallback behavior.

Response schema

FieldTypeNotes
okbooleanTrue on success
data.defaultLocalestringDefault/fallback locale
data.enabledLocales[]arrayEnabled locales selected in tenant settings
data.languageCountnumberCount of enabled locales
data.autoTranslateOnPageViewbooleanWhether first page request should trigger translation

Endpoint errors

FieldTypeNotes
401ErrorInvalid or revoked API key
500ErrorFailed to load tenant locales

curl example

curl "https://relay.forgelabspro.com/api/v1/translations/locales" \
  -H "X-API-Key: rf_live_your_api_key"

JavaScript example

const res = await fetch("https://relay.forgelabspro.com/api/v1/translations/locales", {
  headers: { "X-API-Key": "rf_live_your_api_key" },
});
const payload = await res.json();
const locales = payload.data?.enabledLocales || [];
const defaultLocale = payload.data?.defaultLocale || "en";

Sample response

{
  "ok": true,
  "data": {
    "defaultLocale": "en",
    "enabledLocales": ["en", "fr", "de", "es", "it", "sw"],
    "languageCount": 6,
    "autoTranslateOnPageView": true
  }
}
POST

/api/v1/translations/generate

Translates and stores page content in RelayForge DB. Reuses existing translations for unchanged source hashes.

Request body

FieldTypeNotes
pageIdstringStable page key, e.g. home/about/tour-123
pageSlugstringPage slug
sourceTextstringSource content text to translate
sourceLocalestringSource locale, usually en
sourceVersionstringVersion/hash marker for cache invalidation
targetLocalesstring[]Optional; omit to use tenant configured locales

Response schema

FieldTypeNotes
data.translationsobjectMap keyed by locale code
data.created[]arrayLocales translated during this call
data.failed[]arrayLocales that failed with error details
data.sourceHashstringComputed hash used for dedupe

Endpoint errors

FieldTypeNotes
400ErrorInvalid body or translation provider failure
401ErrorInvalid or revoked API key

curl example

curl -X POST "https://relay.forgelabspro.com/api/v1/translations/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: rf_live_your_api_key" \
  -d '{
    "pageId": "home",
    "pageSlug": "home",
    "sourceText": "Welcome to our safari tours",
    "sourceLocale": "en",
    "sourceVersion": "v1"
  }'

JavaScript example

const res = await fetch("https://relay.forgelabspro.com/api/v1/translations/generate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "rf_live_your_api_key",
  },
  body: JSON.stringify({
    pageId: "home",
    pageSlug: "home",
    sourceText: "Welcome to our safari tours",
    sourceLocale: "en",
    sourceVersion: "v1",
  }),
});
const payload = await res.json();
const translated = payload.data?.translations?.fr || null;

Sample response

{
  "ok": true,
  "data": {
    "pageId": "home",
    "sourceLocale": "en",
    "sourceVersion": "v1",
    "sourceHash": "sha256_hash",
    "translations": { "fr": "Bienvenue dans nos safaris" },
    "created": ["fr"],
    "failed": []
  }
}
GET

/api/v1/website-media + /api/v1/media/view/:fileId

Fetch media catalog metadata and stream secured asset bytes.

Query params

FieldTypeNotes
fileIdpath paramRequired for /media/view/:fileId

Response schema

FieldTypeNotes
data.images[]arrayMedia catalog items
/media/viewbinaryInline streamed file bytes

Endpoint errors

FieldTypeNotes
401ErrorInvalid or revoked API key
404ErrorMedia file not found

curl example

curl "https://relay.forgelabspro.com/api/v1/website-media" -H "X-API-Key: rf_live_your_api_key"
curl "https://relay.forgelabspro.com/api/v1/media/view/file_abc123" -H "X-API-Key: rf_live_your_api_key" --output hero.webp

JavaScript example

const catalog = await fetch("https://relay.forgelabspro.com/api/v1/website-media", {
  headers: { "X-API-Key": "rf_live_your_api_key" },
}).then(r => r.json());

const fileUrl = "https://relay.forgelabspro.com/api/v1/media/view/" + catalog.data.images[0].fileId;

Sample response

HTTP 200 binary stream
Content-Type: image/webp

Security and production checklist

  • Keep API keys on your backend proxy whenever possible.
  • Rotate API keys regularly and after team member changes.
  • Cache GET endpoints (`tours`, `destinations`, `blogs`) aggressively.
  • Implement retries with backoff for intermittent `500` errors.
  • Validate user-submitted fields before calling forms submit endpoint.