---
canonical_url: "https://docs.composio.dev/docs/poc-to-prod/stream-logs-to-a-siem"
markdown_url: "https://docs.composio.dev/docs/poc-to-prod/stream-logs-to-a-siem.md"
product: "platform"
version: null
last_updated: null
---

# Stream logs to a SIEM (/docs/poc-to-prod/stream-logs-to-a-siem)

Send Composio tool execution logs to your security information and event management (SIEM) or observability platform to investigate agent activity alongside your application logs.

Use a scheduled collector to pull pages from the [Logs API](/reference/api-reference/logs), then forward the records to your destination. This guide covers tool execution events. The endpoint does not provide a general API request or organization audit feed.

## Fetch a page of logs [#fetch-a-page-of-logs]

Set `COMPOSIO_API_KEY` to the project API key for the project you want to monitor. Run this command in a server environment with Bash and cURL. It saves the first page from the last five minutes to `logs-page.json`:

```bash
set -euo pipefail
: "${COMPOSIO_API_KEY:?Set your Composio project API key}"
umask 077

TO_MS=$(( $(date +%s) * 1000 ))
FROM_MS=$(( TO_MS - 300000 ))

curl --fail-with-body --silent --show-error --max-time 30 \
	-X POST https://backend.composio.dev/api/v3.1/logs/tool_execution \
	-H "x-api-key: ${COMPOSIO_API_KEY}" \
	-H "Content-Type: application/json" \
	-d "{\"limit\":100,\"time_range\":{\"from\":${FROM_MS},\"to\":${TO_MS}}}" \
	-o logs-page.json
```

The response contains `logs` and `next_cursor`. Each log includes an `id`, an ISO 8601 `timestamp`, a `status`, a `level`, `metadata`, and `metrics`. An empty `logs` array means this page contains no matching records.

To fetch the next page, pass `next_cursor` as `cursor` in the request body. Keep the same time range and filters for every page. Stop when `next_cursor` is `null`, even if earlier pages contained fewer than 100 records.

