---
name: integrate-siyaqi-ads
description: Step-by-step Siyaqi publisher integration. Detect web vs React vs React Native vs REST, add the text-ad unit, hit the Fly serve API, confirm a test impression. Use when the user is ready to monetize an app with Siyaqi, add a placement snippet, or wire @siyaqi/react-native.
category: monetization
author: Siyaqi
homepage: https://siyaqi.com/skills
---

# Integrate Siyaqi text ads

Guide the user’s agent (Cursor, Claude Code, Codex, etc.) to add a **publisher** text unit. Do not dump every step at once. The user approves file edits. You do not deploy autonomously.

~10 minutes once they have a placement id.

## When to use

Ready to ship: “add Siyaqi ads”, “monetize this empty state”, “React Native text ads”, “wire the serve API”.

Still exploring dollars → `estimate-text-ad-revenue`.  
Unsure the slot is native → `score-text-placement`.

Advertiser campaign/MCP management is **not** this skill. Point advertisers to [siyaqi.com/docs#mcp](https://siyaqi.com/docs#mcp) and the advertiser portal.

## Step 1: Detect path

Ask (or infer from the repo):

| Path | When |
|---|---|
| **A. Hosted web snippet** | Marketing site, Rails, PHP, static HTML |
| **B. React / Next.js** | Existing React tree |
| **C. React Native / Expo** | Mobile app |
| **D. REST only** | Custom client, game engine, server-rendered non-JS |

Default to **A** if unclear.

## Step 2: Prerequisites

- A [publisher portal](https://siyaqi.com/login?portal=publisher&next=/publishers) account
- An **application** + **placement** id (`plc_…`) from Applications
- Network access to serve: `https://siyaqi-prod.fly.dev` (or their `SERVE_PUBLIC_URL`)
- Web host for the script (their `WEB_PUBLIC_URL`) — local: `http://localhost:3000`

No API key for **serve**. Keys are only for the advertiser portal machine API / MCP.

If they have no placement id yet: pause and send them to the portal. Do not invent ids.

## Step 3A: Hosted snippet

```html
<div id="siyaqi-ad"></div>
<script
  src="https://siyaqi.app/sdk/siyaqi.js"
  data-serve="https://siyaqi-prod.fly.dev"
  data-placement="plc_YOUR_ID"
  data-target="siyaqi-ad"
  async
></script>
```

Production hosts the script at `https://siyaqi.app/sdk/siyaqi.js` (web app, not Fly). Local: `src="http://localhost:3000/sdk/siyaqi.js"` and `data-serve="http://localhost:8080"`.

Place the unit in the scored moment (empty state, sidebar, pause). Add a visible **Sponsored** (or equivalent) label next to the line.

## Step 3B: React

Fetch serve directly if they do not want the script tag:

```tsx
import { useEffect, useState } from "react";

type ServedAd = {
  text: string;
  cta: string;
  trackingId: string;
  campaignId?: string;
};

export function SiyaqiAd({ placementId }: { placementId: string }) {
  const [ad, setAd] = useState<ServedAd | null>(null);

  useEffect(() => {
    let cancelled = false;
    fetch(
      `https://siyaqi-prod.fly.dev/v1/ads/serve?placementId=${encodeURIComponent(placementId)}`,
    )
      .then((r) => (r.ok ? r.json() : null))
      .then((data) => {
        if (!cancelled && data?.ad) setAd(data.ad);
      })
      .catch(() => {});
    return () => {
      cancelled = true;
    };
  }, [placementId]);

  if (!ad) return null;

  async function onClick(e: React.MouseEvent) {
    e.preventDefault();
    await fetch("https://siyaqi-prod.fly.dev/v1/ads/click", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        trackingId: ad.trackingId,
        campaignId: ad.campaignId,
        placementId,
      }),
    }).catch(() => {});
    // Then navigate to advertiser destination if the payload includes one.
  }

  return (
    <p>
      <span>{ad.text}</span>{" "}
      <button type="button" onClick={onClick}>
        {ad.cta}
      </button>
    </p>
  );
}
```

If serve JSON shape differs, follow the live response / [OpenAPI](https://siyaqi-prod.fly.dev/openapi/v1.yaml). Do not invent fields.

## Step 3C: React Native / Expo

```bash
npm install @siyaqi/react-native
```

```tsx
import { SiyaqiAd } from "@siyaqi/react-native";

<SiyaqiAd
  serveBase="https://siyaqi-prod.fly.dev"
  placementId="plc_YOUR_ID"
/>
```

Style the surrounding text to match the screen. Keep one unit visible.

## Step 3D: REST

```bash
curl "https://siyaqi-prod.fly.dev/v1/ads/serve?placementId=plc_YOUR_ID"
```

**200** + `ad` → render `text` + `cta`, record clicks via `POST /v1/ads/click`.  
**404** `{ "error": "No eligible creatives" }` → **no demand yet**. Keep the unit mounted; do not treat as a code bug.

Health: `GET https://siyaqi-prod.fly.dev/health`.

## Step 4: Test

1. Run the app locally.
2. Confirm the network call to `/v1/ads/serve?placementId=…`.
3. If filled: line + CTA render; click hits `/v1/ads/click`.
4. If 404: show nothing (or a non-ad placeholder). Tell the user to activate an advertiser campaign or wait for marketplace fill.
5. Check publisher **Analytics / Earnings** after ops drain (not instant).

## Step 5: Production checklist

- [ ] Production `placementId` (not a demo id)
- [ ] `data-serve` / `serveBase` is the Fly production serve URL
- [ ] Script `src` is the production web host
- [ ] CORS: serve allows the app origin (`WEB_PUBLIC_URL`)
- [ ] Disclosure visible
- [ ] Unit does not cover primary CTA
- [ ] Failure path: empty UI, no thrown error to users

## Step 6: Done

```
Siyaqi publisher unit is wired.

Next:
1. Confirm fill in the publisher portal once campaigns are live.
2. Add another placement only if score-text-placement stays ≥70.
3. Full reference: https://siyaqi.com/docs
4. Compare formats: https://siyaqi.com/compare
```

## Notes for the executing agent

- Read their layout before inserting. Match type size and spacing.
- Never wrap the unit in an iframe “ad frame” that looks like AdSense.
- Never auto-refresh serve in a tight loop (once per view / mount is enough).
- If the stack is not JS, use path D.
- `@siyaqi/mcp` is for **advertisers** managing campaigns, not publisher serve.

## What this skill does not do

- Does not create portal accounts or placement ids via API (publishers use the UI today).
- Does not top up advertiser wallets or launch campaigns.
- Does not replace [siyaqi.com/docs](https://siyaqi.com/docs).

## Security

- Serve is unauthenticated by design (public placement id). Treat `plc_…` as a public site key, not a secret.
- Do not commit advertiser `sk_live_…` keys into publisher apps.
- No secrets in this SKILL.md.

## Cross-references

- `estimate-text-ad-revenue`
- `score-text-placement`
- [siyaqi.com/for-publishers](https://siyaqi.com/for-publishers)
- [siyaqi.com/docs](https://siyaqi.com/docs)
- [siyaqi.com/skills](https://siyaqi.com/skills)
