> ## Documentation Index
> Fetch the complete documentation index at: https://docs.freeplay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrate with Your Application

> Connect Freeplay to your AI application for observability, evaluations, and prompt management.

<Tip>
  **Using a coding agent?** Point it to [docs.freeplay.ai/llms.txt](https://docs.freeplay.ai/llms.txt) for LLM-optimized documentation.
</Tip>

Integrating Freeplay with your application unlocks the full platform: production monitoring, dataset creation from real traffic, online and offline evaluations, and optional prompt and model deployment tools.

<CardGroup cols={2}>
  <Card title="Need a Freeplay account?" icon="user-plus">
    [Sign up for free](https://app.freeplay.ai/signup) to get started, or [reach out](https://freeplay.ai/demo) about self-hosting and enterprise options.
  </Card>

  <Card title="New to Freeplay?" icon="book" href="/resources/glossary">
    See the Glossary for definitions of key terms like sessions, traces, completions, and prompt templates.
  </Card>
</CardGroup>

## Choose your integration pattern

Before writing code, decide how you want to manage prompts.

### Pattern 1: Freeplay manages your prompts (recommended)

Freeplay becomes the source of truth for your prompt templates. Your application fetches prompts from Freeplay either at runtime, or as part of your build process (or both).

**Benefits:**

* Non-engineers can iterate on prompts and swap models without code changes
* Deploy prompt updates like feature flags and/or as part of your build process
* Automatic versioning and environment promotion
* Detailed observability with each log connected directly to a specific version (prompt and model configuration)

**Best for:** Teams that want to empower PMs, domain experts, or anyone else to iterate on prompts independently of code.

<Note>
  You can configure your Freeplay client to retrieve prompts from the server at runtime, or "bundle" them as part of your build process ([learn more about "prompt bundling"](/core-concepts/prompt-management/prompt-bundling)).

  Many Freeplay customers retrieve prompts at runtime in lower-level environments like dev or staging to get the benefit of fast server-side experimentation, then use prompt bundling in production for tighter release management and zero latency.
</Note>

### Pattern 2: Code manages your prompts

The source of truth for your prompts remains your codebase. You push prompt templates and model configurations to Freeplay to enable experimentation and organize your observability data.

**Benefits:**

* Prompts stay entirely in your code
* Use your existing code review process for prompt changes
* Full flexibility over prompt structure
* Sync prompts to Freeplay automatically via API in your CI/CD pipeline

**Best for:** Teams with complex prompt construction expectations, strict infrastructure-as-code requirements, and/or those who prefer prompt changes remain solely the domain of engineers.

<Accordion title="Example: Creating a prompt template via API" icon="code">
  With Pattern 2, you create prompt templates in Freeplay programmatically using the API. Use the `create_template_if_not_exists` parameter to sync prompts from your codebase:

  ```bash theme={null}
  curl -X POST \
    "https://app.freeplay.ai/api/v2/projects/$PROJECT_ID/prompt-templates/name/my-assistant/versions?create_template_if_not_exists=true" \
    -H "Authorization: Bearer $FREEPLAY_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "template_messages": [
        {"role": "system", "content": "You are a helpful assistant. The user'\''s name is {{user_name}}."},
        {"role": "user", "content": "{{user_input}}"}
      ],
      "provider": "openai",
      "model": "gpt-4o",
      "llm_parameters": {"temperature": 0.2, "max_tokens": 1024}
    }'
  ```
</Accordion>

See the full guide: [Code as source of truth](/core-concepts/prompt-management/managing-prompts#code-as-the-source-of-truth-for-prompts) and [Create prompt template version by name](/api-reference/prompt-templates/create-prompt-template-version-by-name).

### Optional: Start with observability only

You can begin by logging LLM calls to Freeplay without setting up prompt templates. This gets you started quickly, but **this is not recommended as an end state** since it limits core Freeplay features.

**What works without prompt templates:**

* Basic observability and search, including any evaluation values or other metadata you choose to log
* Manual agent dataset curation (trace level)
* Configuring auto-evaluations to run at the trace / agent level

**What requires prompt templates:**

* Experimenting in the Freeplay playground
* Creating prompt datasets from observability logs (requires `inputs` field / knowledge of variables in your prompts)
* Running evaluations that target prompt-level changes
* Searching by prompt template or version
* Running test runs against saved prompts
* Tracking prompt version performance over time

**When to use this approach:**

* You want to start logging immediately and add prompt templates later
* You're evaluating Freeplay before committing to a prompt management pattern

<Note>
  **Upgrade path**: Set up prompt templates as soon as possible to unlock the full set of Freeplay features. You can create templates either in the [Freeplay UI](/getting-started/start-in-ui) or [via the API](/core-concepts/prompt-management/managing-prompts#code-as-the-source-of-truth-for-prompts), then update your code to include `prompt_version_info` when recording.
</Note>

<Accordion title="Example: Observability-only integration" icon="eye">
  <CodeGroup>
    ```python Python theme={null}
    from freeplay import Freeplay, RecordPayload, CallInfo
    from openai import OpenAI
    import os

    fp_client = Freeplay(
        freeplay_api_key=os.getenv("FREEPLAY_API_KEY"),
        api_base="https://app.freeplay.ai/api"
    )
    openai_client = OpenAI()

    # Your existing prompt
    messages = [
        {"role": "system", "content": "You are helpful."},
        {"role": "user", "content": "Hello!"}
    ]

    # Make LLM call
    response = openai_client.chat.completions.create(
        model="gpt-4o",
        messages=messages
    )

    # Record to Freeplay (no prompt template)
    all_messages = messages + [
        {"role": "assistant", "content": response.choices[0].message.content}
    ]

    fp_client.recordings.create(
        RecordPayload(
            project_id=os.getenv("FREEPLAY_PROJECT_ID"),
            all_messages=all_messages,
            call_info=CallInfo(provider="openai", model="gpt-4o")
        )
    )
    ```

    ```typescript TypeScript theme={null}
    import Freeplay from "freeplay";
    import OpenAI from "openai";

    const fpClient = new Freeplay({
      freeplayApiKey: process.env.FREEPLAY_API_KEY,
      baseUrl: "https://app.freeplay.ai/api"
    });
    const openaiClient = new OpenAI();

    // Your existing prompt
    const messages = [
      { role: "system", content: "You are helpful." },
      { role: "user", content: "Hello!" }
    ];

    // Make LLM call
    const response = await openaiClient.chat.completions.create({
      model: "gpt-4o",
      messages
    });

    // Record to Freeplay (no prompt template)
    const allMessages = [...messages, response.choices[0].message];

    await fpClient.recordings.create({
      projectId: process.env.FREEPLAY_PROJECT_ID,
      allMessages,
      callInfo: { provider: "openai", model: "gpt-4o" }
    });
    ```
  </CodeGroup>
</Accordion>

## Get started

### Step 1: Install the SDK

You can integrate directly with your code using one of Freeplay's SDKs, or select from [integrations with common frameworks](#framework-integrations) outlined below.

<CodeGroup>
  ```bash Python theme={null}
  pip install freeplay
  ```

  ```bash TypeScript theme={null}
  npm install freeplay
  ```

  ```groovy Java (Gradle) theme={null}
  implementation 'ai.freeplay:freeplay-client-thin:VERSION'
  ```

  ```xml Java (Maven) theme={null}
  <dependency>
    <groupId>ai.freeplay</groupId>
    <artifactId>freeplay-client-thin</artifactId>
    <version>VERSION</version>
  </dependency>
  ```
</CodeGroup>

#### Alternative: Framework integrations

If you're using a common AI framework, Freeplay provides native integrations that require separate packages from our standard SDKs. Learn more:

<CardGroup cols={2}>
  <Card title="LangGraph" icon="diagram-project" href="/developer-resources/integrations/langgraph">
    Instrument LangGraph agents with Freeplay
  </Card>

  <Card title="Vercel AI SDK" icon="triangle" href="/developer-resources/integrations/vercel-ai-sdk">
    Add observability to Vercel AI applications
  </Card>

  <Card title="Google ADK" icon="google" href="/developer-resources/integrations/adk">
    Integrate with Google's Agent Development Kit
  </Card>

  <Card title="OpenTelemetry" icon="signal" href="/developer-resources/integrations/tracing-with-otel">
    Use standard OTel tracing with Freeplay
  </Card>
</CardGroup>

### Step 2: Create a prompt template

Create your first prompt template in the [UI](/getting-started/start-in-ui#1-create-your-first-prompt-template) or [via the API](/core-concepts/prompt-management/managing-prompts#code-as-the-source-of-truth-for-prompts).

Once you save a prompt, the **Integration** tab provides code snippets tailored to your template.

<img src="https://mintcdn.com/freeplay-485a287e/0Gd2SRSxIc4qaRiv/images/cf1b5970bef9ddbd17c5d02fe53a567d742c5694fc8c15facf33e65b69240b3e-integration-page.png?fit=max&auto=format&n=0Gd2SRSxIc4qaRiv&q=85&s=abbee3cab266de874e246627c0872d12" alt="Integration page" width="1286" height="813" data-path="images/cf1b5970bef9ddbd17c5d02fe53a567d742c5694fc8c15facf33e65b69240b3e-integration-page.png" />

### Step 3: Integrate

<Tabs>
  <Tab title="Pattern 1: Freeplay-managed prompts">
    Fetch prompts from Freeplay and log completions:

    <CodeGroup>
      ```python Python theme={null}
      from freeplay import Freeplay
      from openai import OpenAI
      import os

      fp_client = Freeplay(
          freeplay_api_key=os.getenv("FREEPLAY_API_KEY"),
          api_base="https://app.freeplay.ai/api"
      )
      openai_client = OpenAI()

      project_id = os.getenv("FREEPLAY_PROJECT_ID")

      ## FETCH PROMPT ##
      formatted_prompt = fp_client.prompts.get_formatted(
          project_id=project_id,
          template_name="my-template",
          environment="production",
          variables={"user_input": "Hello, world!"}
      )

      ## LLM CALL ##
      response = openai_client.chat.completions.create(
          model=formatted_prompt.model,
          messages=formatted_prompt.llm_messages
      )

      ## RECORD ##
      all_messages = formatted_prompt.all_messages + [
          {"role": "assistant", "content": response.choices[0].message.content}
      ]

      fp_client.recordings.create(
          project_id=project_id,
          all_messages=all_messages,
          prompt_version_info={
              "prompt_template_version_id": formatted_prompt.prompt_template_version_id,
              "environment": "production"
          }
      )
      ```

      ```typescript TypeScript theme={null}
      import Freeplay from "freeplay";
      import OpenAI from "openai";

      const fpClient = new Freeplay({
        freeplayApiKey: process.env.FREEPLAY_API_KEY,
        baseUrl: "https://app.freeplay.ai/api"
      });
      const openaiClient = new OpenAI();

      const projectId = process.env.FREEPLAY_PROJECT_ID;

      /* FETCH PROMPT */
      const formattedPrompt = await fpClient.prompts.getFormatted({
        projectId,
        templateName: "my-template",
        environment: "production",
        variables: { userInput: "Hello, world!" }
      });

      /* LLM CALL */
      const response = await openaiClient.chat.completions.create({
        model: formattedPrompt.model,
        messages: formattedPrompt.llmMessages
      });

      /* RECORD */
      const allMessages = [
        ...formattedPrompt.allMessages,
        response.choices[0].message
      ];

      await fpClient.recordings.create({
        projectId,
        allMessages,
        promptVersionInfo: {
          promptTemplateVersionId: formattedPrompt.promptTemplateVersionId,
          environment: "production"
        }
      });
      ```

      ```java Java theme={null}
      import ai.freeplay.client.thin.Freeplay;
      import ai.freeplay.client.thin.resources.prompts.FormattedPrompt;
      import com.openai.client.OpenAIClient;
      import com.openai.models.ChatCompletion;

      String apiKey = System.getenv("FREEPLAY_API_KEY");
      String projectId = System.getenv("FREEPLAY_PROJECT_ID");

      Freeplay fpClient = new Freeplay(
          Freeplay.Config()
              .freeplayAPIKey(apiKey)
              .baseUrl("https://app.freeplay.ai/api")
      );

      OpenAIClient openaiClient = OpenAIClient.builder().build();

      /* FETCH PROMPT */
      Map<String, Object> variables = Map.of("userInput", "Hello, world!");

      FormattedPrompt formattedPrompt = fpClient.prompts().getFormatted(
          projectId,
          "my-template",
          "production",
          variables
      ).get();

      /* LLM CALL */
      ChatCompletion response = openaiClient.chat().completions().create(
          ChatCompletionCreateParams.builder()
              .model(formattedPrompt.getModel())
              .messages(formattedPrompt.getLlmMessages())
              .build()
      );

      /* RECORD */
      List<Map<String, Object>> allMessages = new ArrayList<>(formattedPrompt.getAllMessages());
      allMessages.add(Map.of(
          "role", "assistant",
          "content", response.choices().get(0).message().content()
      ));

      fpClient.recordings().create(
          projectId,
          allMessages,
          Map.of(
              "promptTemplateVersionId", formattedPrompt.getPromptTemplateVersionId(),
              "environment", "production"
          )
      );
      ```

      ```kotlin Kotlin theme={null}
      import ai.freeplay.client.thin.Freeplay
      import com.openai.client.OpenAIClient
      import kotlinx.coroutines.runBlocking

      val apiKey = System.getenv("FREEPLAY_API_KEY")
      val projectId = System.getenv("FREEPLAY_PROJECT_ID")

      val fpClient = Freeplay(
          Freeplay.Config()
              .freeplayAPIKey(apiKey)
              .baseUrl("https://app.freeplay.ai/api")
      )

      val openaiClient = OpenAIClient.builder().build()

      /* FETCH PROMPT */
      val variables = mapOf("userInput" to "Hello, world!")

      val formattedPrompt = runBlocking {
          fpClient.prompts().getFormatted(
              projectId,
              "my-template",
              "production",
              variables
          ).await()
      }

      /* LLM CALL */
      val response = openaiClient.chat().completions().create(
          ChatCompletionCreateParams.builder()
              .model(formattedPrompt.model)
              .messages(formattedPrompt.llmMessages)
              .build()
      )

      /* RECORD */
      val allMessages = formattedPrompt.allMessages.toMutableList()
      allMessages.add(mapOf(
          "role" to "assistant",
          "content" to response.choices()[0].message().content()
      ))

      runBlocking {
          fpClient.recordings().create(
              projectId,
              allMessages,
              mapOf(
                  "promptTemplateVersionId" to formattedPrompt.promptTemplateVersionId,
                  "environment" to "production"
              )
          ).await()
      }
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Pattern 2: Code-managed prompts">
    Define prompts in your code and log to Freeplay:

    <CodeGroup>
      ```python Python theme={null}
      from freeplay import Freeplay, RecordPayload, CallInfo
      from openai import OpenAI
      import os

      fp_client = Freeplay(
          freeplay_api_key=os.getenv("FREEPLAY_API_KEY"),
          api_base="https://app.freeplay.ai/api"
      )
      openai_client = OpenAI()

      project_id = os.getenv("FREEPLAY_PROJECT_ID")

      ## YOUR PROMPT (defined in code) ##
      prompt_vars = {"user_name": "Alice", "topic": "weather"}
      messages = [
          {"role": "system", "content": "You are a helpful assistant."},
          {"role": "user", "content": f"Hi {prompt_vars['user_name']}, tell me about {prompt_vars['topic']}."}
      ]

      ## LLM CALL ##
      model = "gpt-4.1-mini"
      response = openai_client.chat.completions.create(
          model=model,
          messages=messages
      )

      ## RECORD ##
      all_messages = messages + [
          {"role": "assistant", "content": response.choices[0].message.content}
      ]

      fp_client.recordings.create(
          RecordPayload(
              project_id=project_id,
              all_messages=all_messages,
              inputs=prompt_vars,  # Variables enable dataset creation
              call_info=CallInfo(provider="openai", model=model)
          )
      )
      ```

      ```typescript TypeScript theme={null}
      import Freeplay from "freeplay";
      import OpenAI from "openai";

      const fpClient = new Freeplay({
        freeplayApiKey: process.env.FREEPLAY_API_KEY,
        baseUrl: "https://app.freeplay.ai/api"
      });
      const openaiClient = new OpenAI();

      const projectId = process.env.FREEPLAY_PROJECT_ID;

      /* YOUR PROMPT (defined in code) */
      const promptVars = { userName: "Alice", topic: "weather" };
      const messages = [
        { role: "system", content: "You are a helpful assistant." },
        { role: "user", content: `Hi ${promptVars.userName}, tell me about ${promptVars.topic}.` }
      ];

      /* LLM CALL */
      const model = "gpt-4.1-mini";
      const response = await openaiClient.chat.completions.create({
        model,
        messages
      });

      /* RECORD */
      const allMessages = [...messages, response.choices[0].message];

      await fpClient.recordings.create({
        projectId,
        allMessages,
        inputs: promptVars,  // Variables enable dataset creation
        callInfo: { provider: "openai", model }
      });
      ```

      ```java Java theme={null}
      import ai.freeplay.client.thin.Freeplay;
      import com.openai.client.OpenAIClient;
      import com.openai.models.ChatCompletion;

      String apiKey = System.getenv("FREEPLAY_API_KEY");
      String projectId = System.getenv("FREEPLAY_PROJECT_ID");

      Freeplay fpClient = new Freeplay(
          Freeplay.Config()
              .freeplayAPIKey(apiKey)
              .baseUrl("https://app.freeplay.ai/api")
      );

      OpenAIClient openaiClient = OpenAIClient.builder().build();

      /* YOUR PROMPT (defined in code) */
      Map<String, String> promptVars = Map.of("userName", "Alice", "topic", "weather");
      List<Map<String, Object>> messages = List.of(
          Map.of("role", "system", "content", "You are a helpful assistant."),
          Map.of("role", "user", "content", 
              String.format("Hi %s, tell me about %s.", promptVars.get("userName"), promptVars.get("topic")))
      );

      /* LLM CALL */
      String model = "gpt-4.1-mini";
      ChatCompletion response = openaiClient.chat().completions().create(
          ChatCompletionCreateParams.builder()
              .model(model)
              .messages(messages)
              .build()
      );

      /* RECORD */
      List<Map<String, Object>> allMessages = new ArrayList<>(messages);
      allMessages.add(Map.of(
          "role", "assistant",
          "content", response.choices().get(0).message().content()
      ));

      fpClient.recordings().create(
          projectId,
          allMessages,
          promptVars,  // Variables enable dataset creation
          Map.of("provider", "openai", "model", model)
      );
      ```

      ```kotlin Kotlin theme={null}
      import ai.freeplay.client.thin.Freeplay
      import com.openai.client.OpenAIClient
      import kotlinx.coroutines.runBlocking

      val apiKey = System.getenv("FREEPLAY_API_KEY")
      val projectId = System.getenv("FREEPLAY_PROJECT_ID")

      val fpClient = Freeplay(
          Freeplay.Config()
              .freeplayAPIKey(apiKey)
              .baseUrl("https://app.freeplay.ai/api")
      )

      val openaiClient = OpenAIClient.builder().build()

      /* YOUR PROMPT (defined in code) */
      val promptVars = mapOf("userName" to "Alice", "topic" to "weather")
      val messages = listOf(
          mapOf("role" to "system", "content" to "You are a helpful assistant."),
          mapOf("role" to "user", "content" to "Hi ${promptVars["userName"]}, tell me about ${promptVars["topic"]}.")
      )

      /* LLM CALL */
      val model = "gpt-4.1-mini"
      val response = openaiClient.chat().completions().create(
          ChatCompletionCreateParams.builder()
              .model(model)
              .messages(messages)
              .build()
      )

      /* RECORD */
      val allMessages = messages.toMutableList()
      allMessages.add(mapOf(
          "role" to "assistant",
          "content" to response.choices()[0].message().content()
      ))

      runBlocking {
          fpClient.recordings().create(
              projectId,
              allMessages,
              promptVars,  // Variables enable dataset creation
              mapOf("provider" to "openai", "model" to model)
          ).await()
      }
      ```
    </CodeGroup>

    <Tip>
      To fully unlock Freeplay features like searching by prompt template or version in Observability, sync the prompts from your code to Freeplay using the API. See [Code as source of truth](/core-concepts/prompt-management/managing-prompts#code-as-the-source-of-truth-for-prompts) for the complete workflow and [Create prompt template version by name](/api-reference/prompt-templates/create-prompt-template-version-by-name) for endpoint details.
    </Tip>
  </Tab>
</Tabs>

## Next steps

Your integration is complete. Sessions will appear in the [Observability dashboard](/core-concepts/observability/observability-dashboard) as you log data from your application.

With prompts and observability configured, you can now set up evaluations and run tests.

<CardGroup cols={2}>
  <Card title="Create evaluations" icon="scale-balanced" href="/core-concepts/evaluations/evaluations">
    Learn more about evaluation types and alignment
  </Card>

  <Card title="Run tests" icon="flask" href="/core-concepts/test-runs/test-runs">
    Execute batch tests to compare prompt versions and measure quality
  </Card>
</CardGroup>