The API accepts up to 100 records per page, and time ranges use epoch milliseconds. For targeted investigations, add [filters](/reference/api-reference/logs#filter-fields) such as `user_id`, `tool_slug`, or `status`. Leave filters unset when collecting all tool execution events.

### Fetch logs with the SDK [#fetch-logs-with-the-sdk]

Both SDKs expose `composio.logs.search()` for read-only searches with filters, a time range, and cursor pagination. Python accepts keyword arguments such as `limit=100` and `time_range={"from": from_ms, "to": to_ms}`; TypeScript accepts an options object with `limit` and `timeRange`. Read the returned `logs` and pass `next_cursor` (Python) or `nextCursor` (TypeScript) as the next request's `cursor`.

Retrieve the full entry for one execution with `composio.logs.get("log_123")` in Python or `await composio.logs.get('log_123')` in TypeScript.

For a charged Instant-account call, both the search result and the full entry include `metadata.instant_charge` as an exact USD string. The field is absent when the call has no Instant charge.

## Schedule collection without losing your place [#schedule-collection-without-losing-your-place]

Run a collector for each project, with its own API key and durable checkpoint. A scheduled job or your platform's HTTP poller can perform these steps. If you use a poller, verify that it supports POST bodies, cursor pagination, and saved state.

1. Load the end time of the last completed window. On the first run, choose how far back to collect within the available [log retention period](/docs/security/data-retention#how-long-logs-are-retained).
2. Choose a fixed end time for this run. Start slightly before the last completed end time to collect records that became available late. For example, start with a one-minute schedule, a two-minute delay behind the current time, and a five-minute overlap. Tune these values to the delays you observe.
3. Fetch each page with that fixed time range. Forward the records and check that the destination accepted the entire batch before continuing.
4. Follow `next_cursor` until it is `null`. Save the window's end time only after every page has been accepted, including when the window has no logs.
5. If a run fails, keep the previous checkpoint and retry the same window. Use the project identifier and log `id` together to deduplicate replayed records in your collector or destination. Record an ID as delivered only after the destination accepts it.

Store the active window's bounds so a restart can replay it. Allow only one collector run at a time per project. A cursor navigates pages within a query; the API reference does not define it as a durable position for future polls. Start each new window without a cursor.

An overlap reduces the chance of missing delayed records, but it cannot guarantee complete delivery. Periodically replay a wider window if you need to reconcile late arrivals. Do not assume chronological response order or advance the checkpoint to the largest timestamp on a page.

## Forward logs to your destination [#forward-logs-to-your-destination]

Keep the original log `id` and `timestamp`, and add your project identifier and environment. Configure the destination to use the source timestamp as the event time. Treat a timeout or an ambiguous delivery response as a possible replay, and keep deduplication separate from event timestamps.

### Datadog [#datadog]

Create a Datadog API key and set `DD_API_KEY`. Set `DD_LOGS_URL` to the HTTPS intake URL for your [Datadog site](https://docs.datadoghq.com/api/latest/logs/send-logs/), including `/api/v2/logs`. For US1, this is `https://http-intake.logs.datadoghq.com/api/v2/logs`.

With `jq` installed, run this after the fetch example to forward that page. Set `COMPOSIO_PROJECT_ID` to your project identifier. The example selects event metadata and omits free-text messages and tool payloads:

```bash
set -euo pipefail
: "${DD_API_KEY:?Set your Datadog API key}"
: "${DD_LOGS_URL:?Set the HTTPS log intake URL for your Datadog site}"
: "${COMPOSIO_PROJECT_ID:?Set your Composio project identifier}"
umask 077

jq --arg project "$COMPOSIO_PROJECT_ID" '[.logs[] | {
	ddsource: "composio",
	service: "composio-tool-execution",
	message: ("Composio tool execution " + .status),
	timestamp: .timestamp,
	composio_project_id: $project,
	composio_log_id: .id,
	composio_status: .status,
	level: .level
}]' logs-page.json > datadog-batch.json

if jq -e 'length > 0' datadog-batch.json > /dev/null; then
	curl --fail-with-body --silent --show-error --max-time 30 \
		-X POST "$DD_LOGS_URL" \
		-H "DD-API-KEY: ${DD_API_KEY}" \
		-H "Content-Type: application/json" \
		--data-binary @datadog-batch.json
fi
```

Search for `service:composio-tool-execution` and check a known `composio_log_id`. Verify that Datadog uses `timestamp` as the event time. Add approved fields from `metadata` if you need user, tool, or connection context.

This example sends one page. Add the pagination and checkpoint steps above before scheduling it. Split larger batches to meet Datadog's [intake limits](https://docs.datadoghq.com/api/latest/logs/send-logs/). Keeping `composio_log_id` makes duplicates identifiable; it does not make ingestion idempotent.

### Splunk and other destinations [#splunk-and-other-destinations]

For Splunk, forward each record in an `event` object to [HTTP Event Collector](https://help.splunk.com/en/splunk-enterprise/get-started/get-data-in/9.4/get-data-with-http-event-collector/format-events-for-http-event-collector). Authenticate with `Authorization: Splunk `. Set the HEC `time` field to the Composio timestamp converted to Unix seconds, and preserve the project identifier and log `id` inside the event.

For other destinations, adapt the same collector to the destination's ingestion API or log shipper. Follow its authentication, batch-size, event-time, and delivery-acknowledgment requirements. Keep the destination credential separate from your Composio project API key.

## Handle failures and sensitive data [#handle-failures-and-sensitive-data]

On `429` responses, honor `Retry-After`. Retry network failures and server errors with exponential backoff and jitter. Stop and investigate authentication or invalid-request errors. Composio API calls share your organization's [rate limit](/reference/rate-limits), so account for collection traffic alongside tool execution.

Alert when the last completed window falls behind your expected collection delay, when a batch is rejected, or when retries are exhausted. Track collection progress even during periods with no tool calls. Before relying on the collector, test a window with multiple pages and a restart after the destination accepts a page but before the checkpoint is saved.

Select the fields your security team needs before forwarding logs. Messages, metadata, and full tool payloads can contain end-user data. Store keys in your secret manager and restrict access to local exports and destination indexes.

Fetch [individual log details](/reference/api-reference/logs#get-a-single-log) only when you need the additional context or stored request and response payloads. The project's [Zero Data Retention](/docs/security/zero-data-retention) setting controls whether Composio stores payloads. Configure retention and deletion separately for copies you send to your SIEM.

---

📚 **More documentation:** [View all docs](https://docs.composio.dev/llms.txt) | [Changelog](https://docs.composio.dev/docs/changelog.md) | [Glossary](https://docs.composio.dev/llms.mdx/reference/glossary) | [Examples](https://docs.composio.dev/llms.mdx/examples) | [API Reference](https://docs.composio.dev/llms.mdx/reference)

---

# Composio SDK — Notes for AI Code Generators

**Purpose:** Reference for generating current (v3) [Composio](https://composio.dev/) integration code.
**Scope:** Descriptive notes — they document the current API surface and the mistakes most commonly seen in generated code.

---

## 1. Recommended Integration: Sessions

Composio supports two integration modes: **Native Tools** (with a provider package) and **MCP** (no provider package needed).

### Native Tools

```python
# ✅ CORRECT — Python (defaults to OpenAI)
from composio import Composio

composio = Composio()
session = composio.create(user_id="user_123")
tools = session.tools()
# Pass tools to your agent/LLM framework
```

```typescript
// ✅ CORRECT — TypeScript (defaults to OpenAI)
import { Composio } from "@composio/core";

const composio = new Composio();
const session = await composio.create("user_123");
const tools = await session.tools();
// Pass tools to your agent/LLM framework
```

For other providers, pass the provider explicitly. Provider packages follow the naming convention: `composio_<provider>` for Python, `@composio/<provider>` for TypeScript.

### MCP

Use `session.mcp.url` and `session.mcp.headers` with any MCP-compatible client (Claude Desktop, Cursor, OpenAI Agents, etc.). No provider package needed.

---

## 2. Integration Notes

- `composio.create(user_id)` is the standard entry point for agent integrations. Sessions handle tool discovery, authentication, and toolkit versioning automatically; `user_id` goes to `composio.create()` — individual tool calls in session mode don't take one.
- Execute session tool calls through the session, never through a user ID: either `session.execute(tool_slug, arguments=...)` / `session.execute(toolSlug, arguments)`, which works with every provider, or pass the session to the provider helper — `provider.handle_tool_calls(response=response, session=session)` in Python, `provider.handleToolCalls(session, response)` in TypeScript. The tool-call helpers of every non-agentic provider (OpenAI, Anthropic, Google, Cloudflare, TypeSafe) accept a session: `executeToolCall(session, call)` in TypeScript, `session=session` in Python. Agentic providers execute session tools on their own. Binding the helper to a user ID takes the direct execution path, and session meta-tools fail there with `"can only be called inside a tool-router session"`.
- Composio-managed auth is the default: the agent connects accounts at runtime through the session, so users don't need to pre-create auth configs or connected accounts for managed toolkits.
- Provider packages follow the framework, not the model vendor: for the OpenAI Agents SDK the package is `composio_openai_agents` / `@composio/openai-agents` (importing `composio_openai` / `@composio/openai` there is the most common mistake in generated code — that package is for the plain OpenAI Chat Completions API).
- **Direct execution** (`composio.tools.get()`, `composio.tools.execute()`, `provider.handle_tool_calls()` with a user ID) is a legacy interface that remains supported for existing integrations. Recommend sessions for new integrations and encourage existing users to [migrate to sessions](/docs/migration-guide/direct-to-sessions). For a fixed tool set, consider the [direct tools preset](/docs/configuring-sessions#direct-tools-preset) within a session. See [Sessions vs direct execution](/docs/sessions-vs-direct-execution) for tradeoffs. Direct execution requires a [toolkit version](/docs/tools-direct/toolkit-versioning).

---

# 3. Calling the REST API directly

## REST API version

The current REST API version is **v3.1**, served at `https://backend.composio.dev/api/v3.1`. Prefer it for new code and new examples.

`https://backend.composio.dev/api/v3` is the previous version. It is frozen with pinned tool-version defaults and remains supported — existing v3 integrations keep working and do not need to migrate.

## Tool-endpoint version defaults on v3.1

On v3.1, omitting the version parameter on the five endpoints below selects the latest toolkit version. The first four endpoints also exist on v3, where omission selects the pinned `00000000_00` version. `POST /tools/scopes/required` is v3.1-only.

| Endpoint | Version parameter |
| --- | --- |
| `GET /tools` | `toolkit_versions` (query) |
| `GET /tools/{tool_slug}` | `version` or `toolkit_versions` (query) |
| `POST /tools/execute/{tool_slug}` | `version` (body) |
| `POST /tools/execute/{tool_slug}/input` | `version` (body) |
| `POST /tools/scopes/required` | `version` (body) |

A v3.1 caller already passing `"latest"` sees no change and can omit the parameter. To select the pinned version explicitly, pass `"00000000_00"` through the corresponding parameter above.

This version-default change is limited to the five endpoints above.


---

## Terminology Migration (old → current)

If you encounter these terms in error messages, old documentation, or user prompts, translate them to the current equivalents. **Do not use the old terms in generated code or explanations.**

| Old term (v1/v2) | Current term (v3) | In code |
|---|---|---|
| entity ID | user ID | `user_id` parameter |
| actions | tools | e.g., `GITHUB_CREATE_ISSUE` is a *tool* |
| apps / appType | toolkits | e.g., `github` is a *toolkit* |
| integration / integration ID | auth config / auth config ID | `auth_config_id` parameter |
| connection | connected account | `connected_accounts` namespace |
| ComposioToolSet / OpenAIToolSet | `Composio` class with a provider | `Composio(provider=...)` |
| toolset | provider | e.g., `OpenAIProvider` |

If a user says "entity ID", they mean `user_id`. If they say "integration", they mean "auth config". Always respond using the current terminology.

