# Lahjty Partner API Guide

Last reviewed: 2026-09-04

Base URL: https://www.lahjty.com/api/v1

This is the canonical machine-readable integration guide. The localized human documentation is at https://www.lahjty.com/en/api-docs and https://www.lahjty.com/ar/api-docs. The authoritative schemas are at https://www.lahjty.com/api/v1/openapi and live values are at https://www.lahjty.com/api/v1/capabilities.

## Authentication

Send partner keys only from a trusted server:

~~~http
Authorization: Bearer lhj_live_replace_with_your_key
Content-Type: application/json
Idempotency-Key: one-unique-value-per-logical-operation
~~~

Never expose a partner key in a browser, mobile binary, public prompt, log, screenshot, analytics event, or repository. Every billable POST requires an Idempotency-Key of 1–200 characters.

## Server helpers

The endpoint examples below use this TypeScript helper:

~~~ts
async function lahjty(
  path: string,
  init: { method?: "GET" | "POST"; body?: unknown } = {},
) {
  const method = init.method ?? "GET";
  const response = await fetch(
    "https://www.lahjty.com/api/v1" + path,
    {
      method,
      headers: {
        Authorization: "Bearer " + process.env.LAHJTY_API_KEY,
        ...(method === "POST"
          ? {
              "Content-Type": "application/json",
              "Idempotency-Key": crypto.randomUUID(),
            }
          : {}),
      },
      body: init.body === undefined ? undefined : JSON.stringify(init.body),
    },
  );
  const result = await response.json();
  if (!response.ok) throw new Error(result.code + ": " + result.detail);
  return result;
}
~~~

Python:

~~~python
import os
import uuid
import requests

def lahjty(path, method="GET", payload=None):
    headers = {"Authorization": "Bearer " + os.environ["LAHJTY_API_KEY"]}
    if method == "POST":
        headers["Content-Type"] = "application/json"
        headers["Idempotency-Key"] = str(uuid.uuid4())
    response = requests.request(
        method,
        "https://www.lahjty.com/api/v1" + path,
        headers=headers,
        json=payload,
        timeout=90,
    )
    response.raise_for_status()
    return response.json()
~~~

## 1. Capabilities

GET /capabilities is public, free, and authoritative for supported values, credit costs, scopes, models, and rate limits.

~~~bash
curl https://www.lahjty.com/api/v1/capabilities
~~~

~~~ts
const capabilities = await lahjty("/capabilities");
~~~

~~~python
capabilities = lahjty("/capabilities")
~~~

## 2. Arabic and English copy

POST /copy-generations costs 2 credits for ad copy or headlines.

~~~ts
const copy = await lahjty("/copy-generations", {
  method: "POST",
  body: {
    type: "ad_copy",
    description: "Weekend offer: 20% off specialty coffee subscriptions",
    output_language: "ar",
    market: "SA",
    dialect: "saudi",
    platform: "whatsapp",
    tone: "warm",
    framework: "aida",
    audience: "specific",
    specific_audience: "Young professionals in Riyadh",
    required_keywords: ["قهوة مختصة"],
    excluded_keywords: ["مجاني"],
    call_to_action: "اطلب الآن",
    external_customer_id: "merchant-91",
  },
});
~~~

~~~python
copy = lahjty("/copy-generations", "POST", {
    "type": "ad_copy",
    "description": "Weekend offer: 20% off specialty coffee subscriptions",
    "output_language": "ar",
    "market": "SA",
    "dialect": "saudi",
    "platform": "whatsapp",
    "framework": "aida",
    "required_keywords": ["قهوة مختصة"],
    "excluded_keywords": ["مجاني"],
    "call_to_action": "اطلب الآن",
})
~~~

For English copy or headlines, set output_language to en, include an ISO market such as AE, and omit dialect. English is supported; English dialects are not. The v1 response returns `"dialect": "none"` as a backward-compatible string sentinel; it is not an English dialect selector. For headlines, set type to headlines.

For Arabic dialect conversion, set type to dialect_conversion, pass the approved Arabic text as description, and include source_dialect and target_dialect. Conversion costs 1 credit.

