{"slug":"export-usage-data-to-s3","title":"Export usage data to S3","tags":["tailscale"],"agent_summary":"Last validated: Apr 17, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Export usage data to S3\r\n\r\nLast validated: Apr 17, 2026\r\n\r\nAperture by Tailscaleis currently [in alpha](https://tailscale.com/docs/reference/tailscale-release-stages#alpha).\r\n\r\nThe Aperture [dashboard](https://tailscale.com/docs/aperture/reference/dashboard) shows recent usage data, but it is not designed for long-term retention or integration with external analytics tools. Exporting usage data to S3-compatible storage gives you a durable record of every LLM session for compliance auditing, cost analysis, and custom reporting.\r\n\r\nAperture's S3 exporter periodically writes usage records to the bucket you configure as CBOR-encoded, zstd-compressed files. It supports Amazon S3, Google Cloud Storage, MinIO, Backblaze B2, and other S3-compatible services.\r\n\r\n## [Prerequisites](https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3\\#prerequisites)\r\n\r\nBefore you begin, ensure you have the following:\r\n\r\n- An [Aperture instance](https://tailscale.com/docs/aperture/get-started) with at least one provider configured.\r\n- Admin access to the [Aperture configuration](https://tailscale.com/docs/aperture/configuration).\r\n- An S3-compatible bucket with write access. You need the bucket name, region, and credentials (access key ID and secret).\r\n\r\n## [Configure the S3 exporter](https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3\\#configure-the-s3-exporter)\r\n\r\nOpen the **Settings** page in the [Aperture dashboard](https://tailscale.com/docs/aperture/reference/dashboard) and add an `exporters` section to your configuration:\r\n\r\n```json\r\n\"exporters\": {\r\n  \"s3\": {\r\n    \"bucket_name\": \"aperture-exports\",\r\n    \"region\": \"us-east-1\",\r\n    \"access_key_id\": \"YOUR_AWS_ACCESS_KEY_ID\",\r\n    \"access_secret\": \"YOUR_AWS_SECRET_KEY\"\r\n  }\r\n}\r\n```\r\n\r\nSetting `bucket_name` to a non-empty value enables the S3 exporter. Aperture begins exporting usage records automatically on the next export cycle.\r\n\r\n### [Use a non-Amazon S3-compatible service](https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3\\#use-a-non-amazon-s3-compatible-service)\r\n\r\nAperture also supports S3-compatible storage services beyond AWS. For Google Cloud Storage, MinIO, Backblaze B2, or any service with an S3-compatible API, you can use the same configuration with the addition of an `endpoint` field.\r\n\r\nFor services other than Amazon S3 (such as Google Cloud Storage, MinIO, or Backblaze B2), add the `endpoint` field with the service's S3-compatible API URL:\r\n\r\n```json\r\n\"exporters\": {\r\n  \"s3\": {\r\n    \"endpoint\": \"https://storage.googleapis.com\",\r\n    \"bucket_name\": \"aperture-exports\",\r\n    \"region\": \"us-east-1\",\r\n    \"access_key_id\": \"YOUR_ACCESS_KEY_ID\",\r\n    \"access_secret\": \"YOUR_SECRET_KEY\"\r\n  }\r\n}\r\n```\r\n\r\nThe `region` field is required even for non-AWS services because the AWS SDK validates it.\r\n\r\n### [Customize export behavior](https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3\\#customize-export-behavior)\r\n\r\nYou can adjust how frequently Aperture exports data and how many records it includes per batch using the `every` and `limit` fields:\r\n\r\n```json\r\n\"exporters\": {\r\n  \"s3\": {\r\n    \"bucket_name\": \"aperture-exports\",\r\n    \"region\": \"us-east-1\",\r\n    \"access_key_id\": \"YOUR_AWS_ACCESS_KEY_ID\",\r\n    \"access_secret\": \"YOUR_AWS_SECRET_KEY\",\r\n    \"prefix\": \"prod\",\r\n    \"every\": 1800,\r\n    \"limit\": 2000\r\n  }\r\n}\r\n```\r\n\r\nThe following table summarizes these fields:\r\n\r\n| Field | Default | Description |\r\n| --- | --- | --- |\r\n| `prefix` | `\"\"` | (Optional) Path prefix for S3 objects. Must not end with `/`. |\r\n| `every` | `3600` | Seconds between export cycles. The example above exports every 30 minutes. |\r\n| `limit` | `1000` | Maximum records per export batch. Aperture caps this at `2500`. |\r\n\r\n## [Export file format](https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3\\#export-file-format)\r\n\r\nAperture exports usage data as [CBOR](https://cbor.io/)-encoded, [zstd](https://facebook.github.io/zstd/)-compressed files. CBOR is a binary encoding, similar to JSON, that supports binary data such as images and audio from multimodal LLM requests.\r\n\r\nEach export file is named `ts-aperture-export-<unix-timestamp>.cbor.zstd` and contains a sequence of CBOR-encoded records. Each record includes the following top-level fields:\r\n\r\n| Field | Type | Description |\r\n| --- | --- | --- |\r\n| `id` | integer | Record identifier. |\r\n| `ver` | integer | Schema version. `2`. |\r\n| `timestamp` | timestamp | When the LLM request occurred. |\r\n| `identity` | object | User and node identification: login name, stable node ID, and tags. |\r\n| `model` | string | LLM model name. |\r\n| `api_type` | string | API type, such as `oai_completions` or `ant_messages`. |\r\n| `usage` | object | Token counts: input, output, cached, and reasoning tokens. |\r\n| `estimated_cost` | object | Estimated dollar cost of the request. Omitted if unavailable. |\r\n| `duration_ms` | integer | Request duration in milliseconds. |\r\n| `capture_id` | string | Unique identifier for this capture. |\r\n| `session_id` | string | Session grouping identifier. |\r\n| `path` | string | API endpoint path. |\r\n| `status_code` | integer | HTTP response status code. |\r\n| `capture` | object | Full request and response data, including headers, processed JSON bodies, tool use data, and, optionally, raw binary request and response bodies. |\r\n\r\nTo read export files, decompress with a zstd library, then decode the CBOR sequence. Common libraries include `cbor2` and `zstandard` for Python, `fxamacker/cbor` and `klauspost/compress` for Go, and the `zstd` CLI tool for decompression.\r\n\r\n## [Verify the export](https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3\\#verify-the-export)\r\n\r\nAfter saving the configuration, wait for the next export cycle (based on your `every` setting) and check the S3 bucket for new objects. The objects appear under the configured prefix (if set) with the `.cbor.zstd` file extension.\r\n\r\nIf no objects appear after the expected interval, check the Aperture server logs for S3-related errors such as authentication failures or permission issues.\r\n\r\n## [Next steps](https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3\\#next-steps)\r\n\r\n- [Build a custom webhook](https://tailscale.com/docs/aperture/how-to/build-custom-webhook) to send real-time event data to your own services.\r\n- Review the [Aperture dashboard reference](https://tailscale.com/docs/aperture/reference/dashboard) for details on the built-in usage views.\r\n- Refer to the [exporters configuration reference](https://tailscale.com/docs/aperture/configuration#exporters) for the complete field reference.\r\n\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\r\n\r\nAsk AI\r\n\r\nreCAPTCHA\r\n\r\nRecaptcha requires verification.\r\n\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>Export usage data to S3</h1>\n<p>Last validated: Apr 17, 2026</p>\n<p>Aperture by Tailscaleis currently <a href=\"https://tailscale.com/docs/reference/tailscale-release-stages#alpha\">in alpha</a>.</p>\n<p>The Aperture <a href=\"https://tailscale.com/docs/aperture/reference/dashboard\">dashboard</a> shows recent usage data, but it is not designed for long-term retention or integration with external analytics tools. Exporting usage data to S3-compatible storage gives you a durable record of every LLM session for compliance auditing, cost analysis, and custom reporting.</p>\n<p>Aperture's S3 exporter periodically writes usage records to the bucket you configure as CBOR-encoded, zstd-compressed files. It supports Amazon S3, Google Cloud Storage, MinIO, Backblaze B2, and other S3-compatible services.</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3#prerequisites\">Prerequisites</a></h2>\n<p>Before you begin, ensure you have the following:</p>\n<ul>\n<li>An <a href=\"https://tailscale.com/docs/aperture/get-started\">Aperture instance</a> with at least one provider configured.</li>\n<li>Admin access to the <a href=\"https://tailscale.com/docs/aperture/configuration\">Aperture configuration</a>.</li>\n<li>An S3-compatible bucket with write access. You need the bucket name, region, and credentials (access key ID and secret).</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3#configure-the-s3-exporter\">Configure the S3 exporter</a></h2>\n<p>Open the <strong>Settings</strong> page in the <a href=\"https://tailscale.com/docs/aperture/reference/dashboard\">Aperture dashboard</a> and add an <code>exporters</code> section to your configuration:</p>\n<pre><code class=\"language-json\">\"exporters\": {\r\n  \"s3\": {\r\n    \"bucket_name\": \"aperture-exports\",\r\n    \"region\": \"us-east-1\",\r\n    \"access_key_id\": \"YOUR_AWS_ACCESS_KEY_ID\",\r\n    \"access_secret\": \"YOUR_AWS_SECRET_KEY\"\r\n  }\r\n}\n</code></pre>\n<p>Setting <code>bucket_name</code> to a non-empty value enables the S3 exporter. Aperture begins exporting usage records automatically on the next export cycle.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3#use-a-non-amazon-s3-compatible-service\">Use a non-Amazon S3-compatible service</a></h3>\n<p>Aperture also supports S3-compatible storage services beyond AWS. For Google Cloud Storage, MinIO, Backblaze B2, or any service with an S3-compatible API, you can use the same configuration with the addition of an <code>endpoint</code> field.</p>\n<p>For services other than Amazon S3 (such as Google Cloud Storage, MinIO, or Backblaze B2), add the <code>endpoint</code> field with the service's S3-compatible API URL:</p>\n<pre><code class=\"language-json\">\"exporters\": {\r\n  \"s3\": {\r\n    \"endpoint\": \"https://storage.googleapis.com\",\r\n    \"bucket_name\": \"aperture-exports\",\r\n    \"region\": \"us-east-1\",\r\n    \"access_key_id\": \"YOUR_ACCESS_KEY_ID\",\r\n    \"access_secret\": \"YOUR_SECRET_KEY\"\r\n  }\r\n}\n</code></pre>\n<p>The <code>region</code> field is required even for non-AWS services because the AWS SDK validates it.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3#customize-export-behavior\">Customize export behavior</a></h3>\n<p>You can adjust how frequently Aperture exports data and how many records it includes per batch using the <code>every</code> and <code>limit</code> fields:</p>\n<pre><code class=\"language-json\">\"exporters\": {\r\n  \"s3\": {\r\n    \"bucket_name\": \"aperture-exports\",\r\n    \"region\": \"us-east-1\",\r\n    \"access_key_id\": \"YOUR_AWS_ACCESS_KEY_ID\",\r\n    \"access_secret\": \"YOUR_AWS_SECRET_KEY\",\r\n    \"prefix\": \"prod\",\r\n    \"every\": 1800,\r\n    \"limit\": 2000\r\n  }\r\n}\n</code></pre>\n<p>The following table summarizes these fields:</p>\n<p>| Field | Default | Description |\r\n| --- | --- | --- |\r\n| <code>prefix</code> | <code>\"\"</code> | (Optional) Path prefix for S3 objects. Must not end with <code>/</code>. |\r\n| <code>every</code> | <code>3600</code> | Seconds between export cycles. The example above exports every 30 minutes. |\r\n| <code>limit</code> | <code>1000</code> | Maximum records per export batch. Aperture caps this at <code>2500</code>. |</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3#export-file-format\">Export file format</a></h2>\n<p>Aperture exports usage data as <a href=\"https://cbor.io/\">CBOR</a>-encoded, <a href=\"https://facebook.github.io/zstd/\">zstd</a>-compressed files. CBOR is a binary encoding, similar to JSON, that supports binary data such as images and audio from multimodal LLM requests.</p>\n<p>Each export file is named <code>ts-aperture-export-&#x3C;unix-timestamp>.cbor.zstd</code> and contains a sequence of CBOR-encoded records. Each record includes the following top-level fields:</p>\n<p>| Field | Type | Description |\r\n| --- | --- | --- |\r\n| <code>id</code> | integer | Record identifier. |\r\n| <code>ver</code> | integer | Schema version. <code>2</code>. |\r\n| <code>timestamp</code> | timestamp | When the LLM request occurred. |\r\n| <code>identity</code> | object | User and node identification: login name, stable node ID, and tags. |\r\n| <code>model</code> | string | LLM model name. |\r\n| <code>api_type</code> | string | API type, such as <code>oai_completions</code> or <code>ant_messages</code>. |\r\n| <code>usage</code> | object | Token counts: input, output, cached, and reasoning tokens. |\r\n| <code>estimated_cost</code> | object | Estimated dollar cost of the request. Omitted if unavailable. |\r\n| <code>duration_ms</code> | integer | Request duration in milliseconds. |\r\n| <code>capture_id</code> | string | Unique identifier for this capture. |\r\n| <code>session_id</code> | string | Session grouping identifier. |\r\n| <code>path</code> | string | API endpoint path. |\r\n| <code>status_code</code> | integer | HTTP response status code. |\r\n| <code>capture</code> | object | Full request and response data, including headers, processed JSON bodies, tool use data, and, optionally, raw binary request and response bodies. |</p>\n<p>To read export files, decompress with a zstd library, then decode the CBOR sequence. Common libraries include <code>cbor2</code> and <code>zstandard</code> for Python, <code>fxamacker/cbor</code> and <code>klauspost/compress</code> for Go, and the <code>zstd</code> CLI tool for decompression.</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3#verify-the-export\">Verify the export</a></h2>\n<p>After saving the configuration, wait for the next export cycle (based on your <code>every</code> setting) and check the S3 bucket for new objects. The objects appear under the configured prefix (if set) with the <code>.cbor.zstd</code> file extension.</p>\n<p>If no objects appear after the expected interval, check the Aperture server logs for S3-related errors such as authentication failures or permission issues.</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/how-to/export-usage-data-to-s3#next-steps\">Next steps</a></h2>\n<ul>\n<li><a href=\"https://tailscale.com/docs/aperture/how-to/build-custom-webhook\">Build a custom webhook</a> to send real-time event data to your own services.</li>\n<li>Review the <a href=\"https://tailscale.com/docs/aperture/reference/dashboard\">Aperture dashboard reference</a> for details on the built-in usage views.</li>\n<li>Refer to the <a href=\"https://tailscale.com/docs/aperture/configuration#exporters\">exporters configuration reference</a> for the complete field reference.</li>\n</ul>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}