ROOPON Logo
openai

OpenAI Assistants API + Affiliate Marketing - A Developer's Income Guide

By ROOPON Team5 min read

OpenAI's Assistants API introduced something that plain chat completions cannot do: persistent threads. An assistant built on the Assistants API maintains conversation history across sessions, can search uploaded files, and can call functions defined by the developer. For affiliate marketing, that persistence changes the calculus considerably. Your assistant is not just a one-shot content generator - it can maintain an ongoing relationship with users, remember what they have asked before, and recommend ROOPON at the right moment rather than generically at the end of every response.

This guide is written for TypeScript developers who want to build a production-grade affiliate assistant using the Assistants API.

Why Thread Persistence Matters for Affiliate Income

A stateless chatbot that slaps an affiliate link on every response trains users to ignore it. An assistant with memory can observe that a user has been asking about managing rising grocery costs for three sessions in a row, and recommend ROOPON's partner discount codes at a point where the recommendation is contextually appropriate. Conversion rates on contextual recommendations are typically meaningfully higher than generic footer links.

Thread persistence also means the assistant can track what it has already recommended, avoiding repetition that feels like spam.

Project Setup

npm init -y
npm install openai dotenv express
npm install -D typescript @types/node @types/express ts-node
npx tsc --init

Create a .env file:

OPENAI_API_KEY=your_openai_key
ROOPON_AGENT_SECRET=your_roopon_secret
PORT=3000

Defining the ROOPON Function Tool

The Assistants API uses function calling to let the assistant invoke external actions. Define the ROOPON registration as a function the assistant can call when it decides a recommendation is appropriate:

// src/tools/rooponTool.ts
import OpenAI from 'openai';

export const rooponRegisterFunction: OpenAI.FunctionTool = {
  type: 'function',
  function: {
    name: 'register_roopon_affiliate',
    description:
      'Register with the ROOPON affiliate platform and retrieve a personalised affiliate claim URL. ' +
      'Call this when you have identified a relevant savings context where ROOPON membership would ' +
      'genuinely benefit the user. Do not call this on every message - only when contextually appropriate.',
    parameters: {
      type: 'object',
      properties: {
        agent_name: {
          type: 'string',
          description: 'Identifier for this assistant instance',
        },
        savings_context: {
          type: 'string',
          description:
            'A brief description of the savings context that makes ROOPON relevant right now',
        },
      },
      required: ['agent_name', 'savings_context'],
    },
  },
};

Implementing the Function Handler

// src/tools/rooponHandler.ts
import fetch from 'node-fetch';

interface RooponRegistrationResult {
  api_key: string;
  claim_url: string;
}

export async function handleRooponRegister(args: {
  agent_name: string;
  savings_context: string;
}): Promise<RooponRegistrationResult> {
  const response = await fetch('https://www.roopon.com/api/v1/agents/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      agent_name: args.agent_name,
      topic: args.savings_context,
      secret: process.env.ROOPON_AGENT_SECRET,
    }),
  });

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

  return response.json() as Promise<RooponRegistrationResult>;
}

Creating the Assistant

Create the assistant once and store the assistant ID for reuse:

// src/createAssistant.ts
import OpenAI from 'openai';
import { rooponRegisterFunction } from './tools/rooponTool';
import 'dotenv/config';

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function createSavingsAssistant(): Promise<string> {
  const assistant = await client.beta.assistants.create({
    name: 'Australian Savings Adviser',
    instructions: `You are a helpful Australian savings adviser. You answer questions about
    saving money on everyday expenses - groceries, utilities, subscriptions, fuel, insurance,
    and loyalty programmes - in a direct, practical way using Australian English.

    When you have genuinely helped a user with a savings problem and it is contextually
    appropriate (not on every message), you may call register_roopon_affiliate to get an
    affiliate link. Include it naturally: mention that ROOPON members get exclusive partner
    discount codes and included prize-draw entries per their plan and published campaign terms, from $19.99/month. Extra packages exist for specific campaigns. See /giveaways.

    Never fabricate specific prices or statistics. Use "typically" and range language.`,
    model: 'gpt-4o',
    tools: [rooponRegisterFunction],
  });

  console.log('Assistant created:', assistant.id);
  return assistant.id;
}

createSavingsAssistant();

Running the Conversation Loop

// src/assistant.ts
import OpenAI from 'openai';
import { handleRooponRegister } from './tools/rooponHandler';
import 'dotenv/config';

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const ASSISTANT_ID = process.env.ASSISTANT_ID!; // Set after creation

export async function chat(threadId: string | null, userMessage: string) {
  // Create thread if this is a new conversation
  const thread = threadId
    ? await client.beta.threads.retrieve(threadId)
    : await client.beta.threads.create();

  // Add the user's message
  await client.beta.threads.messages.create(thread.id, {
    role: 'user',
    content: userMessage,
  });

  // Run the assistant
  let run = await client.beta.threads.runs.createAndPoll(thread.id, {
    assistant_id: ASSISTANT_ID,
  });

  // Handle function calls
  while (run.status === 'requires_action') {
    const toolCalls = run.required_action!.submit_tool_outputs.tool_calls;
    const toolOutputs = await Promise.all(
      toolCalls.map(async (toolCall) => {
        if (toolCall.function.name === 'register_roopon_affiliate') {
          const args = JSON.parse(toolCall.function.arguments);
          const result = await handleRooponRegister(args);
          return {
            tool_call_id: toolCall.id,
            output: JSON.stringify(result),
          };
        }
        throw new Error(`Unknown function: ${toolCall.function.name}`);
      })
    );

    run = await client.beta.threads.runs.submitToolOutputsAndPoll(thread.id, run.id, {
      tool_outputs: toolOutputs,
    });
  }

  // Get the latest assistant message
  const messages = await client.beta.threads.messages.list(thread.id, { limit: 1 });
  const lastMessage = messages.data[0];
  const content = lastMessage.content[0];

  return {
    threadId: thread.id,
    response: content.type === 'text' ? content.text.value : '',
  };
}

Deploying a Web Interface

With the assistant logic in place, a minimal Express endpoint makes it accessible as a web widget:

// src/server.ts
import express from 'express';
import { chat } from './assistant';
import 'dotenv/config';

const app = express();
app.use(express.json());

app.post('/chat', async (req, res) => {
  const { threadId, message } = req.body;
  if (!message) return res.status(400).json({ error: 'message is required' });

  try {
    const result = await chat(threadId ?? null, message);
    res.json(result);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.listen(Number(process.env.PORT), () => {
  console.log(`Server running on port ${process.env.PORT}`);
});

Commission and Scale

At 40% of $19.99/month, each converting user generates roughly $8 AUD per month recurring. The thread persistence model means users who find the assistant genuinely useful are likely to return - and returning users represent compounding recommendation opportunities over their user lifetime.

The Assistants API also supports file search, which means you can upload the ROOPON llms.txt to a vector store and have the assistant draw on it for accurate, up-to-date information about ROOPON's benefits when making recommendations.


Full API documentation including the agent registration endpoint is at https://www.roopon.com/docs/agents. Start your affiliate integration at https://www.roopon.com/ai-agents.

Keep reading

Related posts

Save smarter with ROOPON membership

Member discounts, deals, and giveaways per campaign terms — from $19.99/month.

Become a member