~~~json
{
  "type": "dialect_conversion",
  "description": "قهوتك المفضلة توصلك لين باب البيت. اطلب الآن.",
  "output_language": "ar",
  "market": "IQ",
  "source_dialect": "saudi",
  "target_dialect": "iraqi",
  "platform": "whatsapp"
}
~~~

## 3. Campaign image

POST /image-generations costs 5 credits and returns a durable private media URL.

~~~ts
const image = await lahjty("/image-generations", {
  method: "POST",
  body: {
    prompt: "Premium Saudi specialty coffee campaign",
    type: "product_showcase",
    output_language: "ar",
    market: "SA",
    dialect: "saudi",
    platform: "instagram",
    style: "photorealistic",
    aspect_ratio: "1:1",
    resolution: "1K",
    visual_keywords: ["Saudi coffee cup", "coffee beans"],
    excluded_visuals: ["people", "logos", "text"],
    include_text: false,
  },
});
~~~

~~~python
image = lahjty("/image-generations", "POST", {
    "prompt": "Premium Saudi specialty coffee campaign",
    "type": "product_showcase",
    "output_language": "ar",
    "market": "SA",
    "dialect": "saudi",
    "platform": "instagram",
    "style": "photorealistic",
    "aspect_ratio": "1:1",
    "resolution": "1K",
    "include_text": False,
})
~~~

For model-rendered image text, set include_text to true, supply overlay_text, and set overlay_text_language to ar, en, or auto. Model-rendered spelling is probabilistic and must be reviewed.

## 4. Arabic speech

POST /speech-generations costs 5 credits and returns a hosted MP3. It reads the exact Arabic transcript; it is not a copywriting prompt.

~~~ts
const speech = await lahjty("/speech-generations", {
  method: "POST",
  body: {
    text: "طلبك صار جاهز، ونوصله لك بكرة بإذن الله.",
    dialect: "gulf",
    tone: "warm",
    platform: "whatsapp",
    delivery: "natural",
    content_type: "marketing",
    voice_profile: "warm_storyteller",
    external_customer_id: "merchant-91",
  },
});
~~~

~~~python
speech = lahjty("/speech-generations", "POST", {
    "text": "طلبك صار جاهز، ونوصله لك بكرة بإذن الله.",
    "dialect": "gulf",
    "tone": "warm",
    "platform": "whatsapp",
    "delivery": "natural",
    "content_type": "marketing",
    "voice_profile": "warm_storyteller",
})
~~~

## 5. Complete campaign

POST /campaigns costs 2 credits for copy, plus 5 for image and 5 for speech when enabled.

~~~ts
const campaign = await lahjty("/campaigns", {
  method: "POST",
  body: {
    external_id: "campaign-1849",
    external_customer_id: "merchant-91",
    brief: {
      description: "Weekend specialty coffee subscription offer",
      offer: "20% off through Saturday",
      objective: "Drive WhatsApp orders",
      output_language: "ar",
      market: "SA",
      dialect: "saudi",
      platform: "whatsapp",
      tone: "warm",
      required_keywords: ["قهوة مختصة"],
      excluded_keywords: ["مجاني"],
      call_to_action: "اطلب الآن",
    },
    deliverables: {
      copy: { type: "ad_copy", framework: "aida" },
      image: {
        enabled: true,
        type: "product_showcase",
        style: "photorealistic",
        aspect_ratio: "1:1",
        resolution: "1K",
        include_text: false,
      },
      speech: {
        enabled: true,
        voice_profile: "confident_announcer",
        delivery: "natural",
        content_type: "marketing",
      },
    },
  },
});
~~~

~~~python
campaign = lahjty("/campaigns", "POST", {
    "external_id": "campaign-1849",
    "external_customer_id": "merchant-91",
    "brief": {
        "description": "Weekend specialty coffee subscription offer",
        "output_language": "ar",
        "market": "SA",
        "dialect": "saudi",
        "platform": "whatsapp",
        "call_to_action": "اطلب الآن",
    },
    "deliverables": {
        "copy": {"type": "ad_copy", "framework": "aida"},
        "image": {"enabled": False},
        "speech": {"enabled": False},
    },
})
~~~

## 6. WhatsApp template draft

POST /whatsapp-template-drafts costs 2 credits. It returns an UNSUBMITTED Meta-shaped payload. Lahjty does not submit the template, guarantee Meta approval, or deliver WhatsApp messages.

~~~ts
const draft = await lahjty("/whatsapp-template-drafts", {
  method: "POST",
  body: {
    name: "order_shipped",
    category: "UTILITY",
    output_language: "ar",
    language_code: "ar",
    market: "SA",
    dialect: "saudi",
    purpose: "Tell a customer that their order has shipped",
    customer_action_context: "The recipient placed the referenced order",
    variables: [
      { name: "customer_name", example: "نورة" },
      { name: "order_number", example: "A-1842" },
      { name: "delivery_date", example: "الخميس" },
    ],
    header: "TEXT",
    footer: true,
    buttons: [{ type: "QUICK_REPLY", text: "تواصل معنا" }],
    external_customer_id: "merchant-91",
  },
});
~~~

~~~python
draft = lahjty("/whatsapp-template-drafts", "POST", {
    "name": "order_shipped",
    "category": "UTILITY",
    "output_language": "ar",
    "language_code": "ar",
    "market": "SA",
    "dialect": "saudi",
    "purpose": "Tell a customer that their order has shipped",
    "customer_action_context": "The recipient placed the referenced order",
    "variables": [
        {"name": "customer_name", "example": "نورة"},
        {"name": "order_number", "example": "A-1842"},
    ],
    "header": "TEXT",
    "footer": True,
})
~~~

UTILITY needs customer_action_context. MARKETING needs opt_in_context. AUTHENTICATION uses the authentication settings. Every variable needs a realistic example. English templates are supported; omit dialect when output_language is en.

## 7. Report a WhatsApp template outcome

POST /whatsapp-template-drafts/{draft_id}/outcomes costs 0 credits. Submit the generated meta_payload through the partner's WhatsApp provider, then report each Meta state change.

~~~ts
const outcome = await lahjty(
  "/whatsapp-template-drafts/wtd_01jexample/outcomes",
  {
    method: "POST",
    body: {
      outcome: "REJECTED",
      meta_template_id: "123456789012345",
      provider_event_id: "provider-meta-event-3001",
      submitted_payload_hash:
        "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
      category: "UTILITY",
      reason: "Meta rejection reason",
      occurred_at: "2026-09-04T09:30:00Z",
    },
  },
);
~~~

~~~python
outcome = lahjty(
    "/whatsapp-template-drafts/wtd_01jexample/outcomes",
    "POST",
    {
        "outcome": "REJECTED",
        "meta_template_id": "123456789012345",
        "provider_event_id": "provider-meta-event-3001",
        "submitted_payload_hash":
            "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
        "category": "UTILITY",
        "reason": "Meta rejection reason",
    },
)
~~~

Outcome reports are partner-supplied measurement data, not proof of a Meta decision. An unchanged draft reported as rejected becomes a refund candidate with `PENDING_VERIFICATION`; the report itself never changes the balance. Lahjty applies the 2-credit refund exactly once only after an administrator verifies trusted Meta/provider evidence. Edited submissions are recorded but are not candidates.

## 8. Usage

GET /usage costs 0 credits.

~~~ts
const usage = await lahjty("/usage");
~~~

~~~python
usage = lahjty("/usage")
~~~

## 9. Stored request

GET /requests/{request_id} costs 0 credits. Terminal request records and generated response payloads are retained for 30 days.

~~~ts
const request = await lahjty("/requests/req_01jexample");
~~~

~~~python
request = lahjty("/requests/req_01jexample")
~~~

## Reliability

- Same idempotency key and same body: replay the stored result without a second charge.
- Same key and changed body: 409 idempotency_conflict.
- Original request still running: 409 request_in_progress.
- Client timeout: retry the exact body with the same key or retrieve the request by request_id.
- Errors use application/problem+json with code, detail, and request_id.
- Honor Retry-After and rate-limit headers.
- Generation failures are refunded according to the endpoint contract.
- Keep a merchant-owned plain-text fallback. Campaign sending must not depend on Lahjty availability.
