<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tutorial</title>
    <description>The latest articles tagged 'tutorial' on DEV Community.</description>
    <link>https://dev.to/t/tutorial</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/tutorial"/>
    <language>en</language>
    <item>
      <title>Git[깃] 초보자를 위한 필수 명령어 가이드</title>
      <dc:creator>바람의평온</dc:creator>
      <pubDate>Tue, 12 May 2026 02:40:13 +0000</pubDate>
      <link>https://dev.to/kys7442/gitgis-cobojareul-wihan-pilsu-myeongryeongeo-gaideu-12ck</link>
      <guid>https://dev.to/kys7442/gitgis-cobojareul-wihan-pilsu-myeongryeongeo-gaideu-12ck</guid>
      <description>&lt;p&gt;&lt;strong&gt;## Git[깃]이란 무엇인가?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;안녕하세요! 이번 강좌에서는 개발자라면 누구나 사용하게 되는 필수 도구, Git[깃]의 기본 명령어들을 알아보겠습니다. Git[깃]은 코드의 변경 이력을 관리하고 여러 개발자가 함께 작업할 때 발생하는 충돌을 최소화하는 강력한 버전 관리 시스템입니다. 특히 협업이 중요한 현대 개발 환경에서는 Git[깃]을 제대로 이해하고 사용하는 것이 필수적입니다. 이 강좌를 통해 Git[깃]의 핵심 명령어들을 쉽고 빠르게 익혀보세요.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;## Git[깃] 저장소 초기화&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;새로운 프로젝트를 시작하거나 기존 프로젝트에 Git[깃]을 적용하고 싶을 때 가장 먼저 해야 할 일은 Git[깃] 저장소를 초기화하는 것입니다. 터미널에서 프로젝트 폴더로 이동한 후, &lt;code&gt;git init[깃 이닛]&lt;/code&gt; 명령어를 실행하면 됩니다. 이 명령어는 현재 폴더에 Git[깃]을 위한 &lt;code&gt;.git[깃]&lt;/code&gt;이라는 숨김 폴더를 생성하고, 버전 관리를 시작할 준비를 마칩니다. 이제부터 이 폴더 안의 모든 변경 사항이 Git[깃]에 의해 추적됩니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;## 작업 상태 확인하기&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;코드를 작성하거나 파일을 수정하면 Git[깃]은 이를 감지합니다. 현재 어떤 파일들이 변경되었고, Git[깃]이 추적하고 있는지 확인하려면 &lt;code&gt;git status[깃 스테이터스]&lt;/code&gt; 명령어를 사용합니다. 이 명령어는 아직 Git[깃]이 관리하지 않는 파일(Untracked[언트랙티드] 파일)과 Git[깃]이 추적 중이지만 변경된 파일(Modified[모디파이드] 파일)을 명확하게 보여줍니다. 변경 사항을 커밋하기 전에 반드시 상태를 확인하는 습관을 들이는 것이 좋습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;## 변경 사항 스테이징하기&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Git[깃]은 변경된 모든 내용을 한 번에 커밋하는 것이 아니라, 커밋할 파일들을 미리 선택하는 과정을 거칩니다. 이 과정을 '스테이징'이라고 합니다. &lt;code&gt;git add . [깃 에드]&lt;/code&gt; 명령어는 현재 디렉토리의 모든 변경된 파일들을 스테이징 영역으로 추가합니다. 특정 파일만 추가하고 싶다면 &lt;code&gt;git add [파일명]&lt;/code&gt;과 같이 파일명을 지정할 수도 있습니다. 스테이징된 파일들만 다음 커밋에 포함됩니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;## 변경 사항 커밋하기&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;스테이징된 파일들을 모아 하나의 커밋으로 기록하는 단계입니다. &lt;code&gt;git commit -m "[커밋 메시지]"&lt;/code&gt; 명령어를 사용합니다. 여기서 &lt;code&gt;-m&lt;/code&gt; 옵션 뒤에 오는 메시지는 해당 커밋이 어떤 변경 사항을 담고 있는지 설명하는 글입니다. 나중에 변경 이력을 볼 때 이 메시지를 보고 내용을 파악하게 되므로, 명확하고 간결하게 작성하는 것이 매우 중요합니다. 예를 들어, 'Initial commit[이니셜 커밋]'은 프로젝트 초기 상태를 의미합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;## 원격 저장소로 푸시하기&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;로컬 컴퓨터에서 작업한 내용을 GitHub[깃허브]와 같은 원격 저장소로 업로드하는 명령어입니다. &lt;code&gt;git push [깃 푸시] [원격 저장소명] [브랜치명]&lt;/code&gt; 형식으로 사용합니다. 일반적으로 &lt;code&gt;origin[오리진]&lt;/code&gt;은 기본 원격 저장소를, &lt;code&gt;main[메인]&lt;/code&gt;은 기본 브랜치를 의미합니다. 이 명령어를 실행하면 로컬에서 커밋한 내용들이 원격 저장소에 반영되어 다른 사람들과 공유하거나 백업할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;## 원격 저장소 내용 가져오기&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;다른 팀원이 원격 저장소에 푸시한 최신 변경 사항을 내 로컬 저장소로 가져오는 명령어입니다. &lt;code&gt;git pull [깃 풀] [원격 저장소명] [브랜치명]&lt;/code&gt; 형식으로 사용합니다. &lt;code&gt;git pull[깃 풀]&lt;/code&gt;은 내부적으로 &lt;code&gt;git fetch[깃 패치]&lt;/code&gt;와 &lt;code&gt;git merge[깃 머지]&lt;/code&gt;를 함께 수행하여 원격 저장소의 변경 내용을 가져온 후 현재 작업 중인 브랜치에 병합합니다. 협업 시에는 주기적으로 pull[풀]을 받아 최신 상태를 유지하는 것이 중요합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;## Git[깃] 명령어 요약&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;지금까지 Git[깃]의 기본적인 명령어들을 살펴보았습니다. &lt;code&gt;git init[깃 이닛]&lt;/code&gt;으로 저장소를 만들고, &lt;code&gt;git status[깃 스테이터스]&lt;/code&gt;로 변경 사항을 확인한 뒤, &lt;code&gt;git add . [깃 에드]&lt;/code&gt;로 커밋할 내용을 선택하고, &lt;code&gt;git commit -m "메시지"&lt;/code&gt;로 변경 내역을 기록했습니다. 마지막으로 &lt;code&gt;git push[깃 푸시]&lt;/code&gt;와 &lt;code&gt;git pull[깃 풀]&lt;/code&gt; 명령어를 통해 원격 저장소와 로컬 저장소를 동기화하는 방법까지 익혔습니다. 이 명령어들을 꾸준히 연습하면 Git[깃] 사용에 자신감이 생길 것입니다.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>codenewbie</category>
      <category>git</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cómo Rastrear el Gasto de la API de OpenAI por Función: Guía de Atribución de Costos</title>
      <dc:creator>Roobia</dc:creator>
      <pubDate>Tue, 12 May 2026 02:39:34 +0000</pubDate>
      <link>https://dev.to/roobia/como-rastrear-el-gasto-de-la-api-de-openai-por-funcion-guia-de-atribucion-de-costos-4ji1</link>
      <guid>https://dev.to/roobia/como-rastrear-el-gasto-de-la-api-de-openai-por-funcion-guia-de-atribucion-de-costos-4ji1</guid>
      <description>&lt;p&gt;Tu factura de OpenAI dice que gastaste $4,237 el mes pasado. No te dice que $3,100 vinieron de un único endpoint de resumen descontrolado, $700 de un cliente que paga $50 al mes y $437 de una función que nadie usa. El panel de control oculta la información que necesitas para decidir precios, capacidad y roadmap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;Prueba Apidog hoy&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Esta guía muestra cómo implementar atribución de costos para la API de OpenAI: etiquetar cada solicitud con metadatos, calcular el gasto por función, ruta y cliente, configurar límites de presupuesto por clave y diseñar prompts para que el costo deje de ser una línea opaca.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Apidog te ayuda a validar solicitudes etiquetadas antes de producción. Úsalo para reproducir llamadas, verificar la forma del log y comprobar que cada request incluye los metadatos que tu almacén espera.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  En resumen
&lt;/h2&gt;

&lt;p&gt;Etiqueta cada llamada a la API de OpenAI con metadatos estructurados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;route&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Después, emite un log estructurado por solicitud con tokens, latencia y costo calculado. Agrega esos eventos en tu almacén de datos y consulta el gasto por función, cliente o ruta.&lt;/p&gt;

&lt;p&gt;También deberías:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;configurar límites de presupuesto por clave en OpenAI;&lt;/li&gt;
&lt;li&gt;crear alertas sobre anomalías de gasto por hora;&lt;/li&gt;
&lt;li&gt;validar el wrapper extremo a extremo con &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Lanzas una función de IA el martes. El viernes, tu CFO pregunta por qué el gasto de OpenAI subió 40%. Abres el panel de OpenAI. Ves que el gasto total sube, pero no sabes qué función, cliente o ruta lo causó.&lt;/p&gt;

&lt;p&gt;Esa es la brecha que encuentran los equipos que ejecutan LLMs en producción. La interfaz de facturación de OpenAI sirve para cuentas por pagar, no para atribución técnica. Ves totales diarios y desglose por modelo, pero no ves la solicitud, el cliente, la ruta ni la función que activó el gasto.&lt;/p&gt;

&lt;p&gt;La solución es directa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;envolver cada llamada a OpenAI;&lt;/li&gt;
&lt;li&gt;añadir metadatos obligatorios;&lt;/li&gt;
&lt;li&gt;registrar cada solicitud en un formato estructurado;&lt;/li&gt;
&lt;li&gt;calcular el costo al momento de escribir el evento;&lt;/li&gt;
&lt;li&gt;agregar por etiquetas;&lt;/li&gt;
&lt;li&gt;alertar sobre desviaciones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Para el contexto de precios que alimenta el cálculo de costos, consulta el &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;desglose de precios de GPT-5.5&lt;/a&gt;. Para un problema relacionado de atribución de facturación en herramientas para desarrolladores, consulta &lt;a href="http://apidog.com/blog/github-copilot-usage-billing-api-teams?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;la facturación de uso de GitHub Copilot para equipos de API&lt;/a&gt;. Para los conceptos básicos, revisa la &lt;a href="https://platform.openai.com/docs/api-reference" rel="noopener noreferrer"&gt;referencia oficial de la API de OpenAI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué el panel de facturación de OpenAI no es suficiente
&lt;/h2&gt;

&lt;p&gt;El panel de OpenAI muestra gasto diario, desglose por modelo y límite de uso. Eso funciona si tienes una sola app, un solo cliente y una sola función.&lt;/p&gt;

&lt;p&gt;Deja de ser suficiente cuando tienes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;múltiples funciones;&lt;/li&gt;
&lt;li&gt;múltiples clientes;&lt;/li&gt;
&lt;li&gt;múltiples entornos;&lt;/li&gt;
&lt;li&gt;múltiples desarrolladores;&lt;/li&gt;
&lt;li&gt;jobs en background;&lt;/li&gt;
&lt;li&gt;workers o colas;&lt;/li&gt;
&lt;li&gt;distintos productos usando la misma organización.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lo que falta:&lt;/p&gt;

&lt;h3&gt;
  
  
  Gasto total sin contexto
&lt;/h3&gt;

&lt;p&gt;El panel puede decir que ayer gastaste $312 en GPT-5.5. No te dice si vino de un cliente usando el chat de soporte 50,000 veces o de un job nocturno que resumió toda tu base de conocimiento por error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sin desglose por función
&lt;/h3&gt;

&lt;p&gt;OpenAI etiqueta por clave de API y modelo. No etiqueta por función, ruta, cliente ni entorno. Si necesitas esas dimensiones, debes capturarlas en tu aplicación.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retraso en los informes
&lt;/h3&gt;

&lt;p&gt;Los datos de uso pueden tardar decenas de minutos o algunas horas. Para cuando un bucle descontrolado aparece en el panel, ya consumió presupuesto. Para alertas operativas necesitas datos propios casi en tiempo real.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sin alertas por función
&lt;/h3&gt;

&lt;p&gt;OpenAI ofrece límites y notificaciones generales. No hay una alerta nativa del tipo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“avísame si &lt;code&gt;/api/v1/chat/answer&lt;/code&gt; supera $50 en una hora”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Eso debes construirlo con tus propios logs y consultas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sin atribución por cliente
&lt;/h3&gt;

&lt;p&gt;Si vendes SaaS B2B con funciones de IA, necesitas responder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“¿cuánto me cuesta el cliente X este mes?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sin ese dato, no puedes calcular margen bruto por cliente ni decidir cuotas, precios o upsells.&lt;/p&gt;

&lt;h3&gt;
  
  
  Las claves por proyecto ayudan, pero no resuelven todo
&lt;/h3&gt;

&lt;p&gt;Las claves de proyecto permiten separar uso por proyecto, pero no por función, cliente o ruta. La &lt;a href="https://platform.openai.com/docs/api-reference/usage" rel="noopener noreferrer"&gt;API de uso de OpenAI&lt;/a&gt; devuelve datos agregados por proyecto, no por solicitud.&lt;/p&gt;

&lt;p&gt;El patrón es claro: el panel nativo responde una pregunta financiera. Tú necesitas responder una pregunta de producto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modelo de datos para atribución de costos
&lt;/h2&gt;

&lt;p&gt;Cada solicitud a OpenAI debe generar un evento etiquetado. Ese evento es tu unidad de análisis.&lt;/p&gt;

&lt;p&gt;Esquema mínimo recomendado:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Columna&lt;/th&gt;
&lt;th&gt;Tipo&lt;/th&gt;
&lt;th&gt;Ejemplo&lt;/th&gt;
&lt;th&gt;Por qué importa&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;request_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;uuid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7a91...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Idempotencia, deduplicación, reintentos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamptz&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-05-06T14:23:01Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Series temporales y anomalías&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;soporte-chat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Función del producto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;route&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/chat/answer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ruta HTTP o job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cliente_4291&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gasto por cliente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;prod&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, &lt;code&gt;dev&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Separar costo interno de costo de cliente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gpt-5.5&lt;/code&gt;, &lt;code&gt;gpt-5.4-mini&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;El precio depende del modelo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15234&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens de entrada&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completion_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;812&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens de salida&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reasoning_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4500&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens de razonamiento facturados como salida&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cached_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens cacheados&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;latency_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2341&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Correlación costo/experiencia&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cost_usd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;numeric(10,6)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.045672&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Costo calculado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_cache_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sistema-v3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Seguimiento de caché&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error_code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;null&lt;/code&gt;, &lt;code&gt;429&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Evitar doble conteo en reintentos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Calcula el costo cuando escribes el evento, no al consultar. Los precios pueden cambiar; el evento debe conservar la tarifa aplicada cuando ocurrió la solicitud.&lt;/p&gt;

&lt;p&gt;Ejemplo en Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PRICING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;# USD por 1M de tokens, a partir de mayo de 2026
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;180.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_cost_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PRICING&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;input_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
    &lt;span class="n"&gt;cache_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
    &lt;span class="n"&gt;output_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cache_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los tokens de razonamiento cuentan como salida. La API los devuelve en:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;usage.completion_tokens_details.reasoning_tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Súmalos a &lt;code&gt;completion_tokens&lt;/code&gt; para calcular el costo. Si no lo haces, subestimarás el gasto en llamadas con razonamiento. Para ver la matemática completa, consulta el &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;desglose de precios de GPT-5.5&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapper de OpenAI con atribución
&lt;/h2&gt;

&lt;p&gt;Todas las llamadas a OpenAI deberían pasar por una única función.&lt;/p&gt;

&lt;p&gt;Ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm.cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_attribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;openai_kwargs&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;openai_kwargs&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;latency_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cached_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cost_usd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_cost_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai.request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ese wrapper es tu punto de control.&lt;/p&gt;

&lt;p&gt;Cada función del producto lo llama. Cada llamada emite una línea JSON. Desde ahí, envía los logs a BigQuery, ClickHouse, Snowflake o Postgres usando tu pipeline existente: Vector, Fluent Bit, Logstash, OTLP collector o equivalente.&lt;/p&gt;

&lt;p&gt;Para Node.js, el patrón es el mismo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;envolver el SDK;&lt;/li&gt;
&lt;li&gt;recibir metadatos obligatorios;&lt;/li&gt;
&lt;li&gt;capturar &lt;code&gt;response.usage&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;calcular &lt;code&gt;cost_usd&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;emitir un evento JSON;&lt;/li&gt;
&lt;li&gt;publicarlo en stdout, Kafka, NATS, Pub/Sub o tu sistema de logs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Implementación paso a paso
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Reemplaza llamadas directas a OpenAI
&lt;/h3&gt;

&lt;p&gt;Busca en tu base de código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAI(
client.chat.completions.create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada llamada directa debe convertirse en:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;call_with_attribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;soporte-chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/chat/answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Haz que &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;customer_id&lt;/code&gt; y &lt;code&gt;environment&lt;/code&gt; sean obligatorios. No uses &lt;code&gt;unknown&lt;/code&gt; como valor por defecto. Si falta un campo, lanza error.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Emite logs estructurados
&lt;/h3&gt;

&lt;p&gt;Registra una línea JSON por solicitud:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai.request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"soporte-chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/v1/chat/answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cliente_4291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;812&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cached_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2341&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.045672&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usa &lt;code&gt;INFO&lt;/code&gt; para estos eventos y evita mezclarlos con logs de depuración.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Agrega por función en tu almacén de datos
&lt;/h3&gt;

&lt;p&gt;Ejemplo de consulta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cache_hit_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con esto puedes ver qué función consume más presupuesto y cómo evoluciona en el tiempo.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Grafica gasto por ruta y cliente
&lt;/h3&gt;

&lt;p&gt;Conecta Grafana, Metabase, Looker o Superset a la tabla y crea tres vistas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gasto por función en el tiempo;&lt;/li&gt;
&lt;li&gt;gasto por cliente en el tiempo;&lt;/li&gt;
&lt;li&gt;top 20 rutas por gasto de ayer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ese panel debería ser parte de tus operaciones diarias.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Prueba el wrapper con Apidog
&lt;/h3&gt;

&lt;p&gt;Antes de desplegar, valida que el wrapper produce eventos correctos. Si el esquema está mal, tus dashboards mentirán.&lt;/p&gt;

&lt;p&gt;Usa &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt; para probar el flujo extremo a extremo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Crea un escenario que llame a tu endpoint de IA con &lt;code&gt;customer_id&lt;/code&gt; y &lt;code&gt;feature&lt;/code&gt; conocidos.&lt;/li&gt;
&lt;li&gt;Captura la respuesta y el log emitido por stdout, OTLP o tu endpoint de logs.&lt;/li&gt;
&lt;li&gt;Agrega aserciones para verificar que el log incluye:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;feature&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;route&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customer_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cost_usd &amp;gt; 0&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prompt_tokens &amp;gt; 0&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ejecuta el mismo escenario en staging y producción usando variables de entorno.&lt;/li&gt;
&lt;li&gt;Reproduce solicitudes etiquetadas y valida que los reintentos no duplican costo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Para enfoques más amplios de pruebas de API, consulta &lt;a href="http://apidog.com/blog/api-testing-tool-qa-engineers?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;herramientas de prueba de API para ingenieros de control de calidad&lt;/a&gt;. Para combinar esto con un enfoque contract-first, revisa &lt;a href="http://apidog.com/blog/api-tool-contract-first-development?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;desarrollo de API "contract-first"&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Configura límites de presupuesto y alertas
&lt;/h3&gt;

&lt;p&gt;Crea claves de proyecto separadas por entorno o función:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prod-support-chat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prod-summarization&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging-all&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configura límites estrictos en OpenAI para que una función no pueda agotar todo el presupuesto de la organización.&lt;/p&gt;

&lt;p&gt;Después, añade alertas propias desde tu almacén de datos. Por ejemplo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;alertar si una función supera 3 veces su gasto horario promedio móvil de 7 días.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Puedes enviar la alerta a PagerDuty, Opsgenie o Slack. El disparador debe venir de tus datos, no del panel de OpenAI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Técnicas avanzadas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Caché de prompts
&lt;/h3&gt;

&lt;p&gt;GPT-5.5 cobra el 50% de la tarifa de entrada por tokens cacheados. Para aprovecharlo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mantén el prompt del sistema estable;&lt;/li&gt;
&lt;li&gt;coloca variables por solicitud al final;&lt;/li&gt;
&lt;li&gt;rastrea &lt;code&gt;cache_hit_rate&lt;/code&gt; por función;&lt;/li&gt;
&lt;li&gt;alerta si una modificación de prompt reduce la tasa de acierto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La &lt;a href="https://platform.openai.com/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;documentación oficial de caché de prompts de OpenAI&lt;/a&gt; explica las reglas de elegibilidad.&lt;/p&gt;

&lt;h3&gt;
  
  
  API por lotes para trabajo offline
&lt;/h3&gt;

&lt;p&gt;Todo lo que no necesita respuesta síncrona debería pasar por la API por lotes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resúmenes nocturnos;&lt;/li&gt;
&lt;li&gt;evaluaciones;&lt;/li&gt;
&lt;li&gt;reprocesamiento de documentos;&lt;/li&gt;
&lt;li&gt;backfills;&lt;/li&gt;
&lt;li&gt;embeddings offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Etiqueta esos eventos con &lt;code&gt;batch_job_id&lt;/code&gt; para atribuirlos a la carga de trabajo original.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ajuste del esfuerzo de razonamiento
&lt;/h3&gt;

&lt;p&gt;GPT-5.5 Thinking usa niveles de &lt;code&gt;reasoning.effort&lt;/code&gt;. Más esfuerzo implica más tokens de salida.&lt;/p&gt;

&lt;p&gt;Audita tus funciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿usas &lt;code&gt;medium&lt;/code&gt; donde &lt;code&gt;low&lt;/code&gt; sería suficiente?&lt;/li&gt;
&lt;li&gt;¿la calidad mejora lo suficiente para justificar el costo?&lt;/li&gt;
&lt;li&gt;¿puedes hacer A/B testing entre niveles?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para más detalles, consulta &lt;a href="http://apidog.com/blog/how-to-use-gpt-5-5-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;cómo usar la API de GPT-5.5&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disciplina en la ventana de contexto
&lt;/h3&gt;

&lt;p&gt;Los prompts largos son caros. RAG con un presupuesto de recuperación ajustado suele ser mejor que meter toda la base de conocimiento en el contexto.&lt;/p&gt;

&lt;p&gt;Rastrea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AVG(prompt_tokens) BY feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si sube semana tras semana sin cambios funcionales, tu prompt se está inflando.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cuidado con el umbral de 272K tokens
&lt;/h3&gt;

&lt;p&gt;OpenAI aplica un multiplicador de entrada de 2x y un multiplicador de salida de 1.5x en solicitudes que superan los 272K tokens.&lt;/p&gt;

&lt;p&gt;Agrega una guarda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;250_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt cerca del umbral de 272K tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para detalles de precios, consulta la &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;publicación sobre precios de GPT-5.5&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Límites de gasto por cliente
&lt;/h3&gt;

&lt;p&gt;Si vendes B2B, necesitas cuotas por cliente.&lt;/p&gt;

&lt;p&gt;Flujo recomendado:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;calcula gasto mensual por &lt;code&gt;customer_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;consulta ese gasto antes de cada llamada;&lt;/li&gt;
&lt;li&gt;si supera la cuota, devuelve &lt;code&gt;429&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;incluye un mensaje claro de límite mensual;&lt;/li&gt;
&lt;li&gt;ofrece una acción de upgrade o contacto con ventas.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai_quota_exceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cuota mensual de IA excedida"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto convierte las funciones de IA de un riesgo de margen en una unidad de producto controlable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errores comunes
&lt;/h2&gt;

&lt;p&gt;Evita estos patrones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contar tokens de razonamiento como entrada;&lt;/li&gt;
&lt;li&gt;confiar en el panel de OpenAI para alertas en tiempo real;&lt;/li&gt;
&lt;li&gt;etiquetar a nivel global del SDK en lugar del sitio de llamada;&lt;/li&gt;
&lt;li&gt;olvidar cron jobs, workers y webhooks;&lt;/li&gt;
&lt;li&gt;muestrear solicitudes;&lt;/li&gt;
&lt;li&gt;permitir &lt;code&gt;customer_id = null&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;calcular costos con una tabla de precios desactualizada;&lt;/li&gt;
&lt;li&gt;no deduplicar reintentos;&lt;/li&gt;
&lt;li&gt;mezclar logs de costo con logs de debug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para jobs internos, usa rutas sintéticas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cron:nightly-summarize
queue:image-caption
worker:document-indexing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Alternativas y herramientas
&lt;/h2&gt;

&lt;p&gt;No siempre necesitas construir todo desde cero.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Lo que hace bien&lt;/th&gt;
&lt;th&gt;Lo que cuesta&lt;/th&gt;
&lt;th&gt;Cuándo usar&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API de uso de OpenAI&lt;/td&gt;
&lt;td&gt;Nativa, sin configuración, precisa para conciliación&lt;/td&gt;
&lt;td&gt;Gratis&lt;/td&gt;
&lt;td&gt;Un proyecto, una función, sin atribución por cliente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helicone&lt;/td&gt;
&lt;td&gt;Proxy fácil, dashboards, caché, costos por usuario&lt;/td&gt;
&lt;td&gt;Nivel gratuito; pago desde $20/mes&lt;/td&gt;
&lt;td&gt;Quieres un panel alojado rápido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Langfuse&lt;/td&gt;
&lt;td&gt;Código abierto, trazas + costo, autoalojado o cloud&lt;/td&gt;
&lt;td&gt;Autoalojado gratis; cloud desde $29/mes&lt;/td&gt;
&lt;td&gt;Quieres observabilidad open source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangSmith&lt;/td&gt;
&lt;td&gt;Integración con LangChain, evaluación + costo&lt;/td&gt;
&lt;td&gt;Pago desde $39/usuario/mes&lt;/td&gt;
&lt;td&gt;Ya usas LangChain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Almacén de datos propio&lt;/td&gt;
&lt;td&gt;Control total, sin proxy, dimensiones personalizadas&lt;/td&gt;
&lt;td&gt;Tiempo de ingeniería&lt;/td&gt;
&lt;td&gt;Cargas grandes, compliance o residencia de datos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Consideraciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un proxy añade un salto en la ruta crítica.&lt;/li&gt;
&lt;li&gt;Un stack autoalojado te da control, pero debes operarlo.&lt;/li&gt;
&lt;li&gt;Un almacén de datos propio se integra mejor con tu stack, pero tú mantienes consultas y alertas.&lt;/li&gt;
&lt;li&gt;La API de uso nativa sirve para conciliación, no para atribución granular.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para más contexto, la &lt;a href="https://www.helicone.ai/blog/monitor-and-optimize-llm-costs" rel="noopener noreferrer"&gt;guía de Helicone sobre seguimiento de costos de LLM&lt;/a&gt; explica el enfoque basado en proxy. La &lt;a href="https://langfuse.com/docs/model-usage-and-cost" rel="noopener noreferrer"&gt;documentación de Langfuse sobre seguimiento de costos&lt;/a&gt; cubre la ruta open source.&lt;/p&gt;

&lt;p&gt;Si operas esto a escala de plataforma, revisa &lt;a href="http://apidog.com/blog/api-platform-microservices-architecture?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;plataformas de API para arquitectura de microservicios&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Casos de uso reales
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SaaS B2B con gasto por cliente
&lt;/h3&gt;

&lt;p&gt;Una empresa vende inteligencia de ventas. Cada cliente activa llamadas a GPT-5.5 al generar informes.&lt;/p&gt;

&lt;p&gt;Sin atribución, solo sabe que gasta $80,000 al mes en OpenAI.&lt;/p&gt;

&lt;p&gt;Con atribución por cliente, descubre que el 12% de los clientes genera el 71% del gasto. Con esos datos puede introducir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;precios escalonados;&lt;/li&gt;
&lt;li&gt;cuotas suaves en planes bajos;&lt;/li&gt;
&lt;li&gt;cargos por exceso;&lt;/li&gt;
&lt;li&gt;upsells basados en uso real.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Herramientas internas para desarrolladores
&lt;/h3&gt;

&lt;p&gt;Una organización da a cada desarrollador acceso a un asistente privado con GPT-5.5.&lt;/p&gt;

&lt;p&gt;Usando &lt;code&gt;customer_id = dev_email&lt;/code&gt;, plataforma detecta que tres desarrolladores concentran 50% del gasto. Dos tenían agentes automatizados corriendo en bucle. Desactivarlos ahorra $1,800 al mes.&lt;/p&gt;

&lt;p&gt;El tercero sí tenía uso legítimo, así que recibe una cuota mayor basada en datos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forecast de nuevas funciones de IA
&lt;/h3&gt;

&lt;p&gt;Un equipo de producto quiere lanzar una función de resumen. Para estimar costo, usa datos históricos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tokens promedio de prompt;&lt;/li&gt;
&lt;li&gt;tokens promedio de salida;&lt;/li&gt;
&lt;li&gt;llamadas esperadas por usuario activo;&lt;/li&gt;
&lt;li&gt;usuarios activos esperados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$0.04 por usuario activo por día
$1.20 por usuario activo por mes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con esa información, el equipo puede fijar el precio de la función en $5 por usuario al mes y justificar el margen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;No puedes gestionar lo que no puedes medir. El panel de facturación de OpenAI responde una pregunta financiera. La atribución por función, cliente y ruta responde la pregunta de producto.&lt;/p&gt;

&lt;p&gt;Implementa el flujo así:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;etiqueta cada solicitud;&lt;/li&gt;
&lt;li&gt;calcula costo al escribir el evento;&lt;/li&gt;
&lt;li&gt;usa claves separadas por entorno o función;&lt;/li&gt;
&lt;li&gt;configura límites nativos en OpenAI;&lt;/li&gt;
&lt;li&gt;agrega alertas desde tu almacén de datos;&lt;/li&gt;
&lt;li&gt;valida el wrapper con &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;audita prompts, caché y esfuerzo de razonamiento periódicamente.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Descarga Apidog&lt;/a&gt; y úsalo para verificar tu wrapper de atribución de costos de extremo a extremo. Envía solicitudes etiquetadas, valida la carga útil del log y reproduce escenarios en distintos entornos antes de confiar en los dashboards.&lt;/p&gt;

&lt;p&gt;Para lecturas relacionadas, consulta el &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;desglose de precios de GPT-5.5&lt;/a&gt; y &lt;a href="http://apidog.com/blog/github-copilot-usage-billing-api-teams?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;la facturación de uso de GitHub Copilot para equipos de API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preguntas frecuentes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Los tokens de razonamiento cuentan como entrada o salida?
&lt;/h3&gt;

&lt;p&gt;Como salida. La API los devuelve en:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;usage.completion_tokens_details.reasoning_tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Súmalos a &lt;code&gt;completion_tokens&lt;/code&gt; cuando calcules el costo. Para multiplicadores y precios, consulta el &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;desglose de precios de GPT-5.5&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué tan preciso es &lt;code&gt;response.usage&lt;/code&gt; frente al panel de OpenAI?
&lt;/h3&gt;

&lt;p&gt;Los recuentos de tokens en &lt;code&gt;response.usage&lt;/code&gt; coinciden con el panel. La desviación aparece si calculas costos con una tabla de tarifas desactualizada. Versiona tu tabla de precios y actualízala cuando OpenAI cambie tarifas.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Puedo hacer atribución solo con claves de proyecto?
&lt;/h3&gt;

&lt;p&gt;Solo parcialmente. Las claves de proyecto dan una dimensión: proyecto. No dan función, cliente ni ruta. Úsalas para separar entornos y establecer límites; usa metadatos de aplicación para la atribución granular.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué pasa con reintentos y errores de rate limit?
&lt;/h3&gt;

&lt;p&gt;Si una solicitud falla antes de ejecutar el modelo, normalmente no hay &lt;code&gt;usage&lt;/code&gt; y no se registra costo. Si la solicitud sí se ejecuta y luego tu aplicación reintenta, puedes duplicar el costo si no deduplicas.&lt;/p&gt;

&lt;p&gt;Usa el mismo &lt;code&gt;request_id&lt;/code&gt; en reintentos idempotentes y deduplica al escribir.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué tan rápido devuelve datos la API de uso de OpenAI?
&lt;/h3&gt;

&lt;p&gt;Tiene retraso de decenas de minutos. Úsala para conciliación mensual. Para alertas o interruptores de emergencia, usa tus propios eventos.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Debo muestrear solicitudes para reducir logs?
&lt;/h3&gt;

&lt;p&gt;No. El volumen es pequeño: una línea JSON por solicitud. El muestreo rompe la atribución por cliente y ruta.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Funciona con otros proveedores de LLM?
&lt;/h3&gt;

&lt;p&gt;Sí. Agrega una columna:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ejemplos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openai
anthropic
google
deepseek
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El wrapper cambia por proveedor, pero el almacén de datos y los dashboards pueden mantenerse. Para comparar precios, consulta &lt;a href="http://apidog.com/blog/deepseek-v4-api-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;precios de la API de DeepSeek V4&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Funciona para embeddings e imágenes?
&lt;/h3&gt;

&lt;p&gt;Sí, pero la fórmula cambia.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embeddings: costo por token de entrada.&lt;/li&gt;
&lt;li&gt;Imágenes: costo por imagen y resolución.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agrega una columna &lt;code&gt;endpoint&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chat
embeddings
image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Después ramifica el cálculo de costos según el endpoint.&lt;/p&gt;

</description>
      <category>api</category>
      <category>openai</category>
      <category>spanish</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>OpenAI API 기능별 사용량 추적: 비용 귀속 가이드</title>
      <dc:creator>Rihpig</dc:creator>
      <pubDate>Tue, 12 May 2026 02:38:49 +0000</pubDate>
      <link>https://dev.to/rihpig/openai-api-gineungbyeol-sayongryang-cujeog-biyong-gwisog-gaideu-3h6b</link>
      <guid>https://dev.to/rihpig/openai-api-gineungbyeol-sayongryang-cujeog-biyong-gwisog-gaideu-3h6b</guid>
      <description>&lt;p&gt;OpenAI 인보이스는 지난달 $4,237를 썼다고 알려줍니다. 하지만 그중 $3,100가 폭주한 요약 엔드포인트에서 발생했고, $700는 월 $50를 내는 고객에게서, $437는 아무도 쓰지 않는 기능에서 발생했다는 사실은 알려주지 않습니다. 기본 대시보드만으로는 가격 책정, 용량 계획, 로드맵 결정을 할 수 없습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;지금 Apidog 사용해 보기&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;이 글에서는 OpenAI API 비용을 기능, 경로, 고객 단위로 귀속하는 구현 방법을 다룹니다. 핵심은 모든 요청에 메타데이터를 붙이고, 토큰 수와 비용을 구조화된 로그로 남기고, 데이터 웨어하우스에서 집계한 뒤, 키별 예산 상한과 알림을 설정하는 것입니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Apidog는 비용 추적 래퍼를 프로덕션에 배포하기 전에 요청 수준 가시성과 시나리오 테스트를 제공합니다. 태그가 지정된 요청을 재생하고, 로그 형태를 확인하며, 모든 호출이 데이터 웨어하우스가 기대하는 메타데이터를 포함하는지 검증하는 데 사용할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  요약 (TL;DR)
&lt;/h2&gt;

&lt;p&gt;구현해야 할 것은 단순합니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;모든 OpenAI API 호출을 하나의 래퍼 함수로 통과시킵니다.&lt;/li&gt;
&lt;li&gt;각 호출에 &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;customer_id&lt;/code&gt;, &lt;code&gt;environment&lt;/code&gt;를 필수로 붙입니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;response.usage&lt;/code&gt;에서 토큰 수를 읽고, 쓰기 시점에 &lt;code&gt;cost_usd&lt;/code&gt;를 계산합니다.&lt;/li&gt;
&lt;li&gt;요청당 JSON 로그 한 줄을 남깁니다.&lt;/li&gt;
&lt;li&gt;BigQuery, ClickHouse, Snowflake, Postgres 같은 웨어하우스에서 집계합니다.&lt;/li&gt;
&lt;li&gt;OpenAI 프로젝트 키별 상한과 자체 알림을 함께 둡니다.&lt;/li&gt;
&lt;li&gt;Apidog 시나리오 테스트로 래퍼와 로그 스키마를 검증합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  왜 OpenAI 청구 대시보드만으로는 부족한가
&lt;/h2&gt;

&lt;p&gt;OpenAI 청구 페이지는 일일 지출, 모델별 사용량, 조직 수준 제한을 보여줍니다. 애플리케이션 하나, 고객 하나, 기능 하나만 있다면 충분할 수 있습니다.&lt;/p&gt;

&lt;p&gt;하지만 실제 프로덕션에서는 보통 다음 질문에 답해야 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;어떤 기능이 비용을 만들었는가?&lt;/li&gt;
&lt;li&gt;어떤 고객이 가장 많은 비용을 발생시키는가?&lt;/li&gt;
&lt;li&gt;어떤 API 경로가 폭주하고 있는가?&lt;/li&gt;
&lt;li&gt;스테이징 비용과 프로덕션 비용은 분리되어 있는가?&lt;/li&gt;
&lt;li&gt;특정 기능의 시간당 지출이 평소보다 급증했는가?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;기본 대시보드는 이 질문에 답하지 못합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  기본 대시보드의 한계
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;컨텍스트 없는 총액&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;대시보드는 어제 GPT-5.5에 $312를 썼다고 알려줄 수 있습니다. 하지만 이것이 고객 한 명의 지원 채팅 호출 때문인지, 잘못된 배치 작업이 전체 지식 기반을 다시 요약했기 때문인지는 알려주지 않습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;기능별 분석 없음&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenAI는 모델과 키 기준 사용량은 보여주지만, 제품 기능, HTTP 경로, 고객 ID, 환경 같은 애플리케이션 수준 차원은 제공하지 않습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;보고 지연&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;사용량 데이터는 수십 분에서 몇 시간 지연될 수 있습니다. 폭주 루프를 대시보드에서 확인했을 때는 이미 비용이 발생한 뒤입니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;세밀한 알림 부족&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;조직 단위 예산 상한과 이메일 알림은 가능하지만, “지원 채팅 엔드포인트가 한 시간에 $50를 넘으면 Slack으로 알림” 같은 조건은 직접 만들어야 합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;고객별 귀속 없음&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;B2B SaaS에서 AI 기능을 제공한다면 고객별 LLM 원가를 알아야 합니다. 그래야 가격 책정, 사용량 제한, 상향 판매, 총마진 계산이 가능합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;프로젝트 키만으로는 부족함&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenAI 프로젝트 키는 프로젝트별 분리를 제공합니다. 하지만 기능별, 고객별, 경로별 귀속은 여전히 애플리케이션에서 직접 처리해야 합니다. &lt;a href="https://platform.openai.com/docs/api-reference/usage" rel="noopener noreferrer"&gt;OpenAI 사용량 API&lt;/a&gt;도 요청 단위가 아니라 집계 데이터를 반환합니다.&lt;/p&gt;

&lt;p&gt;이 문제는 LLM 기능을 운영하는 대부분의 팀에서 반복됩니다. &lt;a href="http://Dev.to" rel="noopener noreferrer"&gt;Dev.to&lt;/a&gt;의 “OpenAI는 당신이 얼마를 썼는지 알려줍니다. 어디에 썼는지는 아닙니다. 그래서 대시보드를 만들었습니다”라는 주제가 공감을 얻은 이유도 여기에 있습니다.&lt;/p&gt;

&lt;p&gt;비용 계산에 필요한 가격 맥락은 &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 가격 분석&lt;/a&gt;을 참고하십시오. 개발자 도구 측면의 관련 문제는 &lt;a href="http://apidog.com/blog/github-copilot-usage-billing-api-teams?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 팀을 위한 GitHub Copilot 사용량 청구&lt;/a&gt;를 참고할 수 있습니다. OpenAI API 기본 사항은 &lt;a href="https://platform.openai.com/docs/api-reference" rel="noopener noreferrer"&gt;공식 OpenAI API 참조&lt;/a&gt;를 확인하십시오.&lt;/p&gt;

&lt;h2&gt;
  
  
  비용 귀속 데이터 모델 설계
&lt;/h2&gt;

&lt;p&gt;비용 귀속의 기본 단위는 “OpenAI 요청 1건”입니다.&lt;/p&gt;

&lt;p&gt;모든 요청은 다음 정보를 가진 이벤트로 기록되어야 합니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;열&lt;/th&gt;
&lt;th&gt;유형&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;request_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;uuid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7a91...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;멱등성, 중복 제거, 재시도 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamptz&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-05-06T14:23:01Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;시계열 분석, 이상 감지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;support-chat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호출을 발생시킨 제품 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;route&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/chat/answer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 경로 또는 백그라운드 작업 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cust_4291&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;고객별 지출, 총마진 계산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;prod&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, &lt;code&gt;dev&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;개발/운영 비용 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gpt-5.5&lt;/code&gt;, &lt;code&gt;gpt-5.4-mini&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;모델별 가격 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15234&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;입력 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completion_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;812&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;출력 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reasoning_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4500&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;추론 토큰, 출력 요금으로 계산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cached_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;프롬프트 캐시 적중 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;latency_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2341&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;비용과 성능 상관관계 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cost_usd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.045672&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;쓰기 시점 계산 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_cache_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system-v3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;캐시 적중률 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error_code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;null&lt;/code&gt;, &lt;code&gt;429&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;실패/재시도 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;중요한 원칙은 &lt;strong&gt;쿼리 시점이 아니라 쓰기 시점에 비용을 계산하는 것&lt;/strong&gt;입니다. 가격은 바뀔 수 있습니다. 과거 이벤트는 해당 요청이 발생한 날의 요율로 고정되어야 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  비용 계산 함수 만들기
&lt;/h2&gt;

&lt;p&gt;다음은 GPT-5.5 계열 가격을 기준으로 한 예시입니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PRICING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;# USD per 1M tokens, as of May 2026
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;180.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_cost_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PRICING&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;input_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
    &lt;span class="n"&gt;cache_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
    &lt;span class="n"&gt;output_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cache_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;주의할 점은 &lt;code&gt;reasoning_tokens&lt;/code&gt;입니다. OpenAI API는 이를 &lt;code&gt;usage.completion_tokens_details.reasoning_tokens&lt;/code&gt;에 반환하지만, 요금은 출력 토큰 기준으로 계산됩니다. 이를 입력으로 처리하면 Thinking 모드 호출 비용을 잘못 계산하게 됩니다.&lt;/p&gt;

&lt;p&gt;전체 가격 맥락은 &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 가격 분석&lt;/a&gt;을 참고하십시오.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAI 클라이언트 래퍼 구현
&lt;/h2&gt;

&lt;p&gt;이제 모든 OpenAI 호출이 하나의 함수만 통과하도록 만듭니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm.cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_attribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;openai_kwargs&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;openai_kwargs&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;

    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;latency_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cached_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cost_usd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_cost_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai.request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 래퍼가 비용 귀속의 단일 진입점입니다.&lt;/p&gt;

&lt;p&gt;코드베이스에서 다음 패턴을 검색해 모두 교체합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAI(
client.chat.completions.create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;모든 호출은 다음처럼 명시적으로 태그를 전달해야 합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_with_attribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/chat/answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_question&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;customer_id&lt;/code&gt;, &lt;code&gt;environment&lt;/code&gt;는 기본값을 두지 않는 것이 좋습니다. 누락되면 &lt;code&gt;"unknown"&lt;/code&gt;으로 기록하지 말고 오류를 발생시키십시오. &lt;code&gt;"unknown"&lt;/code&gt;은 나중에 귀속 블랙홀이 됩니다.&lt;/p&gt;

&lt;p&gt;Node.js에서도 구조는 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI SDK를 직접 호출하지 않습니다.&lt;/li&gt;
&lt;li&gt;래퍼 함수가 메타데이터를 받습니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;response.usage&lt;/code&gt;를 읽습니다.&lt;/li&gt;
&lt;li&gt;비용을 계산합니다.&lt;/li&gt;
&lt;li&gt;JSON 이벤트를 stdout, Kafka, NATS, Pub/Sub, OTLP 중 하나로 보냅니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  구조화 로그를 데이터 웨어하우스로 보내기
&lt;/h2&gt;

&lt;p&gt;요청당 JSON 한 줄이면 충분합니다.&lt;/p&gt;

&lt;p&gt;예시 로그:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai.request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7a91d2d3-1d7f-4a21-91b5-f29b7f12a111"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support-chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/v1/chat/answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cust_4291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;812&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cached_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2341&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.045672&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;기존 로그 파이프라인을 재사용하십시오.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vector&lt;/li&gt;
&lt;li&gt;Fluent Bit&lt;/li&gt;
&lt;li&gt;Logstash&lt;/li&gt;
&lt;li&gt;OpenTelemetry Collector&lt;/li&gt;
&lt;li&gt;Cloud Logging&lt;/li&gt;
&lt;li&gt;Datadog&lt;/li&gt;
&lt;li&gt;Kafka 기반 이벤트 파이프라인&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;대상은 BigQuery, ClickHouse, Snowflake, Postgres 등 무엇이든 됩니다. 별도 서비스가 꼭 필요한 것은 아닙니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  기능별 비용 집계 쿼리
&lt;/h2&gt;

&lt;p&gt;이벤트가 웨어하우스에 들어오면, 대시보드는 SQL 문제로 바뀝니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cache_hit_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;운영 대시보드에는 최소한 다음 세 가지 뷰를 만드십시오.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;시간대별 기능별 지출&lt;/li&gt;
&lt;li&gt;시간대별 고객별 지출&lt;/li&gt;
&lt;li&gt;어제 지출 기준 상위 20개 경로&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Grafana, Metabase, Looker, Superset 등 어떤 BI 도구든 사용할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apidog로 배포 전 검증하기
&lt;/h2&gt;

&lt;p&gt;많은 팀이 래퍼는 만들지만 검증을 건너뜁니다. 그러면 스키마가 조용히 틀어지고, 대시보드는 그럴듯한 거짓말을 보여줍니다.&lt;/p&gt;

&lt;p&gt;Apidog로 다음 시나리오를 만드십시오.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;알려진 &lt;code&gt;customer_id&lt;/code&gt;, &lt;code&gt;feature&lt;/code&gt;를 사용해 AI 엔드포인트에 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;응답과 로그 방출을 함께 확인합니다.&lt;/li&gt;
&lt;li&gt;로그 페이로드에 다음 필드가 있는지 검증합니다.

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;route&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prompt_tokens&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completion_tokens&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cost_usd&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cost_usd &amp;gt; 0&lt;/code&gt;인지 확인합니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prompt_tokens &amp;gt; 0&lt;/code&gt;인지 확인합니다.&lt;/li&gt;
&lt;li&gt;Apidog 환경 변수를 사용해 staging/prod에서 같은 시나리오를 실행합니다.&lt;/li&gt;
&lt;li&gt;동일 요청을 재생하여 재시도 시 비용이 중복 집계되지 않는지 확인합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;재시도 처리에서는 &lt;code&gt;request_id&lt;/code&gt;가 중요합니다. 애플리케이션 레벨에서 같은 작업을 재시도한다면 같은 &lt;code&gt;request_id&lt;/code&gt;를 전달하고, 웨어하우스 적재 또는 집계 단계에서 중복 제거해야 합니다.&lt;/p&gt;

&lt;p&gt;API 테스트 접근 방식은 &lt;a href="http://apidog.com/blog/api-testing-tool-qa-engineers?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;QA 엔지니어를 위한 API 테스트 도구&lt;/a&gt;를 참고하십시오. 계약 기반 API 개발과 함께 운영하려면 &lt;a href="http://apidog.com/blog/api-tool-contract-first-development?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;계약 우선 API 개발&lt;/a&gt;도 참고할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  프로젝트 키와 예산 상한 설정
&lt;/h2&gt;

&lt;p&gt;애플리케이션 수준 귀속과 별개로 OpenAI 프로젝트 키는 방어선으로 사용하십시오.&lt;/p&gt;

&lt;p&gt;예시:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prod-support-chat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prod-summarization&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prod-agent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging-all&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dev-all&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;각 키에 하드 상한을 설정하면 하나의 기능이 폭주해도 조직 전체 예산을 소진하지 않습니다.&lt;/p&gt;

&lt;p&gt;그 위에 자체 알림을 둡니다.&lt;/p&gt;

&lt;p&gt;예를 들어 10분마다 다음 로직을 실행합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;기능별 최근 1시간 지출 계산&lt;/li&gt;
&lt;li&gt;같은 기능의 7일 이동 평균 시간당 지출 계산&lt;/li&gt;
&lt;li&gt;현재 지출이 평균의 3배를 넘으면 Slack/PagerDuty/Opsgenie 알림&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;트리거는 OpenAI 대시보드가 아니라 데이터 웨어하우스에서 나와야 합니다. 그래야 지연을 줄이고 원하는 차원으로 감지할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  고급 최적화 패턴
&lt;/h2&gt;

&lt;h3&gt;
  
  
  프롬프트 캐싱
&lt;/h3&gt;

&lt;p&gt;GPT-5.5는 캐시된 토큰에 대해 입력 요금의 50%를 청구합니다.&lt;/p&gt;

&lt;p&gt;캐시 적중률을 높이려면 다음처럼 프롬프트를 구성하십시오.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;안정적인 시스템 프롬프트를 앞에 둡니다.&lt;/li&gt;
&lt;li&gt;자주 바뀌는 사용자 입력은 뒤에 둡니다.&lt;/li&gt;
&lt;li&gt;버전이 바뀌는 프롬프트에는 &lt;code&gt;prompt_cache_key&lt;/code&gt;를 명시적으로 기록합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;대시보드에서는 기능별 &lt;code&gt;cache_hit_rate&lt;/code&gt;를 추적하십시오. 프롬프트 변경 후 캐시 적중률이 떨어지면 입력 비용이 조용히 증가할 수 있습니다.&lt;/p&gt;

&lt;p&gt;공식 규칙은 &lt;a href="https://platform.openai.com/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;OpenAI 프롬프트 캐싱 문서&lt;/a&gt;를 참고하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  배치 API 사용
&lt;/h3&gt;

&lt;p&gt;동기 응답이 필요 없는 작업은 배치 API로 보내십시오.&lt;/p&gt;

&lt;p&gt;대표 예시:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;야간 요약&lt;/li&gt;
&lt;li&gt;평가 실행&lt;/li&gt;
&lt;li&gt;임베딩 백필&lt;/li&gt;
&lt;li&gt;문서 재처리&lt;/li&gt;
&lt;li&gt;대량 분류 작업&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;배치 작업에도 동일한 귀속 스키마를 적용하십시오. &lt;code&gt;batch_job_id&lt;/code&gt;를 추가하면 원래 워크로드와 연결할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  추론 노력 튜닝
&lt;/h3&gt;

&lt;p&gt;GPT-5.5 Thinking 계열은 &lt;code&gt;reasoning.effort&lt;/code&gt;에 따라 출력 토큰이 늘어납니다.&lt;/p&gt;

&lt;p&gt;각 기능에 대해 다음을 점검하십시오.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;medium&lt;/code&gt;이 꼭 필요한가?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;low&lt;/code&gt;로 품질 기준을 통과하는가?&lt;/li&gt;
&lt;li&gt;노력 수준별 비용 대비 품질은 어떤가?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A/B 테스트를 통해 품질과 비용을 함께 기록하고, 품질이 유지된다면 더 낮은 옵션을 배포하십시오. 관련 구현 맥락은 &lt;a href="http://apidog.com/blog/how-to-use-gpt-5-5-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 API 사용 방법&lt;/a&gt;을 참고하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  컨텍스트 창 관리
&lt;/h3&gt;

&lt;p&gt;긴 프롬프트는 비용을 빠르게 증가시킵니다.&lt;/p&gt;

&lt;p&gt;RAG를 사용한다면 전체 문서를 컨텍스트 창에 넣지 말고 검색 예산을 제한하십시오.&lt;/p&gt;

&lt;p&gt;기능별로 다음 지표를 추적합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;PERCENTILE_CONT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p95_prompt_tokens&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;avg_prompt_tokens&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;기능 변경이 없는데 &lt;code&gt;prompt_tokens&lt;/code&gt;가 매주 증가한다면 프롬프트가 비대해지고 있는 것입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  GPT-5.5 272K 토큰 절벽 감지
&lt;/h3&gt;

&lt;p&gt;GPT-5.5는 272K 토큰을 초과하는 요청에 대해 입력 2배, 출력 1.5배 승수가 적용됩니다.&lt;/p&gt;

&lt;p&gt;래퍼에 다음 가드를 추가하십시오.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;250_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai.large_context_warning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;가격 세부 정보는 &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 가격 책정 게시물&lt;/a&gt;을 참고하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  고객별 지출 상한
&lt;/h3&gt;

&lt;p&gt;B2B SaaS에서는 고객별 AI 사용량 제한이 필요합니다.&lt;/p&gt;

&lt;p&gt;간단한 패턴은 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;고객별 월 누적 &lt;code&gt;cost_usd&lt;/code&gt;를 계산합니다.&lt;/li&gt;
&lt;li&gt;각 OpenAI 호출 전에 고객의 사용량을 확인합니다.&lt;/li&gt;
&lt;li&gt;한도를 넘으면 OpenAI를 호출하지 않고 429를 반환합니다.&lt;/li&gt;
&lt;li&gt;응답에는 “월별 AI 할당량 초과” 메시지와 업그레이드 CTA를 포함합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;예시:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ensure_customer_budget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;spend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_month_to_date_llm_spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_customer_llm_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;spend&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AiQuotaExceeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Monthly AI quota exceeded for customer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 해야 AI 기능이 마진 리스크가 아니라 가격 책정 가능한 제품 기능이 됩니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  피해야 할 실수
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;추론 토큰을 입력 토큰으로 계산하는 것&lt;/li&gt;
&lt;li&gt;실시간 알림을 OpenAI 대시보드에 의존하는 것&lt;/li&gt;
&lt;li&gt;SDK 내부에서만 태그를 붙이고 호출 지점의 기능 컨텍스트를 잃는 것&lt;/li&gt;
&lt;li&gt;Cron, 큐 워커, 웹훅 같은 백그라운드 작업에 태그를 붙이지 않는 것&lt;/li&gt;
&lt;li&gt;요청을 샘플링하는 것&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customer_id&lt;/code&gt;를 &lt;code&gt;null&lt;/code&gt;로 두는 것&lt;/li&gt;
&lt;li&gt;실패한 요청과 성공 후 재시도된 요청을 구분하지 않는 것&lt;/li&gt;
&lt;li&gt;가격표를 코드에 넣고 업데이트 절차를 만들지 않는 것&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;백그라운드 작업에는 다음처럼 합성 route를 사용하십시오.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cron:nightly-summarize
queue:image-caption
webhook:crm-sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;customer_id&lt;/code&gt;를 모를 경우에도 &lt;code&gt;null&lt;/code&gt; 대신 &lt;code&gt;internal&lt;/code&gt;, &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;unknown_customer&lt;/code&gt;처럼 명시적인 값을 사용하십시오.&lt;/p&gt;

&lt;h2&gt;
  
  
  대안 및 도구 비교
&lt;/h2&gt;

&lt;p&gt;직접 구축하지 않아도 됩니다. 선택지는 다음과 같습니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;접근 방식&lt;/th&gt;
&lt;th&gt;강점&lt;/th&gt;
&lt;th&gt;비용&lt;/th&gt;
&lt;th&gt;적합한 경우&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI 사용량 API&lt;/td&gt;
&lt;td&gt;기본 제공, 설정 불필요, 정확도 높음&lt;/td&gt;
&lt;td&gt;무료&lt;/td&gt;
&lt;td&gt;프로젝트/기능 수가 적고 고객별 귀속이 필요 없을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helicone&lt;/td&gt;
&lt;td&gt;드롭인 프록시, 대시보드, 캐싱, 사용자별 비용&lt;/td&gt;
&lt;td&gt;무료 티어, 유료는 월 $20부터&lt;/td&gt;
&lt;td&gt;빠르게 호스팅 대시보드가 필요하고 프록시를 허용할 수 있을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Langfuse&lt;/td&gt;
&lt;td&gt;오픈 소스, 자체 호스팅/클라우드, 추적 + 비용&lt;/td&gt;
&lt;td&gt;자체 호스팅 무료, 클라우드 월 $29부터&lt;/td&gt;
&lt;td&gt;추적과 비용 관찰성을 한 도구에서 원할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangSmith&lt;/td&gt;
&lt;td&gt;LangChain 통합, 평가 + 비용&lt;/td&gt;
&lt;td&gt;사용자당 월 $39부터&lt;/td&gt;
&lt;td&gt;이미 LangChain을 사용하고 있을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;맞춤형 데이터 웨어하우스&lt;/td&gt;
&lt;td&gt;완전한 제어, 기존 스택과 통합, 프록시 없음&lt;/td&gt;
&lt;td&gt;엔지니어링 시간&lt;/td&gt;
&lt;td&gt;대규모 워크로드, 맞춤형 차원, 데이터 상주 요건이 있을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;프록시 기반 도구는 빠르게 시작할 수 있지만 경로에 추가 홉이 생깁니다. 자체 호스팅 관찰성 도구는 제어권이 크지만 운영 부담이 있습니다. 맞춤형 웨어하우스 접근은 초기 구현이 필요하지만 대규모 팀이 결국 선택하는 경우가 많습니다.&lt;/p&gt;

&lt;p&gt;프록시 기반 접근은 &lt;a href="https://www.helicone.ai/blog/monitor-and-optimize-llm-costs" rel="noopener noreferrer"&gt;Helicone 팀의 LLM 비용 추적 가이드&lt;/a&gt;를 참고하십시오. 오픈 소스 접근은 &lt;a href="https://langfuse.com/docs/model-usage-and-cost" rel="noopener noreferrer"&gt;Langfuse 비용 추적 문서&lt;/a&gt;를 참고할 수 있습니다.&lt;/p&gt;

&lt;p&gt;플랫폼 규모에서 이 패턴을 운영한다면 &lt;a href="http://apidog.com/blog/api-platform-microservices-architecture?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;마이크로서비스 아키텍처를 위한 API 플랫폼&lt;/a&gt;도 참고하십시오.&lt;/p&gt;

&lt;h2&gt;
  
  
  실제 사용 사례
&lt;/h2&gt;

&lt;h3&gt;
  
  
  고객별 LLM 지출이 필요한 B2B SaaS
&lt;/h3&gt;

&lt;p&gt;한 영업 인텔리전스 제품은 고객의 요약 요청마다 GPT-5.5를 호출합니다.&lt;/p&gt;

&lt;p&gt;귀속을 도입하기 전에는 월 OpenAI 비용이 $80,000라는 사실만 알 수 있었습니다. 고객별 귀속을 적용한 뒤에는 고객의 12%가 지출의 71%를 만든다는 것을 확인했습니다.&lt;/p&gt;

&lt;p&gt;이후 팀은 다음을 도입했습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;계층별 가격&lt;/li&gt;
&lt;li&gt;하위 플랜의 소프트 할당량&lt;/li&gt;
&lt;li&gt;좌석당 초과 요금&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그 결과 AI 기능의 총마진이 한 분기 만에 41%에서 73%로 증가했습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  내부 개발자 도구 비용 추적
&lt;/h3&gt;

&lt;p&gt;한 엔지니어링 조직은 모든 개발자에게 개인 GPT-5.5 채팅 도우미를 제공합니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;customer_id&lt;/code&gt;에 개발자 이메일을 기록하자, 세 명의 개발자가 내부 LLM 지출의 50%를 차지한다는 사실이 드러났습니다.&lt;/p&gt;

&lt;p&gt;두 명은 끄는 것을 잊은 자동화 에이전트 루프를 실행 중이었고, 이를 중단해 월 $1,800를 절감했습니다. 나머지 한 명은 실제 업무상 높은 사용량이 필요했고, 데이터는 더 높은 할당량을 정당화하는 근거가 되었습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI 기능 출시 전 비용 예측
&lt;/h3&gt;

&lt;p&gt;제품팀이 새 요약 기능을 출시하려 할 때, 과거 기능별 데이터를 사용해 다음을 예측할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;호출당 평균 입력 토큰&lt;/li&gt;
&lt;li&gt;호출당 평균 출력 토큰&lt;/li&gt;
&lt;li&gt;활성 사용자당 예상 호출 수&lt;/li&gt;
&lt;li&gt;예상 활성 사용자 수&lt;/li&gt;
&lt;li&gt;사용자당 일/월 비용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예측 결과가 활성 사용자당 하루 $0.04, 월 $1.20이라면, 가격 책정팀은 기능을 사용자당 월 $5로 책정할 수 있습니다. 단위 경제학이 보이면 재무 승인도 쉬워집니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  결론
&lt;/h2&gt;

&lt;p&gt;OpenAI 청구 대시보드는 “얼마를 썼는가”에는 답합니다. 하지만 제품팀과 엔지니어링팀은 “어디에서, 누가, 왜 썼는가”를 알아야 합니다.&lt;/p&gt;

&lt;p&gt;구현 체크리스트는 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;모든 요청에 &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;customer_id&lt;/code&gt;, &lt;code&gt;environment&lt;/code&gt;를 필수로 붙입니다.&lt;/li&gt;
&lt;li&gt;OpenAI 호출을 단일 래퍼로 통과시킵니다.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;response.usage&lt;/code&gt;에서 토큰 수를 읽고 쓰기 시점에 비용을 계산합니다.&lt;/li&gt;
&lt;li&gt;요청당 구조화된 JSON 로그를 남깁니다.&lt;/li&gt;
&lt;li&gt;웨어하우스에서 기능별, 고객별, 경로별로 집계합니다.&lt;/li&gt;
&lt;li&gt;프로젝트 키별 하드 상한을 설정합니다.&lt;/li&gt;
&lt;li&gt;웨어하우스 기반 알림을 추가합니다.&lt;/li&gt;
&lt;li&gt;배포 전에 Apidog로 로그 스키마와 시나리오를 검증합니다.&lt;/li&gt;
&lt;li&gt;추론 노력, 프롬프트 크기, 캐시 적중률을 정기적으로 감사합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog를 다운로드&lt;/a&gt;하여 비용 귀속 래퍼를 엔드투엔드로 검증할 수 있습니다. 태그가 지정된 요청으로 AI 엔드포인트를 호출하고, 로그 페이로드 형태를 확인하며, 여러 환경에서 시나리오를 재생해 데이터 웨어하우스가 신뢰할 수 있는 데이터를 받고 있는지 확인하십시오.&lt;/p&gt;

&lt;p&gt;관련 비용 관리 자료는 &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 가격 분석&lt;/a&gt;과 &lt;a href="http://apidog.com/blog/github-copilot-usage-billing-api-teams?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API 팀을 위한 GitHub Copilot 사용량 청구&lt;/a&gt;를 참고하십시오.&lt;/p&gt;

&lt;h2&gt;
  
  
  자주 묻는 질문 (FAQ)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  추론 토큰은 입력인가요, 출력인가요?
&lt;/h3&gt;

&lt;p&gt;출력 요율로 청구됩니다. OpenAI API는 &lt;code&gt;usage.completion_tokens_details.reasoning_tokens&lt;/code&gt;에 값을 반환합니다. 비용 계산 시 &lt;code&gt;completion_tokens&lt;/code&gt;에 더하십시오. 자세한 내용은 &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 가격 분석&lt;/a&gt;을 참고하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;response.usage&lt;/code&gt;는 OpenAI 대시보드와 얼마나 일치하나요?
&lt;/h3&gt;

&lt;p&gt;토큰 수는 대시보드와 토큰 단위로 일치합니다. 다만 오래된 가격표로 비용을 계산하면 가격 변경 때문에 차이가 생길 수 있습니다. 모델별 요율은 고정하고, OpenAI가 가격 변경을 발표하면 버전을 업데이트하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAI 프로젝트 키만으로 귀속할 수 있나요?
&lt;/h3&gt;

&lt;p&gt;부분적으로만 가능합니다. 프로젝트 키는 프로젝트 단위 귀속을 제공합니다. 기능별, 고객별, 경로별 귀속은 애플리케이션 메타데이터가 필요합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  재시도 요청은 비용이 이중 계산되나요?
&lt;/h3&gt;

&lt;p&gt;모델 실행 전에 실패한 요청은 보통 &lt;code&gt;usage&lt;/code&gt;를 반환하지 않으므로 비용이 기록되지 않습니다. 하지만 성공 후 애플리케이션 레이어에서 재시도하면 중복 기록될 수 있습니다. 같은 작업의 재시도는 동일한 &lt;code&gt;request_id&lt;/code&gt;를 사용하고, 저장 또는 집계 단계에서 중복 제거하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAI 사용량 API는 실시간인가요?
&lt;/h3&gt;

&lt;p&gt;아니요. 수십 분 정도 지연될 수 있습니다. 실시간 알림, 킬 스위치, 고객별 제한에는 자체 이벤트 로그와 웨어하우스를 사용하십시오. 월별 대사에는 사용량 API가 적합합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  로그 볼륨을 줄이기 위해 샘플링해도 되나요?
&lt;/h3&gt;

&lt;p&gt;권장하지 않습니다. 요청당 JSON 한 줄이면 데이터 볼륨은 작습니다. 샘플링하면 고객별, 경로별 귀속 정확도가 깨집니다. 모든 요청을 기록하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  다른 LLM 공급자에도 같은 방식을 쓸 수 있나요?
&lt;/h3&gt;

&lt;p&gt;가능합니다. 스키마에 &lt;code&gt;provider&lt;/code&gt; 열을 추가하십시오.&lt;/p&gt;

&lt;p&gt;예:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openai
anthropic
google
deepseek
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;공급자별 래퍼와 가격표만 다르고, 웨어하우스와 대시보드는 재사용할 수 있습니다. 비교 자료로 &lt;a href="http://apidog.com/blog/deepseek-v4-api-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;DeepSeek V4 API 가격&lt;/a&gt;을 참고하십시오.&lt;/p&gt;

&lt;h3&gt;
  
  
  임베딩과 이미지 생성에도 적용되나요?
&lt;/h3&gt;

&lt;p&gt;예. 비용 계산식만 다릅니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;임베딩: 입력 토큰 기준 과금&lt;/li&gt;
&lt;li&gt;이미지 생성: 이미지 수, 해상도, 품질 기준 과금&lt;/li&gt;
&lt;li&gt;채팅: 입력/캐시/출력/추론 토큰 기준 과금&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;스키마에 &lt;code&gt;endpoint&lt;/code&gt;를 추가하면 됩니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chat
embeddings
image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그리고 &lt;code&gt;endpoint&lt;/code&gt;별로 비용 계산 함수를 분기하십시오.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>api</category>
      <category>openai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>OpenAI API利用料金を機能別に追跡する方法：コスト配分プレイブック</title>
      <dc:creator>Akira</dc:creator>
      <pubDate>Tue, 12 May 2026 02:37:48 +0000</pubDate>
      <link>https://dev.to/aakira/openai-apili-yong-liao-jin-woji-neng-bie-nizhui-ji-surufang-fa-kosutopei-fen-pureibutuku-57o5</link>
      <guid>https://dev.to/aakira/openai-apili-yong-liao-jin-woji-neng-bie-nizhui-ji-surufang-fa-kosutopei-fen-pureibutuku-57o5</guid>
      <description>&lt;p&gt;OpenAIの請求書には、先月4,237ドル使ったと書かれています。しかし、そのうち3,100ドルは暴走した要約エンドポイントから、700ドルは月に50ドル支払っている顧客から、437ドルは誰も使わない機能から発生したことは書かれていません。ダッシュボードでは、価格設定、キャパシティ、ロードマップの判断に必要な情報が隠れています。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;今すぐApidogを試す&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;このガイドでは、OpenAI APIのコストを機能、ルート、顧客、環境ごとに割り当てる実装方法を説明します。すべてのリクエストにメタデータを付与し、トークン数とコストを構造化ログとして出力し、ウェアハウスで集計し、予算上限とアラートを設定します。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Apidogは、コスト追跡ラッパーを本番環境に出す前に、リクエストレベルの可視性とシナリオテストを提供します。タグ付きリクエストの再生、ログ形式のアサート、すべての呼び出しがウェアハウスの期待するメタデータを持つことの検証に使えます。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;OpenAI API呼び出しごとに、以下を必ず記録します。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;route&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;トークン数&lt;/li&gt;
&lt;li&gt;計算済みの&lt;code&gt;cost_usd&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;そのうえで、ウェアハウスでタグごとに集計し、OpenAI側ではキーごとの予算上限を設定します。さらに、時間ごとの支出異常を検知し、リリース前にApidogのシナリオテストでラッパーを検証します。&lt;/p&gt;

&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;火曜日に新しいAI機能をリリースしました。金曜日の朝、CFOから「OpenAIの利用料が40%も跳ね上がったのはなぜだ」とDMが来ます。OpenAIダッシュボードを見ると、合計支出が増えていることは分かります。しかし、どの機能、どの顧客、どのルートが原因かは分かりません。&lt;/p&gt;

&lt;p&gt;これは、本番環境でLLMワークロードを運用するチームが必ず直面する問題です。OpenAIの請求インターフェースは経理向けであり、エンジニアリングやプロダクトの帰属分析向けではありません。&lt;/p&gt;

&lt;p&gt;この記事では、次を実装します。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OpenAIクライアントのラッパー&lt;/li&gt;
&lt;li&gt;コスト帰属用のイベントスキーマ&lt;/li&gt;
&lt;li&gt;トークン数からのコスト計算&lt;/li&gt;
&lt;li&gt;構造化ログ出力&lt;/li&gt;
&lt;li&gt;ウェアハウス集計SQL&lt;/li&gt;
&lt;li&gt;ApidogによるE2E検証&lt;/li&gt;
&lt;li&gt;予算上限と異常検知&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;価格計算の前提については、&lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5の価格内訳&lt;/a&gt;を参照してください。開発者ツール側の請求帰属については、&lt;a href="http://apidog.com/blog/github-copilot-usage-billing-api-teams?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;APIチーム向けのGitHub Copilot利用料請求&lt;/a&gt;も参考になります。OpenAI APIの基本は&lt;a href="https://platform.openai.com/docs/api-reference" rel="noopener noreferrer"&gt;公式のOpenAI APIリファレンス&lt;/a&gt;を確認してください。&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAIの課金ダッシュボードでは不十分な理由
&lt;/h2&gt;

&lt;p&gt;OpenAIの課金ページでは、主に次が確認できます。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;日別の支出&lt;/li&gt;
&lt;li&gt;モデル別の使用量&lt;/li&gt;
&lt;li&gt;組織レベルの使用制限&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;単一アプリ、単一機能、単一顧客なら十分です。しかし、実際のプロダクトでは複数の機能、顧客、環境、開発者が同じOpenAI組織を使います。&lt;/p&gt;

&lt;p&gt;不足する情報は次のとおりです。&lt;/p&gt;

&lt;h3&gt;
  
  
  コンテキストのない総支出
&lt;/h3&gt;

&lt;p&gt;ダッシュボードに「昨日GPT-5.5に312ドル使った」と表示されても、それがサポートチャットの大量呼び出しなのか、バックグラウンド要約ジョブの暴走なのかは分かりません。&lt;/p&gt;

&lt;h3&gt;
  
  
  機能ごとの内訳がない
&lt;/h3&gt;

&lt;p&gt;OpenAIはAPIキーやモデル単位では集計できますが、あなたのプロダクト上の&lt;code&gt;feature&lt;/code&gt;、&lt;code&gt;route&lt;/code&gt;、&lt;code&gt;customer_id&lt;/code&gt;、&lt;code&gt;environment&lt;/code&gt;では集計しません。&lt;/p&gt;

&lt;h3&gt;
  
  
  レポートに遅延がある
&lt;/h3&gt;

&lt;p&gt;使用状況データは数十分から数時間遅れて表示されます。暴走ループの検知には遅すぎます。&lt;/p&gt;

&lt;h3&gt;
  
  
  アラートが粗い
&lt;/h3&gt;

&lt;p&gt;OpenAI側の予算通知だけでは、「チャット機能が1時間に50ドルを超えたら通知する」といった制御はできません。&lt;/p&gt;

&lt;h3&gt;
  
  
  顧客帰属がない
&lt;/h3&gt;

&lt;p&gt;B2B SaaSでAI機能を提供している場合、顧客ごとのAI原価を把握しないと粗利益を計算できません。&lt;/p&gt;

&lt;h3&gt;
  
  
  プロジェクトキーだけでは粒度が足りない
&lt;/h3&gt;

&lt;p&gt;OpenAIのプロジェクトキーは有用ですが、機能、顧客、ルート単位の帰属には不十分です。&lt;a href="https://platform.openai.com/docs/api-reference/usage" rel="noopener noreferrer"&gt;OpenAI usage API&lt;/a&gt;も、基本的には集計済みデータを返すため、リクエスト単位のメタデータはアプリケーション側で持つ必要があります。&lt;/p&gt;

&lt;p&gt;この問題は多くのチームに共通しています。&lt;a href="http://Dev.to" rel="noopener noreferrer"&gt;Dev.to&lt;/a&gt;でも「OpenAIはいくら使ったかは教えてくれる。どこで使ったかは教えてくれない」という文脈で議論されています。&lt;/p&gt;

&lt;h2&gt;
  
  
  コスト帰属のデータモデル
&lt;/h2&gt;

&lt;p&gt;まず、OpenAIリクエスト1回につき1つのイベントを記録します。このイベントが分析単位です。&lt;/p&gt;

&lt;p&gt;最小スキーマは次のとおりです。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;カラム&lt;/th&gt;
&lt;th&gt;型&lt;/th&gt;
&lt;th&gt;例&lt;/th&gt;
&lt;th&gt;目的&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;request_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;uuid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7a91...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;冪等性、重複排除、リトライ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamptz&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-05-06T14:23:01Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;時系列分析、異常検知&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;support-chat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;呼び出し元のプロダクト機能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;route&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/chat/answer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTPルートまたはジョブID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cust_4291&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;顧客ごとの支出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本番、ステージング、開発の分離&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpt-5.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;モデル別価格計算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15234&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;入力トークン数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completion_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;812&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;出力トークン数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reasoning_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4500&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;推論トークン&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cached_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;キャッシュ済み入力トークン&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;latency_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2341&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;レイテンシ分析&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cost_usd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.045672&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;書き込み時に計算したコスト&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_cache_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system-v3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;キャッシュヒット率の追跡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error_code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;null&lt;/code&gt; / &lt;code&gt;429&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;エラーとリトライ分析&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;重要なのは、&lt;code&gt;cost_usd&lt;/code&gt;をクエリ時ではなく書き込み時に計算することです。価格は変更されるため、履歴イベントには「その時点のレート」で計算した値を固定して保存します。&lt;/p&gt;

&lt;h2&gt;
  
  
  コスト計算を実装する
&lt;/h2&gt;

&lt;p&gt;GPT-5.5系の価格表をコードに固定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PRICING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;# USD per 1M tokens, as of May 2026
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;180.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_cost_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PRICING&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;input_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
    &lt;span class="n"&gt;cache_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
    &lt;span class="n"&gt;output_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cache_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;推論トークンは出力として扱います。OpenAI APIでは&lt;code&gt;usage.completion_tokens_details.reasoning_tokens&lt;/code&gt;として返されますが、課金上は出力レートです。ここを間違えると、Thinking系の呼び出しコストを過小評価します。&lt;/p&gt;

&lt;p&gt;詳細な価格計算は&lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5の価格内訳&lt;/a&gt;を参照してください。&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAIクライアントをラップする
&lt;/h2&gt;

&lt;p&gt;すべてのOpenAI呼び出しを1つの関数に集約します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm.cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_attribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;openai_kwargs&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature, route, customer_id, environment are required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;openai_kwargs&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;

    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;latency_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cached_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cost_usd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_cost_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai.request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;このラッパーを、コスト帰属の唯一の入口にします。&lt;/p&gt;

&lt;p&gt;やることは明確です。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;コードベースで&lt;code&gt;OpenAI(&lt;/code&gt;を検索する&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client.chat.completions.create&lt;/code&gt;の直接呼び出しを禁止する&lt;/li&gt;
&lt;li&gt;すべて&lt;code&gt;call_with_attribution(...)&lt;/code&gt;に置き換える&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;feature&lt;/code&gt;、&lt;code&gt;route&lt;/code&gt;、&lt;code&gt;customer_id&lt;/code&gt;、&lt;code&gt;environment&lt;/code&gt;を必須にする&lt;/li&gt;
&lt;li&gt;不明な値を&lt;code&gt;unknown&lt;/code&gt;で埋めず、呼び出し時に失敗させる&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Node.jsでも構造は同じです。OpenAI SDKを関数で包み、&lt;code&gt;response.usage&lt;/code&gt;を読み取り、JSONイベントを書き込みます。Kafka、NATS、Pub/Subなどのイベントバスがある場合は、stdoutではなくそこに発行しても構いません。&lt;/p&gt;

&lt;h2&gt;
  
  
  コスト追跡を構築し、Apidogでテストする
&lt;/h2&gt;

&lt;p&gt;実装手順は6ステップです。&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 直接のOpenAI呼び出しをラッパーに置き換える
&lt;/h3&gt;

&lt;p&gt;コードベースで次を検索します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"OpenAI("&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"chat.completions.create"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;見つかった呼び出しをすべて&lt;code&gt;call_with_attribution(...)&lt;/code&gt;に置き換えます。&lt;/p&gt;

&lt;p&gt;呼び出し例：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_with_attribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/chat/answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_question&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. 構造化ログを出力する
&lt;/h3&gt;

&lt;p&gt;各イベントは1行のJSONで出力します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai.request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7a91..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support-chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/v1/chat/answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cust_4291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;812&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cached_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2341&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.045672&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;このログを既存のパイプラインでBigQuery、ClickHouse、Snowflake、Postgresなどに送ります。&lt;/p&gt;

&lt;h3&gt;
  
  
  3. ウェアハウスで機能ごとに集計する
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cache_hit_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;次のビューを作ると運用しやすくなります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;機能ごとの日次支出&lt;/li&gt;
&lt;li&gt;顧客ごとの日次支出&lt;/li&gt;
&lt;li&gt;ルート別の上位支出&lt;/li&gt;
&lt;li&gt;モデル別の支出&lt;/li&gt;
&lt;li&gt;キャッシュヒット率&lt;/li&gt;
&lt;li&gt;平均プロンプトトークン数&lt;/li&gt;
&lt;li&gt;平均出力トークン数&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. ルートごとの支出をグラフ化する
&lt;/h3&gt;

&lt;p&gt;Grafana、Metabase、Looker、Supersetなどで可視化します。&lt;/p&gt;

&lt;p&gt;最低限、次の3つは作ってください。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;機能別支出の時系列&lt;/li&gt;
&lt;li&gt;顧客別支出の時系列&lt;/li&gt;
&lt;li&gt;昨日の支出が多い上位20ルート&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;これが、OpenAIダッシュボードの代わりに毎日見る運用ダッシュボードになります。&lt;/p&gt;

&lt;h3&gt;
  
  
  5. リリース前にApidogでラッパーをテストする
&lt;/h3&gt;

&lt;p&gt;ラッパーのバグは静かにダッシュボードを壊します。特に危険なのは、ログが出ているように見えて、&lt;code&gt;customer_id&lt;/code&gt;や&lt;code&gt;feature&lt;/code&gt;が欠落している状態です。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;で次を検証します。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;既知の&lt;code&gt;customer_id&lt;/code&gt;と&lt;code&gt;feature&lt;/code&gt;を持つリクエストをAIエンドポイントに送る&lt;/li&gt;
&lt;li&gt;レスポンスを検証する&lt;/li&gt;
&lt;li&gt;stdout、OTLP、ログエンドポイントなどのサイドチャネルを確認する&lt;/li&gt;
&lt;li&gt;ログペイロードに&lt;code&gt;feature&lt;/code&gt;、&lt;code&gt;route&lt;/code&gt;、&lt;code&gt;customer_id&lt;/code&gt;が含まれることをアサートする&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cost_usd &amp;gt; 0&lt;/code&gt;と&lt;code&gt;prompt_tokens &amp;gt; 0&lt;/code&gt;をアサートする&lt;/li&gt;
&lt;li&gt;ステージングと本番で同じシナリオを実行する&lt;/li&gt;
&lt;li&gt;リトライ時にコストが二重計上されないことを確認する&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;APIテスト全般については、&lt;a href="http://apidog.com/blog/api-testing-tool-qa-engineers?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;QAエンジニア向けのAPIテストツール&lt;/a&gt;を参照してください。契約優先でAPIを設計する場合は、&lt;a href="http://apidog.com/blog/api-tool-contract-first-development?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;契約優先API開発&lt;/a&gt;も参考になります。&lt;/p&gt;

&lt;h3&gt;
  
  
  6. キーごとの予算上限とアラートを設定する
&lt;/h3&gt;

&lt;p&gt;OpenAI側では、環境や主要機能ごとにプロジェクトキーを分けます。&lt;/p&gt;

&lt;p&gt;例：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prod-support-chat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prod-summarization&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prod-agent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging-all&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;それぞれにOpenAIダッシュボードで上限を設定します。&lt;/p&gt;

&lt;p&gt;ただし、ネイティブの上限だけでは不十分です。ウェアハウス側でも異常検知します。&lt;/p&gt;

&lt;p&gt;例：10分ごとに実行する監視SQL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;hourly&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TIMESTAMP_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;baseline&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spend_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_hourly_spend&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;hourly&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;current_hour&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;current_spend&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_spend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_hourly_spend&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;current_hour&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;baseline&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_spend&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_hourly_spend&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;結果が返ったらSlack、PagerDuty、Opsgenieなどに通知します。&lt;/p&gt;

&lt;p&gt;ネイティブ上限は最後の防衛線、ウェアハウスアラートは早期検知です。&lt;/p&gt;

&lt;h2&gt;
  
  
  高度なテクニック
&lt;/h2&gt;

&lt;h3&gt;
  
  
  プロンプトキャッシングを前提にプロンプトを設計する
&lt;/h3&gt;

&lt;p&gt;GPT-5.5では、キャッシュされたトークンは入力レートの50%で課金されます。システムプロンプトを安定したプレフィックスとして配置し、リクエストごとの変数を末尾に置きます。&lt;/p&gt;

&lt;p&gt;追跡すべき指標：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cache_hit_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cache_hit_rate&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;公式の&lt;a href="https://platform.openai.com/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;OpenAIプロンプトキャッシングドキュメント&lt;/a&gt;も確認してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  オフライン処理はBatch APIに寄せる
&lt;/h3&gt;

&lt;p&gt;同期応答が不要な処理はBatch APIに回します。&lt;/p&gt;

&lt;p&gt;対象例：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;夜間要約&lt;/li&gt;
&lt;li&gt;評価実行&lt;/li&gt;
&lt;li&gt;埋め込みのバックフィル&lt;/li&gt;
&lt;li&gt;ドキュメント再処理&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Batch呼び出しにも同じコスト帰属を適用し、イベントに&lt;code&gt;batch_job_id&lt;/code&gt;を追加します。&lt;/p&gt;

&lt;h3&gt;
  
  
  推論努力をチューニングする
&lt;/h3&gt;

&lt;p&gt;GPT-5.5 Thinkingでは、&lt;code&gt;reasoning.effort&lt;/code&gt;によって推論トークンが変わります。&lt;code&gt;medium&lt;/code&gt;で動かしている機能が、&lt;code&gt;low&lt;/code&gt;でも品質基準を満たすか確認してください。&lt;/p&gt;

&lt;p&gt;やること：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;reasoning.effort&lt;/code&gt;別にA/Bテストする&lt;/li&gt;
&lt;li&gt;品質指標を比較する&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cost_usd&lt;/code&gt;を比較する&lt;/li&gt;
&lt;li&gt;品質が維持される最安設定を採用する&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;詳細は&lt;a href="http://apidog.com/blog/how-to-use-gpt-5-5-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 APIの使用方法&lt;/a&gt;を参照してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  コンテキストウィンドウを管理する
&lt;/h3&gt;

&lt;p&gt;プロンプトが長いほどコストは増えます。RAGでは、知識ベース全体を入れるのではなく、取得件数とトークン予算を明示的に制限します。&lt;/p&gt;

&lt;p&gt;監視SQL：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WEEK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_prompt_tokens&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_prompt_tokens&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;機能変更がないのに&lt;code&gt;avg_prompt_tokens&lt;/code&gt;が増えている場合、プロンプトが肥大化しています。&lt;/p&gt;

&lt;h3&gt;
  
  
  GPT-5.5の272Kトークンクリフを監視する
&lt;/h3&gt;

&lt;p&gt;GPT-5.5では、272Kトークンを超えるリクエストに対して、入力に2倍、出力に1.5倍の乗数が適用されます。&lt;/p&gt;

&lt;p&gt;ラッパーにガードを追加します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;250_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai.prompt_token_warning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;価格の詳細は&lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5の価格に関する投稿&lt;/a&gt;を参照してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  顧客ごとの支出上限を設定する
&lt;/h3&gt;

&lt;p&gt;B2B SaaSでは、顧客ごとのAI原価を制御する必要があります。&lt;/p&gt;

&lt;p&gt;実装方針：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ウェアハウスまたは高速ストアで&lt;code&gt;customer_id&lt;/code&gt;ごとの月次支出を集計&lt;/li&gt;
&lt;li&gt;各OpenAI呼び出し前に上限をチェック&lt;/li&gt;
&lt;li&gt;上限超過時は429を返す&lt;/li&gt;
&lt;li&gt;レスポンスに課金CTAを含める&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;例：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_customer_budget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;spend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_monthly_ai_spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_customer_ai_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;spend&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AIQuotaExceeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;月間AIクォータを超過しました。プランのアップグレードを検討してください。&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  避けるべきミス
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;推論トークンを入力として課金する&lt;/li&gt;
&lt;li&gt;リアルタイム監視にOpenAIダッシュボードだけを使う&lt;/li&gt;
&lt;li&gt;呼び出しサイトではなくSDKレベルで雑にタグ付けする&lt;/li&gt;
&lt;li&gt;cron、キューワーカー、Webhookのタグ付けを忘れる&lt;/li&gt;
&lt;li&gt;リクエストログをサンプリングする&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customer_id&lt;/code&gt;を&lt;code&gt;null&lt;/code&gt;のままにする&lt;/li&gt;
&lt;li&gt;リトライ時に&lt;code&gt;request_id&lt;/code&gt;を再利用せず二重計上する&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;バックグラウンドジョブには、次のような合成&lt;code&gt;route&lt;/code&gt;を付けます。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cron:nightly-summarize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;queue:image-caption&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webhook:customer-import&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;customer_id&lt;/code&gt;が存在しない内部処理では、&lt;code&gt;null&lt;/code&gt;ではなく&lt;code&gt;internal&lt;/code&gt;や&lt;code&gt;system&lt;/code&gt;を使います。&lt;/p&gt;

&lt;h2&gt;
  
  
  代替手段とツール
&lt;/h2&gt;

&lt;p&gt;自前実装以外の選択肢もあります。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;アプローチ&lt;/th&gt;
&lt;th&gt;得意な点&lt;/th&gt;
&lt;th&gt;コスト&lt;/th&gt;
&lt;th&gt;向いているケース&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI usage API&lt;/td&gt;
&lt;td&gt;ネイティブ、セットアップ不要&lt;/td&gt;
&lt;td&gt;無料&lt;/td&gt;
&lt;td&gt;1プロジェクト、1機能、顧客帰属不要&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helicone&lt;/td&gt;
&lt;td&gt;ドロップインプロキシ、ダッシュボード、キャッシュ&lt;/td&gt;
&lt;td&gt;無料枠あり、月額20ドル〜&lt;/td&gt;
&lt;td&gt;早く可視化したい、プロキシを許容できる&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Langfuse&lt;/td&gt;
&lt;td&gt;OSS、セルフホスト、トレース + コスト&lt;/td&gt;
&lt;td&gt;セルフホスト無料、クラウド月額29ドル〜&lt;/td&gt;
&lt;td&gt;トレースとコストを一体で管理したい&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangSmith&lt;/td&gt;
&lt;td&gt;LangChain統合、評価 + コスト&lt;/td&gt;
&lt;td&gt;月額39ドル/ユーザー〜&lt;/td&gt;
&lt;td&gt;LangChainをすでに使っている&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;カスタムウェアハウス&lt;/td&gt;
&lt;td&gt;完全制御、既存スタックに統合&lt;/td&gt;
&lt;td&gt;エンジニアリング時間&lt;/td&gt;
&lt;td&gt;大規模、独自ディメンション、データ所在地要件あり&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;プロキシ型のHeliconeは導入が速い一方、クリティカルパスにホップが増えます。Langfuseは制御しやすいですが、セルフホストする場合は運用が必要です。カスタムウェアハウスは実装コストがありますが、大規模チームでは最終的にこの形に寄ることが多いです。&lt;/p&gt;

&lt;p&gt;LLMコスト可観測性の実装例として、&lt;a href="https://www.helicone.ai/blog/monitor-and-optimize-llm-costs" rel="noopener noreferrer"&gt;HeliconeチームのLLMコスト追跡に関するガイド&lt;/a&gt;と&lt;a href="https://langfuse.com/docs/model-usage-and-cost" rel="noopener noreferrer"&gt;Langfuseのコスト追跡に関するドキュメント&lt;/a&gt;も参考になります。&lt;/p&gt;

&lt;p&gt;プラットフォーム規模でこのパターンを運用する場合は、&lt;a href="http://apidog.com/blog/api-platform-microservices-architecture?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;マイクロサービスアーキテクチャのためのAPIプラットフォーム&lt;/a&gt;も参照してください。&lt;/p&gt;

&lt;h2&gt;
  
  
  実世界のユースケース
&lt;/h2&gt;

&lt;h3&gt;
  
  
  顧客ごとのLLM支出を持つB2B SaaS
&lt;/h3&gt;

&lt;p&gt;あるセールスインテリジェンス製品では、顧客がブリーフィングを要求するたびにGPT-5.5呼び出しが発生します。帰属なしでは、月8万ドルのOpenAI支出しか分かりません。&lt;/p&gt;

&lt;p&gt;顧客ごとの帰属を入れると、顧客の12%が支出の71%を占めていることが分かりました。そこで段階的価格、ソフトクォータ、超過料金を導入し、AI機能の粗利益を改善できます。&lt;/p&gt;

&lt;h3&gt;
  
  
  社内開発ツールの追跡
&lt;/h3&gt;

&lt;p&gt;エンジニア向けの社内GPTアシスタントでも同じです。&lt;code&gt;customer_id&lt;/code&gt;に開発者メールを入れると、誰がどれだけ使っているかが分かります。&lt;/p&gt;

&lt;p&gt;異常な支出を見つけることで、放置された自動エージェントループを停止できます。一方、正当な高利用者にはより高いクォータを割り当てる判断もできます。&lt;/p&gt;

&lt;h3&gt;
  
  
  AI機能の支出予測
&lt;/h3&gt;

&lt;p&gt;新しい要約機能を出す前に、過去の機能別データから次を見積もります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;呼び出しあたりの平均入力トークン&lt;/li&gt;
&lt;li&gt;呼び出しあたりの平均出力トークン&lt;/li&gt;
&lt;li&gt;アクティブユーザーあたりの想定呼び出し回数&lt;/li&gt;
&lt;li&gt;想定アクティブユーザー数&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;これにより、機能単位の原価を事前に計算できます。価格設定やリリース可否の判断が推測ではなくなります。&lt;/p&gt;

&lt;h2&gt;
  
  
  結論
&lt;/h2&gt;

&lt;p&gt;測定できないものは管理できません。OpenAIの課金ダッシュボードは財務上の合計金額を示しますが、プロダクト運用には機能、顧客、ルートごとの帰属が必要です。&lt;/p&gt;

&lt;p&gt;実装すべきことはシンプルです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;すべてのリクエストに&lt;code&gt;feature&lt;/code&gt;、&lt;code&gt;route&lt;/code&gt;、&lt;code&gt;customer_id&lt;/code&gt;、&lt;code&gt;environment&lt;/code&gt;を付ける&lt;/li&gt;
&lt;li&gt;OpenAIクライアントをラッパー経由に統一する&lt;/li&gt;
&lt;li&gt;トークン数と&lt;code&gt;cost_usd&lt;/code&gt;を構造化ログで出力する&lt;/li&gt;
&lt;li&gt;ウェアハウスで集計する&lt;/li&gt;
&lt;li&gt;OpenAIプロジェクトキーごとに上限を設定する&lt;/li&gt;
&lt;li&gt;ウェアハウス側で異常検知する&lt;/li&gt;
&lt;li&gt;リリース前に&lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;でラッパーを検証する&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidogをダウンロード&lt;/a&gt;して、コスト帰属ラッパーのE2E検証に使ってください。タグ付きリクエストでAIエンドポイントを実行し、ログペイロードの形状をアサートし、複数環境でシナリオを再生できます。&lt;/p&gt;

&lt;p&gt;関連する読み物：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5の価格内訳&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://apidog.com/blog/github-copilot-usage-billing-api-teams?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;APIチーム向けのGitHub Copilot利用料請求&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  よくある質問
&lt;/h2&gt;

&lt;h3&gt;
  
  
  推論トークンは入力として課金されますか？出力として課金されますか？
&lt;/h3&gt;

&lt;p&gt;出力レートで課金されます。OpenAI APIでは&lt;code&gt;usage.completion_tokens_details.reasoning_tokens&lt;/code&gt;として返されるため、&lt;code&gt;completion_tokens&lt;/code&gt;に加算してコスト計算してください。詳細は&lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5の価格内訳&lt;/a&gt;を参照してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;response.usage&lt;/code&gt;はOpenAIダッシュボードと一致しますか？
&lt;/h3&gt;

&lt;p&gt;トークン数はダッシュボードと一致します。ただし、古い料金表でコストを計算していると、価格変更によってずれます。モデルごとのレートはコードまたは設定で固定し、価格変更日に更新してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAIのプロジェクトキーだけで帰属できますか？
&lt;/h3&gt;

&lt;p&gt;一部は可能です。プロジェクト単位の帰属や予算上限には有効です。ただし、機能、顧客、ルート単位の帰属にはアプリケーションレベルのメタデータが必要です。&lt;/p&gt;

&lt;h3&gt;
  
  
  リトライでコストが二重計上されませんか？
&lt;/h3&gt;

&lt;p&gt;モデル実行前に失敗したリクエストは通常&lt;code&gt;usage&lt;/code&gt;を返さないため、コストは記録されません。成功後にアプリケーション側でリトライすると、&lt;code&gt;request_id&lt;/code&gt;を再利用しない限り二重計上されます。冪等なリトライでは同じ&lt;code&gt;request_id&lt;/code&gt;を使い、書き込み時に重複排除してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAI usage APIはリアルタイム監視に使えますか？
&lt;/h3&gt;

&lt;p&gt;リアルタイム監視には不向きです。数十分の遅延があります。アラートやキルスイッチには自分のログとウェアハウスを使い、月次調整にはusage APIを使うのが現実的です。&lt;/p&gt;

&lt;h3&gt;
  
  
  ログ量を減らすためにサンプリングしてもよいですか？
&lt;/h3&gt;

&lt;p&gt;いいえ。リクエストごとに1行のJSONで済むため、データ量は小さいです。サンプリングすると顧客別、ルート別の正確な帰属が壊れます。すべて記録してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  他のLLMプロバイダーにも使えますか？
&lt;/h3&gt;

&lt;p&gt;使えます。&lt;code&gt;provider&lt;/code&gt;カラムを追加し、&lt;code&gt;openai&lt;/code&gt;、&lt;code&gt;anthropic&lt;/code&gt;、&lt;code&gt;google&lt;/code&gt;、&lt;code&gt;deepseek&lt;/code&gt;などを入れます。プロバイダーごとに料金表とラッパーは変わりますが、ウェアハウスのスキーマとダッシュボードは共通化できます。比較として&lt;a href="http://apidog.com/blog/deepseek-v4-api-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;DeepSeek V4 APIの価格設定&lt;/a&gt;も参照してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  埋め込みや画像生成にも使えますか？
&lt;/h3&gt;

&lt;p&gt;使えます。ただし、コスト計算はエンドポイントごとに分岐します。埋め込みは入力トークン単位、画像生成は画像枚数や解像度単位で計算します。スキーマに&lt;code&gt;endpoint&lt;/code&gt;を追加し、&lt;code&gt;chat&lt;/code&gt;、&lt;code&gt;embeddings&lt;/code&gt;、&lt;code&gt;image&lt;/code&gt;などで分けてください。&lt;/p&gt;

</description>
      <category>api</category>
      <category>monitoring</category>
      <category>openai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>OIDC The Hard way - Mirecloud Home lab Part 3</title>
      <dc:creator>Stevens Emmanuel Ledoux</dc:creator>
      <pubDate>Tue, 12 May 2026 02:36:27 +0000</pubDate>
      <link>https://dev.to/stevens_emmanuelledoux_f/oidc-the-hard-way-mirecloud-home-lab-part-3-39io</link>
      <guid>https://dev.to/stevens_emmanuelledoux_f/oidc-the-hard-way-mirecloud-home-lab-part-3-39io</guid>
      <description>&lt;p&gt;Eliminating password databases: OpenID Connect, front-channel vs. back-channel, role mapping, and the end of local authentication.&lt;br&gt;
Overview&lt;/p&gt;

&lt;p&gt;Parts 1 and 2 built the foundation: Vault manages all credentials, External Secrets Operator bridges them into Kubernetes, cert-manager automates TLS, and Keycloak runs as a production-grade identity provider with clustered session state.&lt;br&gt;
Part 3 is where that infrastructure proves its value: integrating Grafana with Keycloak via OpenID Connect to eliminate Grafana's native login form entirely. By the end, there is no Grafana password database. No local admin account. Every login redirects to Keycloak, authenticates against the central identity layer, and maps realm roles to Grafana permissions automatically.&lt;br&gt;
The deliverables:&lt;br&gt;
Understanding the OIDC Authorization Code Flow&lt;br&gt;
Configuring Keycloak as an Identity Provider (IdP)&lt;br&gt;
Configuring Grafana as a Relying Party (RP)&lt;br&gt;
Managing the client secret through Vault and ESO&lt;br&gt;
Front-channel vs. back-channel URL configuration (the detail most guides get wrong)&lt;br&gt;
Role mapping via JMESPath expressions&lt;/p&gt;

&lt;p&gt;A Primer on OpenID Connect&lt;/p&gt;

&lt;p&gt;Before diving into YAML, it is worth understanding what OpenID Connect actually does - because every configuration decision that follows is a direct consequence of how the protocol works.&lt;br&gt;
The Problem It Solves&lt;/p&gt;

&lt;p&gt;Without SSO, every service in your cluster has its own user database, its own password policy, its own session management. Add a user, you add them five times. Rotate a password, you rotate it five times. An employee leaves, you hope you remembered to revoke access in all five places.&lt;br&gt;
OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0. It defines a standard protocol by which an application (the Relying Party, e.g., Grafana) can delegate authentication to a trusted external service (the Identity Provider, e.g., Keycloak). The application never handles passwords. It only receives a verified identity token.&lt;br&gt;
The Authorization Code Flow&lt;/p&gt;

&lt;p&gt;This is the flow used by Grafana when a user attempts to log in:&lt;br&gt;
Step-by-step breakdown:&lt;br&gt;
User navigates to Grafana → GET /&lt;br&gt;
Grafana redirects to Keycloak → 302 with auth_url&lt;br&gt;
Browser follows redirect to Keycloak → GET /auth/realms/mirecloud/protocol/openid-connect/auth&lt;br&gt;
Keycloak renders login form → User sees username/password fields&lt;br&gt;
User submits credentials → POST to Keycloak (Grafana never sees this)&lt;br&gt;
Keycloak redirects back to Grafana → 302 with code=AUTH_CODE&lt;br&gt;
Browser follows redirect to Grafana callback → GET /login/generic_oauth?code=...&lt;/p&gt;

&lt;p&gt;From here, the flow switches to back-channel (server-to-server, no browser involved):&lt;br&gt;
Grafana exchanges code for tokens (back-channel) → POST /token&lt;br&gt;
Keycloak returns tokens → { access_token, id_token }&lt;br&gt;
Grafana requests user info (back-channel) → GET /userinfo&lt;br&gt;
Keycloak returns user claims → { sub, email, realm_access.roles }&lt;br&gt;
Grafana creates session → Sets grafana_session cookie&lt;/p&gt;

&lt;p&gt;Front-Channel vs. Back-Channel&lt;/p&gt;

&lt;p&gt;The diagram reveals a critical distinction that most tutorials ignore:&lt;br&gt;
Front-channel calls travel through the user's browser as HTTP redirects. The auth_url is a front-channel URL - the browser navigates to it directly. It must be publicly reachable: &lt;a href="https://keycloak.mirecloud.com/" rel="noopener noreferrer"&gt;https://keycloak.mirecloud.com/&lt;/a&gt;...&lt;br&gt;
Back-channel calls are made directly between Grafana's pod and Keycloak's pod, inside the Kubernetes cluster. The browser is not involved. These are the token exchange ( token_url) and user info ( api_url) calls.&lt;br&gt;
This is why token_url in the Grafana configuration uses the internal Kubernetes service DNS name ( keycloak-keycloakx-http.keycloak.svc.cluster.local) rather than the public hostname.&lt;br&gt;
Key Concepts&lt;/p&gt;

&lt;p&gt;Step 1 - Configure Keycloak (One-Time Setup)&lt;/p&gt;

&lt;p&gt;Create a Realm&lt;/p&gt;

&lt;p&gt;Navigate to the Keycloak admin console → Create Realm.&lt;br&gt;
Create a Client for Grafana&lt;/p&gt;

&lt;p&gt;Inside the mirecloud realm, navigate to Clients → Create Client.&lt;br&gt;
General Settings: Capability config:&lt;br&gt;
Client authentication: ON&lt;br&gt;
Authentication flow: Enable "Standard flow"&lt;/p&gt;

&lt;p&gt;Login settings:&lt;br&gt;
Retrieve the Client Secret&lt;/p&gt;

&lt;p&gt;Navigate to Clients → grafana → Credentials tab. Copy the Client Secret and store it in Vault:&lt;br&gt;
kubectl -n vault exec -ti vault-0 -- vault kv put secret/grafana/sso \ client_secret=''&lt;/p&gt;

&lt;p&gt;Step 2 - ExternalSecret for Grafana&lt;/p&gt;

&lt;p&gt;apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: grafana-keycloak-es namespace: monitoring spec: refreshInterval: 1m secretStoreRef: name: vault-backend kind: ClusterSecretStore target: name: grafana-keycloak-secret data: - secretKey: client_secret remoteRef: key: secret/grafana/sso property: client_secret&lt;/p&gt;

&lt;p&gt;Step 3 - Grafana OIDC Configuration&lt;/p&gt;

&lt;p&gt;kube-prometheus-stack: grafana: enabled: true envFromSecret: grafana-keycloak-secret grafana.ini: auth.generic_oauth: enabled: true name: "Keycloak" client_id: "grafana" client_secret: $__env{client_secret} # Front-channel URL (browser navigates here) auth_url: "&lt;a href="https://keycloak.mirecloud.com/auth/realms/mirecloud/protocol/openid-connect/auth" rel="noopener noreferrer"&gt;https://keycloak.mirecloud.com/auth/realms/mirecloud/protocol/openid-connect/auth&lt;/a&gt;" # Back-channel URLs (pod-to-pod) token_url: "&lt;a href="http://keycloak-keycloakx-http.keycloak.svc.cluster.local:80/auth/realms/mirecloud/protocol/openid-connect/token" rel="noopener noreferrer"&gt;http://keycloak-keycloakx-http.keycloak.svc.cluster.local:80/auth/realms/mirecloud/protocol/openid-connect/token&lt;/a&gt;" api_url: "&lt;a href="http://keycloak-keycloakx-http.keycloak.svc.cluster.local:80/auth/realms/mirecloud/protocol/openid-connect/userinfo" rel="noopener noreferrer"&gt;http://keycloak-keycloakx-http.keycloak.svc.cluster.local:80/auth/realms/mirecloud/protocol/openid-connect/userinfo&lt;/a&gt;" scopes: "openid profile email" allow_sign_up: true # Role mapping role_attribute_path: "contains(realm_access.roles[*], 'admin') &amp;amp;&amp;amp; 'Admin' || 'Viewer'"&lt;/p&gt;

&lt;p&gt;Configuration Breakdown&lt;/p&gt;

&lt;p&gt;uses the public DNS name: &lt;a href="https://keycloak.mirecloud.com/" rel="noopener noreferrer"&gt;https://keycloak.mirecloud.com/&lt;/a&gt;...&lt;br&gt;
token_url and api_url use internal cluster DNS to avoid DNS hairpin issues in homelab environments.&lt;br&gt;
Test the OIDC Flow&lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="https://grafana.mirecloud.com" rel="noopener noreferrer"&gt;https://grafana.mirecloud.com&lt;/a&gt;.&lt;br&gt;
Click Sign in with Keycloak.&lt;br&gt;
The browser redirects to Keycloak. Enter credentials for a user in the mirecloud realm.&lt;br&gt;
Grafana exchanges the authorization code for tokens (back-channel, invisible to you) and creates a session.&lt;br&gt;
You land on the Grafana dashboard. Your role (Admin or Viewer) is determined by the admin realm role assignment.&lt;br&gt;
Security Posture&lt;/p&gt;

&lt;p&gt;No Grafana password database - all authentication delegated to Keycloak&lt;br&gt;
Client secret managed through Vault and ESO - never visible in Git&lt;br&gt;
OIDC tokens transmitted securely (TLS on front-channel, internal service mesh for back-channel)&lt;br&gt;
Role assignment driven by Keycloak realm roles - access control changes do not require Grafana restarts&lt;/p&gt;

&lt;p&gt;What's Next: Part 4&lt;/p&gt;

&lt;p&gt;Part 4 will cover GitLab OIDC configuration with discovery: false, explicit OAuth endpoint definition, and CA injection.&lt;br&gt;
The complete repository is available at github.com/mirecloud/home_lab.&lt;br&gt;
Emmanuel Catin - Senior Platform Engineer | Kubernetes, GitOps, Zero Trust&lt;br&gt;
 CKA (90%) | CKS in preparation | Montréal, QC&lt;/p&gt;

&lt;h1&gt;
  
  
  Kubernetes #OIDC #Keycloak #Grafana #SSO #OpenIDConnect #GitOps #Vault #ExternalSecrets #DevSecOps #HomeLab #PlatformEngineering #ZeroTrust
&lt;/h1&gt;




&lt;p&gt;Originally published at &lt;a href="https://emmanuel-steven.blogspot.com" rel="noopener noreferrer"&gt;https://emmanuel-steven.blogspot.com&lt;/a&gt; on February 17, 2026.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Track OpenAI API spend per feature: a cost-attribution playbook</title>
      <dc:creator>Hassann</dc:creator>
      <pubDate>Tue, 12 May 2026 02:35:48 +0000</pubDate>
      <link>https://dev.to/hassann/how-to-track-openai-api-spend-per-feature-a-cost-attribution-playbook-2dm7</link>
      <guid>https://dev.to/hassann/how-to-track-openai-api-spend-per-feature-a-cost-attribution-playbook-2dm7</guid>
      <description>&lt;p&gt;Your OpenAI invoice says you spent $4,237 last month. It does not tell you that $3,100 came from one runaway summarization endpoint, $700 came from a customer paying $50/month, and $437 came from a feature nobody uses. If you want pricing, capacity, or roadmap decisions to be grounded in data, you need request-level cost attribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidog.com/?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation" class="crayons-btn crayons-btn--primary"&gt;Try Apidog today&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;This guide shows how to implement OpenAI API cost attribution in production: tag every request, log token usage and computed cost, aggregate spend by feature/route/customer, set budget caps, and test the wrapper before shipping.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Apidog gives you the request-level visibility and scenario testing you need to verify your cost-tracking wrapper works before it ships to production. Use Apidog to replay tagged requests, assert log shape, and validate that every call carries the metadata your warehouse expects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Implement this pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrap every OpenAI API call.&lt;/li&gt;
&lt;li&gt;Require metadata: &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;customer_id&lt;/code&gt;, and &lt;code&gt;environment&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Capture &lt;code&gt;response.usage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compute &lt;code&gt;cost_usd&lt;/code&gt; at write time.&lt;/li&gt;
&lt;li&gt;Emit one structured log event per request.&lt;/li&gt;
&lt;li&gt;Aggregate by tag in your warehouse.&lt;/li&gt;
&lt;li&gt;Set OpenAI project/key budget caps.&lt;/li&gt;
&lt;li&gt;Alert on hourly spend anomalies.&lt;/li&gt;
&lt;li&gt;Validate the wrapper with &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt; scenario tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You ship a new AI feature on Tuesday. By Friday, your CFO asks why the OpenAI line item jumped 40%. The OpenAI dashboard shows total spend and model usage, but not which feature, customer, or endpoint caused the spike.&lt;/p&gt;

&lt;p&gt;That is the core problem: OpenAI billing is useful for invoices, not engineering attribution.&lt;/p&gt;

&lt;p&gt;The fix is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add metadata at the call site.&lt;/li&gt;
&lt;li&gt;Log every request as structured data.&lt;/li&gt;
&lt;li&gt;Compute cost from token usage.&lt;/li&gt;
&lt;li&gt;Store the event in your warehouse.&lt;/li&gt;
&lt;li&gt;Build dashboards and alerts from that table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this guide, you will have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cost-attribution event schema&lt;/li&gt;
&lt;li&gt;Python wrapper code&lt;/li&gt;
&lt;li&gt;SQL aggregation queries&lt;/li&gt;
&lt;li&gt;A verification workflow with Apidog&lt;/li&gt;
&lt;li&gt;A build-vs-buy tooling comparison&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For pricing context, see the &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 pricing breakdown&lt;/a&gt;. For a related billing-attribution problem, see &lt;a href="http://apidog.com/blog/github-copilot-usage-billing-api-teams?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GitHub Copilot usage billing for API teams&lt;/a&gt;. For API basics, see the &lt;a href="https://platform.openai.com/docs/api-reference" rel="noopener noreferrer"&gt;official OpenAI API reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why OpenAI’s billing dashboard is not enough
&lt;/h2&gt;

&lt;p&gt;The OpenAI billing dashboard typically gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily spend&lt;/li&gt;
&lt;li&gt;Model breakdown&lt;/li&gt;
&lt;li&gt;Usage limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That works for a simple setup. It breaks down when you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple AI features&lt;/li&gt;
&lt;li&gt;Multiple customers&lt;/li&gt;
&lt;li&gt;Multiple environments&lt;/li&gt;
&lt;li&gt;Multiple developers&lt;/li&gt;
&lt;li&gt;Background jobs&lt;/li&gt;
&lt;li&gt;Internal tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is missing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Total spend without context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The dashboard can tell you that you spent $312 yesterday. It cannot tell you whether that came from a customer hammering your support-chat endpoint or from a background job reprocessing your knowledge base.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No per-feature breakdown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenAI usage is grouped around account/project/model dimensions. It does not know your product concepts: &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;customer_id&lt;/code&gt;, or &lt;code&gt;environment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reporting lag&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usage data may lag by tens of minutes or hours. That is too slow for runaway loops or hourly burn alerts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No feature-level alerts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is no native primitive for: “Page me if &lt;code&gt;/api/v1/chat/answer&lt;/code&gt; exceeds $50/hour.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No customer attribution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you run B2B SaaS, you need to know which customer generated which spend. Without that, you cannot compute gross margin per customer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project keys help, but only partially&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenAI project keys can separate workloads at a coarse level. They do not give you per-feature, per-route, or per-customer attribution. The &lt;a href="https://platform.openai.com/docs/api-reference/usage" rel="noopener noreferrer"&gt;OpenAI usage API&lt;/a&gt; returns aggregated data, not request-level product metadata.&lt;/p&gt;

&lt;p&gt;The pattern is common enough that the &lt;a href="http://Dev.to" rel="noopener noreferrer"&gt;Dev.to&lt;/a&gt; thread “OpenAI Tells You What You Spent. Not Where. So I Built a Dashboard” resonated with developers: you cannot manage what you cannot measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost-attribution data model
&lt;/h2&gt;

&lt;p&gt;Treat every OpenAI request as a cost event. That event is the unit you query, alert on, and reconcile.&lt;/p&gt;

&lt;p&gt;Use a schema like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;request_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;uuid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7a91...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Idempotency, deduplication, retries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamptz&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-05-06T14:23:01Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Time-series queries and anomaly detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;support-chat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Product surface that triggered the call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;route&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/chat/answer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP route or background job ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cust_4291&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-customer spend and gross margin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;prod&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, &lt;code&gt;dev&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Separate production from internal usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gpt-5.5&lt;/code&gt;, &lt;code&gt;gpt-5.4-mini&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pricing differs per model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15234&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Input token count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completion_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;812&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Output token count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reasoning_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4500&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reasoning tokens billed as output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cached_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cached input tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;latency_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2341&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cost/performance correlation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cost_usd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;numeric(10,6)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.045672&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cost computed at write time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_cache_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system-v3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cache hit tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error_code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;null&lt;/code&gt;, &lt;code&gt;429&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Retry and failure analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compute cost when you write the event, not later in a dashboard query. Pricing changes over time, so historical events should preserve the rate used at the time.&lt;/p&gt;

&lt;p&gt;Example pricing function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PRICING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;# USD per 1M tokens, as of May 2026
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;180.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_cost_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PRICING&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;input_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
    &lt;span class="n"&gt;cache_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
    &lt;span class="n"&gt;output_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cache_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reasoning tokens are returned under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;usage.completion_tokens_details.reasoning_tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are billed at the output rate. If you omit them, you undercount cost for reasoning-heavy calls.&lt;/p&gt;

&lt;p&gt;For more pricing details, see the &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 pricing breakdown&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap the OpenAI client
&lt;/h2&gt;

&lt;p&gt;Every OpenAI call should go through one wrapper. The wrapper should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Require product metadata.&lt;/li&gt;
&lt;li&gt;Generate or receive a &lt;code&gt;request_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Call OpenAI.&lt;/li&gt;
&lt;li&gt;Capture token usage.&lt;/li&gt;
&lt;li&gt;Compute cost.&lt;/li&gt;
&lt;li&gt;Emit a structured event.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm.cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_attribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;openai_kwargs&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature, route, customer_id, and environment are required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;openai_kwargs&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;

    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;latency_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="n"&gt;cached_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="n"&gt;cost_usd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_cost_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai.request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_with_attribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/chat/answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cust_4291&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a support assistant.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How do I reset my password?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ship these logs to your existing pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vector&lt;/li&gt;
&lt;li&gt;Fluent Bit&lt;/li&gt;
&lt;li&gt;Logstash&lt;/li&gt;
&lt;li&gt;OTLP collector&lt;/li&gt;
&lt;li&gt;Kafka&lt;/li&gt;
&lt;li&gt;Pub/Sub&lt;/li&gt;
&lt;li&gt;NATS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then write them into your warehouse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BigQuery&lt;/li&gt;
&lt;li&gt;ClickHouse&lt;/li&gt;
&lt;li&gt;Snowflake&lt;/li&gt;
&lt;li&gt;Postgres&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Node.js, use the same shape: a wrapper function around the OpenAI SDK that accepts metadata, captures &lt;code&gt;response.usage&lt;/code&gt;, computes cost, and writes a JSON event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wire up cost tracking and test it with Apidog
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Replace direct OpenAI calls
&lt;/h3&gt;

&lt;p&gt;Search your codebase for direct SDK calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"client.chat.completions.create"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"OpenAI("&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace every direct call with your attribution wrapper.&lt;/p&gt;

&lt;p&gt;Do not default missing metadata to &lt;code&gt;"unknown"&lt;/code&gt;. Fail fast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature is required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bad tags create silent attribution errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Emit structured logs
&lt;/h3&gt;

&lt;p&gt;Log one JSON event per request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai.request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7a91..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support-chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/v1/chat/answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cust_4291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;812&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cached_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2341&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.045672&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep these events clean. Do not mix them with debug logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Aggregate spend in SQL
&lt;/h3&gt;

&lt;p&gt;Once events are in your warehouse, start with feature-level spend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cache_hit_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add customer-level spend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MONTH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And route-level spend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_cost_per_request&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Build the dashboard
&lt;/h3&gt;

&lt;p&gt;Create three operational views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spend per feature over time&lt;/li&gt;
&lt;li&gt;Spend per customer over time&lt;/li&gt;
&lt;li&gt;Top routes by daily spend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use whatever BI layer you already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;li&gt;Metabase&lt;/li&gt;
&lt;li&gt;Looker&lt;/li&gt;
&lt;li&gt;Superset&lt;/li&gt;
&lt;li&gt;Mode&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Test the wrapper with Apidog
&lt;/h3&gt;

&lt;p&gt;Before shipping, verify that the wrapper logs the metadata you expect.&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt; to create an end-to-end scenario:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send a request to your AI endpoint with a known &lt;code&gt;customer_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verify the API response succeeds.&lt;/li&gt;
&lt;li&gt;Capture the side-channel log event through your logging endpoint, stdout collector, or OTLP/log pipeline.&lt;/li&gt;
&lt;li&gt;Assert the event contains:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;route&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prompt_tokens &amp;gt; 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cost_usd &amp;gt; 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Run the same scenario against staging and production using Apidog environments.&lt;/li&gt;
&lt;li&gt;Replay the request and verify retries do not double-count cost.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For broader testing workflows, see &lt;a href="http://apidog.com/blog/api-testing-tool-qa-engineers?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API testing tools for QA engineers&lt;/a&gt;. For contract-first coverage, see &lt;a href="http://apidog.com/blog/api-tool-contract-first-development?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;contract-first API development&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Set budget caps and alerts
&lt;/h3&gt;

&lt;p&gt;Use OpenAI project keys to isolate risk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prod-support-chat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prod-summarization&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging-all&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dev-all&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set hard caps in the OpenAI dashboard so one runaway workload cannot drain the whole organization budget.&lt;/p&gt;

&lt;p&gt;Then add warehouse-driven alerts. Example: page if any feature exceeds 3x its seven-day average hourly spend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;hourly&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TIMESTAMP_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spend_usd&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;baseline&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spend_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_hourly_spend&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;hourly&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TIMESTAMP_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;current_hour&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;spend_usd&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;hourly&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spend_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_hourly_spend&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;current_hour&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;baseline&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spend_usd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_hourly_spend&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send the result to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PagerDuty&lt;/li&gt;
&lt;li&gt;Opsgenie&lt;/li&gt;
&lt;li&gt;Slack&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Incident.io&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Native caps protect you from catastrophic burn. Warehouse alerts catch slow drift earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prompt caching
&lt;/h3&gt;

&lt;p&gt;GPT-5.5 charges less for cached input tokens. Structure prompts so stable content appears first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Stable system prompt]
[Stable policy/instructions]
[Stable examples]
[Per-request user data]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Track this per feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cache_hit_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cache_hit_rate&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a prompt change drops cache hit rate, your input cost can rise silently.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://platform.openai.com/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;official OpenAI prompt caching docs&lt;/a&gt; for eligibility rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch API for offline workloads
&lt;/h3&gt;

&lt;p&gt;Use the Batch API for workloads that do not need synchronous responses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nightly summarization&lt;/li&gt;
&lt;li&gt;Evaluation runs&lt;/li&gt;
&lt;li&gt;Embedding backfills&lt;/li&gt;
&lt;li&gt;Document re-processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tag these events with a &lt;code&gt;batch_job_id&lt;/code&gt; so you can attribute cost back to the source workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reasoning effort tuning
&lt;/h3&gt;

&lt;p&gt;Reasoning-heavy calls can multiply output tokens. Audit features that use higher reasoning effort:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can &lt;code&gt;medium&lt;/code&gt; become &lt;code&gt;low&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does quality remain acceptable?&lt;/li&gt;
&lt;li&gt;What is the cost delta?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Track cost and quality side by side before changing production defaults.&lt;/p&gt;

&lt;p&gt;For more details, see &lt;a href="http://apidog.com/blog/how-to-use-gpt-5-5-api?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;how to use the GPT-5.5 API&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context-window discipline
&lt;/h3&gt;

&lt;p&gt;Long prompts are expensive. Prefer tight retrieval over stuffing large context windows.&lt;/p&gt;

&lt;p&gt;Track prompt size by feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;APPROX_QUANTILES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="k"&gt;OFFSET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p95_prompt_tokens&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;openai_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'prod'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p95_prompt_tokens&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If prompt size grows without a product reason, investigate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watch the 272K-token cliff
&lt;/h3&gt;

&lt;p&gt;OpenAI applies higher pricing on GPT-5.5 requests above 272K tokens. Add a guardrail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;250_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai.prompt_size_warning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For pricing details, see the &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 pricing post&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-customer spend caps
&lt;/h3&gt;

&lt;p&gt;For B2B SaaS, enforce spend limits before making the OpenAI call.&lt;/p&gt;

&lt;p&gt;Example flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query current monthly spend for &lt;code&gt;customer_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compare it to the customer’s quota.&lt;/li&gt;
&lt;li&gt;If under quota, call OpenAI.&lt;/li&gt;
&lt;li&gt;If over quota, return &lt;code&gt;429&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"monthly_ai_quota_exceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your monthly AI quota has been exceeded. Upgrade your plan or contact billing."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns AI from a margin risk into a controllable product cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;p&gt;Avoid these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Counting reasoning tokens as input. They are output.&lt;/li&gt;
&lt;li&gt;Trusting the OpenAI dashboard for real-time alerts.&lt;/li&gt;
&lt;li&gt;Adding tags globally instead of at the call site.&lt;/li&gt;
&lt;li&gt;Forgetting background jobs and queue workers.&lt;/li&gt;
&lt;li&gt;Sampling logs. Log every request.&lt;/li&gt;
&lt;li&gt;Allowing &lt;code&gt;customer_id&lt;/code&gt; to be null.&lt;/li&gt;
&lt;li&gt;Computing historical cost with today’s pricing.&lt;/li&gt;
&lt;li&gt;Retrying successful requests with a new &lt;code&gt;request_id&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For background jobs, use synthetic routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cron:nightly-summarize
queue:image-caption
webhook:crm-sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For unknown internal usage, use explicit values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id = "internal"
customer_id = "system"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never use &lt;code&gt;null&lt;/code&gt; as an attribution bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives and tooling
&lt;/h2&gt;

&lt;p&gt;You do not have to build all of this yourself.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it does well&lt;/th&gt;
&lt;th&gt;What it costs&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI usage API&lt;/td&gt;
&lt;td&gt;Native, no setup, accurate to the cent&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;One project, one feature, no per-customer attribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helicone&lt;/td&gt;
&lt;td&gt;Drop-in proxy, dashboards, caching, per-user costs&lt;/td&gt;
&lt;td&gt;Free tier; paid from $20/mo&lt;/td&gt;
&lt;td&gt;You want a hosted dashboard quickly and accept a proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Langfuse&lt;/td&gt;
&lt;td&gt;Open source, self-host or cloud, traces plus cost&lt;/td&gt;
&lt;td&gt;Free self-hosted; cloud from $29/mo&lt;/td&gt;
&lt;td&gt;You want traces and cost in one tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangSmith&lt;/td&gt;
&lt;td&gt;LangChain integration, evals, cost tracking&lt;/td&gt;
&lt;td&gt;Paid from $39/user/mo&lt;/td&gt;
&lt;td&gt;You already use LangChain heavily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom warehouse&lt;/td&gt;
&lt;td&gt;Full control, no proxy, custom dimensions&lt;/td&gt;
&lt;td&gt;Engineering time&lt;/td&gt;
&lt;td&gt;Large workloads, strict residency, custom attribution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tradeoffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A proxy adds another hop in the critical path.&lt;/li&gt;
&lt;li&gt;A self-hosted observability stack gives control but adds ops work.&lt;/li&gt;
&lt;li&gt;A custom warehouse integrates well with your data stack but requires you to own queries and alerts.&lt;/li&gt;
&lt;li&gt;The native usage API is useful for reconciliation, not product-level attribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more on hosted LLM cost monitoring, see &lt;a href="https://www.helicone.ai/blog/monitor-and-optimize-llm-costs" rel="noopener noreferrer"&gt;Helicone’s guide on tracking LLM costs&lt;/a&gt;. For open-source cost tracking, see the &lt;a href="https://langfuse.com/docs/model-usage-and-cost" rel="noopener noreferrer"&gt;Langfuse cost tracking docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you operate at platform scale, these patterns also fit service-mesh and platform-engineering workflows. See &lt;a href="http://apidog.com/blog/api-platform-microservices-architecture?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;API platforms for microservices architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world use cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  B2B SaaS with per-customer LLM spend
&lt;/h3&gt;

&lt;p&gt;A sales-intelligence product spends $80,000/month on OpenAI. After adding per-customer attribution, the team learns that 12% of customers drive 71% of AI spend.&lt;/p&gt;

&lt;p&gt;The company can then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add tiered pricing&lt;/li&gt;
&lt;li&gt;Apply soft quotas to lower tiers&lt;/li&gt;
&lt;li&gt;Charge overages&lt;/li&gt;
&lt;li&gt;Improve gross margin per account&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Internal developer tooling
&lt;/h3&gt;

&lt;p&gt;An engineering org gives developers access to an internal GPT-5.5 assistant. By tagging requests with developer identity, platform engineering sees that three developers account for 50% of internal spend.&lt;/p&gt;

&lt;p&gt;Two are running abandoned agent loops. Turning them off saves $1,800/month. The third is doing legitimate high-value work, so the team increases their quota.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI feature forecasting
&lt;/h3&gt;

&lt;p&gt;A product team wants to ship summarization. Historical events give them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average input tokens per call&lt;/li&gt;
&lt;li&gt;Average output tokens per call&lt;/li&gt;
&lt;li&gt;Calls per active user&lt;/li&gt;
&lt;li&gt;Active user forecast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They estimate cost at $0.04 per active user per day, or about $1.20/month. Pricing can then set a $5/month feature price with visible unit economics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;OpenAI’s billing dashboard answers an accounting question. Request-level attribution answers the engineering and product question: where is the money going?&lt;/p&gt;

&lt;p&gt;Implementation checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tag every request with &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;customer_id&lt;/code&gt;, and &lt;code&gt;environment&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compute cost at write time.&lt;/li&gt;
&lt;li&gt;Log every request as structured data.&lt;/li&gt;
&lt;li&gt;Store events in your warehouse.&lt;/li&gt;
&lt;li&gt;Build feature, route, and customer dashboards.&lt;/li&gt;
&lt;li&gt;Set OpenAI project/key caps.&lt;/li&gt;
&lt;li&gt;Add warehouse-driven anomaly alerts.&lt;/li&gt;
&lt;li&gt;Test the wrapper with &lt;a href="https://apidog.com?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Apidog&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Audit reasoning effort, prompt size, and cache hit rate regularly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://apidog.com/download?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;Download Apidog&lt;/a&gt; and use it to verify your cost-attribution wrapper end to end. Drive AI endpoints with tagged requests, assert the log payload shape, and replay scenarios across environments before your warehouse depends on the data.&lt;/p&gt;

&lt;p&gt;For related cost-management reading, see the &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 pricing breakdown&lt;/a&gt; and &lt;a href="http://apidog.com/blog/github-copilot-usage-billing-api-teams?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GitHub Copilot usage billing for API teams&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do reasoning tokens count as input or output for billing?
&lt;/h3&gt;

&lt;p&gt;Reasoning tokens are billed at the output rate. The OpenAI API returns them under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;usage.completion_tokens_details.reasoning_tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add them to &lt;code&gt;completion_tokens&lt;/code&gt; when computing cost. For per-effort pricing details, see the &lt;a href="http://apidog.com/blog/gpt-5-5-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;GPT-5.5 pricing breakdown&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How accurate is &lt;code&gt;response.usage&lt;/code&gt; compared to the OpenAI dashboard?
&lt;/h3&gt;

&lt;p&gt;Token counts in &lt;code&gt;response.usage&lt;/code&gt; should match dashboard usage. Cost drift usually comes from stale pricing tables. Pin your rate table per model and update it when OpenAI changes pricing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I do attribution with OpenAI project keys alone?
&lt;/h3&gt;

&lt;p&gt;Only partially. Project keys give you one dimension of attribution. They do not give you per-feature, per-customer, or per-route visibility. Use project keys for isolation and budget caps; use application metadata for product attribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about retries and rate-limit errors?
&lt;/h3&gt;

&lt;p&gt;If a request fails before the model runs, there is no &lt;code&gt;usage&lt;/code&gt; object and no cost to log.&lt;/p&gt;

&lt;p&gt;If a request succeeds and your app retries it, you can double-count unless you reuse the same &lt;code&gt;request_id&lt;/code&gt; and dedupe on write.&lt;/p&gt;

&lt;h3&gt;
  
  
  How fast does the OpenAI usage API return data?
&lt;/h3&gt;

&lt;p&gt;The usage API can lag by tens of minutes. Use it for reconciliation. Use your own event stream and warehouse for alerts and kill switches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I sample requests?
&lt;/h3&gt;

&lt;p&gt;No. One JSON line per request is small, and sampling breaks customer and route attribution. Log every request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can this work for other LLM providers?
&lt;/h3&gt;

&lt;p&gt;Yes. Add a &lt;code&gt;provider&lt;/code&gt; column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openai
anthropic
google
deepseek
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then maintain provider-specific pricing logic. The warehouse schema and dashboards can stay mostly the same.&lt;/p&gt;

&lt;p&gt;For a comparison point, see &lt;a href="http://apidog.com/blog/deepseek-v4-api-pricing?utm_source=dev.to&amp;amp;utm_medium=wanda&amp;amp;utm_content=n8n-post-automation"&gt;DeepSeek V4 API pricing&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this work for embeddings and image generation?
&lt;/h3&gt;

&lt;p&gt;Yes, but the cost math changes.&lt;/p&gt;

&lt;p&gt;Add an &lt;code&gt;endpoint&lt;/code&gt; column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chat
embeddings
image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then branch cost computation by endpoint. Embeddings are usually billed per input token. Images are usually billed per image or resolution.&lt;/p&gt;

</description>
      <category>api</category>
      <category>monitoring</category>
      <category>openai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Ship an app on Ghost + Fly.io for $2/month</title>
      <dc:creator>ghost</dc:creator>
      <pubDate>Tue, 12 May 2026 02:34:43 +0000</pubDate>
      <link>https://dev.to/ghostbuild/ship-an-app-on-ghost-flyio-for-2month-4f9</link>
      <guid>https://dev.to/ghostbuild/ship-an-app-on-ghost-flyio-for-2month-4f9</guid>
      <description>&lt;p&gt;Putting a real public app on the internet shouldn't cost $25/month for managed Postgres alone — before you've added compute or shipped a feature. Ghost gives you the database, Fly.io gives you the host, and your AI agent does the plumbing.&lt;/p&gt;

&lt;p&gt;You can launch a public-facing, sparse-traffic hobby app, backed by Postgres, for roughly the cost of a coffee per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;This guide is for developers who use an AI coding agent (Claude Code, Cursor, Codex, Windsurf, etc.) and want to ship a small public app fast and cheap. You don't need to know SQL or Docker — the agent handles both — but you should be comfortable approving shell commands the agent runs on your behalf.&lt;/p&gt;

&lt;p&gt;You'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AI coding agent &lt;strong&gt;with both MCP support and a Bash/shell tool&lt;/strong&gt; (Claude Code, Cursor in agent mode, Codex, Windsurf, Gemini CLI, VS Code, Kiro, or Antigravity). The shell tool is what lets the agent run &lt;code&gt;flyctl&lt;/code&gt; and &lt;code&gt;npm&lt;/code&gt; on your behalf — most modern agents have this.&lt;/li&gt;
&lt;li&gt;macOS, Linux, or Windows (WSL recommended on Windows for &lt;code&gt;flyctl&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A Fly.io account with a credit card on file. &lt;/li&gt;
&lt;li&gt;An internet connection. The agent will install everything else (&lt;code&gt;flyctl&lt;/code&gt;, Node, etc.) on its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Ghost
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ghost is Postgres for builders and their agents.&lt;/strong&gt; Unlimited databases, metered by hours of active compute. All via CLI and MCP, no GUI required.&lt;/p&gt;

&lt;p&gt;Create one in seconds, fork it like git when you want to experiment safely, share it with a simple link like a Google doc. Graduate to production with one command or throw it away when you're done.&lt;/p&gt;

&lt;p&gt;The free tier covers 100 active compute hours per month and 1TB of storage. Compute is metered in 15-minute chunks when something queries the database; an idle database burns no compute. A sparse-traffic hobby app — a handful of human visits a day — comfortably fits the free tier.&lt;/p&gt;

&lt;p&gt;You can do this with managed-Postgres alternatives like Neon, Supabase, or RDS — but those either charge a flat monthly fee, cap project counts, or push you through a GUI for changes the agent could otherwise make in seconds. Ghost is the cheapest, most agent-native way to ship a public app with real Postgres.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will do
&lt;/h2&gt;

&lt;p&gt;In this guide, we'll deploy a public-facing todo app to Fly.io with a Ghost Postgres database. After a one-time bootstrap, &lt;strong&gt;the agent does everything else&lt;/strong&gt; — you just paste prompts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bootstrap (you):&lt;/strong&gt; install the Ghost CLI and &lt;code&gt;flyctl&lt;/code&gt;, log in, configure the Ghost MCP server in your agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaffold the app + create the database + define the schema (agent):&lt;/strong&gt; generate a small Express todo app, create a Ghost database, define a &lt;code&gt;todos&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire the app to the database and test locally (agent):&lt;/strong&gt; set &lt;code&gt;DATABASE_URL&lt;/code&gt;, run the app on &lt;code&gt;localhost&lt;/code&gt;, round-trip a todo through the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to Fly.io (agent):&lt;/strong&gt; create the Fly app, push the connection string as a secret, deploy to a &lt;code&gt;*.fly.dev&lt;/code&gt; URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the public app (agent):&lt;/strong&gt; curl the live URL, add a todo over HTTPS, confirm it landed in Ghost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open it in your browser (you):&lt;/strong&gt; use the live app yourself and share the URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean up (agent):&lt;/strong&gt; destroy the Fly app and delete the Ghost database.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1 — Bootstrap (you, one-time)
&lt;/h2&gt;

&lt;p&gt;This is the only part you can't delegate.&lt;/p&gt;

&lt;p&gt;Install the &lt;code&gt;ghost&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://install.ghost.build | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, run &lt;code&gt;irm https://install.ghost.build/install.ps1 | iex&lt;/code&gt; in PowerShell.&lt;/p&gt;

&lt;p&gt;Install &lt;code&gt;flyctl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://fly.io/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, run &lt;code&gt;pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Log into both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ghost login
flyctl auth login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each opens your browser. &lt;code&gt;flyctl auth login&lt;/code&gt; prompts you to add a credit card if you haven't yet.&lt;/p&gt;

&lt;p&gt;Configure Ghost as an MCP server in your agent. For Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ghost mcp &lt;span class="nb"&gt;install &lt;/span&gt;claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;claude-code&lt;/code&gt; with &lt;code&gt;cursor&lt;/code&gt;, &lt;code&gt;codex&lt;/code&gt;, &lt;code&gt;windsurf&lt;/code&gt;, &lt;code&gt;gemini&lt;/code&gt;, &lt;code&gt;vscode&lt;/code&gt;, &lt;code&gt;kiro-cli&lt;/code&gt;, or &lt;code&gt;antigravity&lt;/code&gt; if you use a different agent. Run &lt;code&gt;ghost mcp install&lt;/code&gt; with no argument for an interactive picker.&lt;/p&gt;

&lt;p&gt;Restart your agent so it picks up the new MCP server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ghost &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;ghost version 1.x.x

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;flyctl version
&lt;span class="go"&gt;flyctl v0.x.x ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once both CLIs are installed, you're logged in, and the agent has been restarted, the rest is the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Scaffold the app, create the database, define the schema (agent)
&lt;/h2&gt;

&lt;p&gt;Tell the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build me a minimal public-facing todo app I can deploy to Fly.io.

Create a fresh empty directory called `todo-app` and work inside it.

Stack: Node.js with Express and the `pg` package. One server file. Server-rendered HTML — no frontend framework. Read DATABASE_URL from the environment.

Routes:
- GET  /                  render the list of todos with a small form to add a new one
- POST /todos             insert a new todo from form data, then redirect to /
- POST /todos/:id/done    mark a todo done, then redirect to /

Files to write:
- package.json             express + pg + dotenv
- server.js                the app
- Dockerfile               minimal Node runtime, copies package.json + server.js, runs `node server.js`
- fly.toml                 app = "todo-app", primary_region = "iad", [http_service] with internal_port=3000, force_https=true, auto_stop_machines="stop", auto_start_machines=true, min_machines_running=0. No [[services]] block. No [[mounts]]. No managed Postgres.
- .dockerignore            node_modules, .env, .git

Then, using the Ghost MCP:
1. Create a new Ghost database called "todo-app". Wait for it to be ready.
2. Create a `todos` table with columns: id (serial primary key), text (text not null), done (boolean default false), created_at (timestamptz default now()).
3. Print the connection string so I can use it in the next step.

Don't use a migration framework. Don't add auth. Keep server.js under 100 lines.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;server.js&lt;/code&gt;, &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;fly.toml&lt;/code&gt;, &lt;code&gt;.dockerignore&lt;/code&gt;, and minimal HTML.&lt;/li&gt;
&lt;li&gt;Create the Ghost database.&lt;/li&gt;
&lt;li&gt;Create the &lt;code&gt;todos&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;Print the connection string.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database "todo-app" created (status: running).
Table "todos" created with 4 columns.
Connection: postgres://tsdbadmin:...@...tsdb.cloud.timescale.com:.../tsdb?sslmode=require
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have a Postgres database in the cloud and a tiny app on disk — including a Dockerfile and fly.toml — ready for deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Wire the app to the database and test locally (agent)
&lt;/h2&gt;

&lt;p&gt;Tell the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wire the app to the Ghost database we just created.

1. Write a `.env` file with DATABASE_URL set to the connection string from the previous step. Add `.env` to `.gitignore`.
2. Make sure server.js loads .env (use the `dotenv` package).
3. SSL setup for Timescale: recent `pg` versions treat `sslmode=require` in the URL as `verify-full`, which rejects Timescale's cert chain and crashes on the first query. Strip the `sslmode` query param from DATABASE_URL before passing it to `new Pool({ ... })`, and pass `ssl: { rejectUnauthorized: false }` in the Pool config.
4. Run `npm install` and start the server on port 3000 in the background.
5. Use curl to: GET /, POST a todo with text="Ship the app", GET / again, then POST /todos/1/done.
6. Print the response bodies so I can see the todo round-tripping through the database.
7. Stop the local server.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write &lt;code&gt;.env&lt;/code&gt; and update &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install dependencies.&lt;/li&gt;
&lt;li&gt;Start &lt;code&gt;node server.js&lt;/code&gt; in the background.&lt;/li&gt;
&lt;li&gt;Run a sequence of &lt;code&gt;curl&lt;/code&gt; commands.&lt;/li&gt;
&lt;li&gt;Kill the local process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl localhost:3000
&lt;span class="gp"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;...&amp;lt;h1&amp;gt;Todos&amp;lt;/h1&amp;gt;&amp;lt;form &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/todos"&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;...
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'text=Ship the app'&lt;/span&gt; localhost:3000/todos
&lt;span class="go"&gt;(302 redirect to /)

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl localhost:3000
&lt;span class="gp"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;...&amp;lt;li&amp;gt;Ship the app &amp;lt;form &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/todos/1/done"&lt;/span&gt;...
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST localhost:3000/todos/1/done
&lt;span class="go"&gt;(302 redirect to /)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app works end-to-end against your Ghost database. Time to put it on the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Deploy to Fly.io (agent)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; This step creates a billable Fly.io app on a public URL. With auto-stop machines enabled (configured in step 2's &lt;code&gt;fly.toml&lt;/code&gt;), an idle app costs only for storage and bandwidth — typically cents per month — but charges accrue once the machine is running. Make sure you're comfortable with Fly's pay-as-you-go pricing before deploying.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tell the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deploy the app to Fly.io. Skip `flyctl launch` entirely — we already have a Dockerfile and fly.toml from step 2, and `flyctl launch --yes` has a habit of provisioning unwanted Fly Postgres clusters and overwriting DATABASE_URL.

1. Pick a globally unique app name. Start with "todo-app" and append a 6-char random suffix if Fly says it's taken (e.g. "todo-app-a1b2c3").
2. Update `app = ` in fly.toml to that name.
3. Create the app: `flyctl apps create &amp;lt;name&amp;gt;`.
4. Set DATABASE_URL as a Fly secret using the connection string from step 2: `flyctl secrets set DATABASE_URL="&amp;lt;connection string&amp;gt;" --app &amp;lt;name&amp;gt;`.
5. Deploy: `flyctl deploy --ha=false`. Wait for it to finish.
6. After deploy, force a single machine: `flyctl scale count 1 --app &amp;lt;name&amp;gt; --yes`. (Fly's first deploy sometimes creates two machines despite `--ha=false`; this keeps it to one so the auto-stop story stays honest.)
7. Print the public URL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick an app name and update &lt;code&gt;fly.toml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;flyctl apps create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the secret.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;flyctl deploy --ha=false&lt;/code&gt; and capture the URL.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;flyctl scale count 1&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;==&amp;gt; Building image
...
==&amp;gt; Pushing image to fly
...
==&amp;gt; Monitoring deployment
 ✔ [job] update succeeded

Visit your newly deployed app at https://todo-app-&amp;lt;suffix&amp;gt;.fly.dev/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app is live on the public internet, talking to your Ghost database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Verify the public app (agent)
&lt;/h2&gt;

&lt;p&gt;Tell the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verify the deployed app works against the Ghost database.

1. curl the public URL and confirm it renders the todos page.
2. Submit a new todo via curl: POST /todos with text="Hello from the internet".
3. curl the public URL again and confirm the new todo shows up.
4. Use the Ghost MCP to run `SELECT * FROM todos ORDER BY id` and show me the rows directly from the database.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;curl https://todo-app-&amp;lt;suffix&amp;gt;.fly.dev/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -X POST -d 'text=Hello from the internet' https://todo-app-&amp;lt;suffix&amp;gt;.fly.dev/todos&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl https://todo-app-&amp;lt;suffix&amp;gt;.fly.dev/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run the SELECT through the Ghost MCP.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; id |          text           | done |          created_at
----+-------------------------+------+-------------------------------
  1 | Ship the app            | t    | 2026-05-07 10:42:15.123+00
  2 | Hello from the internet | f    | 2026-05-07 10:48:03.456+00
(2 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The row added through the public HTTPS URL is sitting in your Ghost database. You shipped a public-facing, Postgres-backed app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Open it in your browser (you)
&lt;/h2&gt;

&lt;p&gt;Click the &lt;code&gt;https://todo-app-&amp;lt;suffix&amp;gt;.fly.dev/&lt;/code&gt; URL printed at the end of step 4 (or paste it into your browser).&lt;/p&gt;

&lt;p&gt;Add a few todos through the form. Mark some done. Refresh the page — your todos persist across reloads because they're sitting in Ghost. Send the URL to a friend; it works for them too. It's on the public internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A working todo app in your browser, with todos that survive a refresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7 — Clean up (agent)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; &lt;code&gt;flyctl apps destroy&lt;/code&gt; and Ghost's &lt;code&gt;delete&lt;/code&gt; are irreversible. The Fly app, all its history, and the Ghost database (including all data) are gone. The agent will run these on your behalf — don't approve unless you mean it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tell the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tear down everything we created so I'm not paying for an idle Fly machine or holding a Ghost database I don't need.

1. Run `flyctl apps destroy &amp;lt;app-name&amp;gt; --yes` to delete the Fly app.
2. Use the Ghost MCP to delete the "todo-app" database.
3. Confirm both are gone: `flyctl apps list` should not list the app, and `ghost list` should not list the database.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;flyctl apps destroy todo-app-&amp;lt;suffix&amp;gt; &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;span class="gp"&gt;Destroyed app todo-app-&amp;lt;suffix&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ghost delete todo-app
&lt;span class="go"&gt;Deleted database "todo-app".

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;flyctl apps list
&lt;span class="go"&gt;NAME    OWNER   STATUS  ...
(no todo-app)

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ghost list
&lt;span class="go"&gt;NAME  STATUS  ...
(no todo-app)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're back to zero cost on both sides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;You shipped a public-facing, Postgres-backed app for the price of a coffee. You learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bootstrap Ghost and Fly.io for an agent-driven workflow.&lt;/li&gt;
&lt;li&gt;Use Ghost's MCP server to create databases and run SQL from inside your agent.&lt;/li&gt;
&lt;li&gt;Deploy a Node.js app to Fly.io with auto-stop machines so you only pay for active compute.&lt;/li&gt;
&lt;li&gt;Connect a Fly app to a Ghost database via a &lt;code&gt;DATABASE_URL&lt;/code&gt; secret.&lt;/li&gt;
&lt;li&gt;Tear it all down cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to do next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add a custom domain with &lt;code&gt;flyctl certs add yourdomain.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add authentication — a session-cookie + bcrypt setup is enough for a small app.&lt;/li&gt;
&lt;li&gt;Try &lt;code&gt;ghost fork&lt;/code&gt; before your next schema change — Ghost lets you run a risky migration on a copy of the database first, then apply the same migration to production once you're happy.&lt;/li&gt;
&lt;li&gt;Read the Fly docs on &lt;a href="https://fly.io/docs/launch/autostop-autostart/" rel="noopener noreferrer"&gt;auto-stop machines&lt;/a&gt; to understand how the scale-to-zero billing works.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Make Ghost your default Postgres!
&lt;/h2&gt;

&lt;p&gt;Tell your agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When I'm prototyping or shipping a small app and need a Postgres database, default to Ghost. Use Ghost MCP tools to create / fork / delete databases and to run SQL. Don't suggest local Postgres setup or other managed-Postgres services unless I ask.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this work with my agent?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, as long as it has both MCP support and a Bash/shell tool. Confirmed-working agents include Claude Code, Cursor (in agent mode), Codex, Windsurf, Gemini CLI, VS Code with Copilot, Kiro, and Antigravity. The shell tool is what lets the agent run &lt;code&gt;flyctl deploy&lt;/code&gt; and &lt;code&gt;npm install&lt;/code&gt; for you — without it, the agent can talk to Ghost via MCP but can't deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does this actually cost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ghost is free for a sparse-traffic hobby app: 100 active compute hours per month and 1TB of storage. Ghost meters in 15-minute chunks when something queries the database, and idle databases don't burn compute. A handful of human visits per day fits comfortably.&lt;/p&gt;

&lt;p&gt;The failure mode to watch for: Ghost meters per 15-minute chunk, so anything that hits the database every 15 minutes — uptime monitors, health checks, aggressive bots, link previewers — can keep the meter running 24/7. That's ~720 hours/month, well past the 100-hour free tier, and works out to roughly $46/month at $0.075/CPU-hr. If your app needs constant availability, switch to Ghost's $10/month dedicated tier (always-on, no auto-pause).&lt;/p&gt;

&lt;p&gt;Fly.io is no longer free as of late 2024. With auto-stop enabled (which we configured in step 4), an idle app costs only for storage and bandwidth — typically cents per month. A small &lt;code&gt;shared-cpu-1x&lt;/code&gt; machine running 24/7 is around $2/month, and auto-stop means most hobby apps spend most of their time at zero compute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just use SQLite on a Fly volume?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SQLite + Fly volumes is a legitimate cheaper option for one-machine apps. You give up real concurrent writes, Postgres's type system and extensions (full-text search, JSONB, time-series, PostGIS, etc.), and the ability to scale to multiple Fly regions without painful litestream/LiteFS setups. You also can't &lt;code&gt;psql&lt;/code&gt; into a SQLite file from your laptop while debugging. For anything you'd want to grow into a real product, Postgres is worth the small extra setup — and with Ghost it's not a meaningful extra cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.ghost.build" rel="noopener noreferrer"&gt;Ghost docs&lt;/a&gt; — full CLI and MCP reference.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fly.io/docs/launch/" rel="noopener noreferrer"&gt;Fly.io launch guide&lt;/a&gt; — deployment basics, including Dockerfile detection.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fly.io/docs/launch/autostop-autostart/" rel="noopener noreferrer"&gt;Fly.io auto-stop machines&lt;/a&gt; — how scale-to-zero billing works.&lt;/li&gt;
&lt;li&gt;
&lt;a href="//movielens-analysis.md"&gt;How to Analyze a Dataset with Ghost and an AI Agent&lt;/a&gt; — same agent-driven shape, focused on data analysis.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>mcp</category>
      <category>sideprojects</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Speaker Tests Can Catch Output Routing Problems</title>
      <dc:creator>Kotty Jan</dc:creator>
      <pubDate>Tue, 12 May 2026 02:24:36 +0000</pubDate>
      <link>https://dev.to/kotty_jan_bcb9d38b943b76b/speaker-tests-can-catch-output-routing-problems-3kdk</link>
      <guid>https://dev.to/kotty_jan_bcb9d38b943b76b/speaker-tests-can-catch-output-routing-problems-3kdk</guid>
      <description>&lt;p&gt;Audio output problems are often routing problems. The sound may be playing, but it is going to a monitor, Bluetooth headset, docking station, or muted device instead of the speakers you expected.&lt;/p&gt;

&lt;p&gt;An &lt;a href="https://mictests.org/en/speaker-test" rel="noopener noreferrer"&gt;online speaker test&lt;/a&gt; can help confirm whether the current output setup is producing sound. Play a test tone and listen for the expected left and right channels. If you hear nothing, the issue may be output selection, volume, mute state, or device connection.&lt;/p&gt;

&lt;p&gt;This is useful before presentations, online classes, interviews, and recordings. It is also helpful after switching between headphones and speakers, joining a new conference room, or reconnecting Bluetooth devices.&lt;/p&gt;

&lt;p&gt;If the speaker test works but a specific app stays silent, check that app audio settings. If the speaker test does not work, check the operating system output device and hardware connection first.&lt;/p&gt;

&lt;p&gt;The test does not need to be complicated. For most people, the practical question is simple: can I hear sound from the device I plan to use? Answering that before a call starts can prevent an awkward first minute.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What to Check When Your Meeting App Cannot Hear You</title>
      <dc:creator>Kotty Jan</dc:creator>
      <pubDate>Tue, 12 May 2026 02:24:22 +0000</pubDate>
      <link>https://dev.to/kotty_jan_bcb9d38b943b76b/what-to-check-when-your-meeting-app-cannot-hear-you-32n7</link>
      <guid>https://dev.to/kotty_jan_bcb9d38b943b76b/what-to-check-when-your-meeting-app-cannot-hear-you-32n7</guid>
      <description>&lt;p&gt;When a meeting app cannot hear you, the problem may not be the app itself. The microphone could be muted, the wrong input could be selected, browser permission could be blocked, or the operating system could be routing audio incorrectly.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://mictests.org/en/microphone-test" rel="noopener noreferrer"&gt;browser microphone test&lt;/a&gt; is a good first step because it checks whether the browser can access the mic and detect sound. If the test shows movement when you speak, the microphone is probably working at the browser level.&lt;/p&gt;

&lt;p&gt;If the browser test works but the meeting app does not, focus on the app settings. Check the selected input device, mute status, permission prompts, and whether another app is using the microphone.&lt;/p&gt;

&lt;p&gt;If the browser test does not detect sound, move outward. Check the physical mute switch, cable connection, Bluetooth battery, operating system input settings, and privacy permissions.&lt;/p&gt;

&lt;p&gt;This layered approach keeps troubleshooting sane. Instead of changing every setting at once, you learn where the signal stops. The goal is not to run a full audio engineering diagnosis. The goal is to answer one practical question quickly: can this microphone capture my voice right now?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Tell Whether a Keyboard Problem Is Hardware or App-Specific</title>
      <dc:creator>Kotty Jan</dc:creator>
      <pubDate>Tue, 12 May 2026 02:23:20 +0000</pubDate>
      <link>https://dev.to/kotty_jan_bcb9d38b943b76b/how-to-tell-whether-a-keyboard-problem-is-hardware-or-app-specific-gcd</link>
      <guid>https://dev.to/kotty_jan_bcb9d38b943b76b/how-to-tell-whether-a-keyboard-problem-is-hardware-or-app-specific-gcd</guid>
      <description>&lt;p&gt;Keyboard problems can be confusing because they do not always look like hardware problems. A shortcut may fail in one app, a key may repeat in a text field, or a letter may stop appearing only sometimes.&lt;/p&gt;

&lt;p&gt;The first step is to separate the keyboard from the application. A &lt;a href="https://mictests.org/en/keyboard-test" rel="noopener noreferrer"&gt;keyboard test online&lt;/a&gt; can help because it shows whether the browser detects each key press. Press the keys that seem unreliable and watch the feedback.&lt;/p&gt;

&lt;p&gt;If the test detects the key consistently, the issue may be app-specific. It could be a shortcut conflict, input method setting, browser extension, remote desktop session, or software focus problem.&lt;/p&gt;

&lt;p&gt;If the test does not detect the key, or detects it inconsistently, the issue may be closer to the hardware or operating system. Common causes include dust, liquid damage, a loose external keyboard connection, low battery, Bluetooth interference, or a damaged switch.&lt;/p&gt;

&lt;p&gt;Testing every key is also useful after spills, repairs, travel, or switching keyboards. It gives you a quick way to find dead keys before the problem interrupts work.&lt;/p&gt;

&lt;p&gt;An online test will not repair a keyboard, but it can narrow the next step. Once you know whether the key is being detected, troubleshooting becomes much less random.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Python Decorators Explained Simply</title>
      <dc:creator>qing</dc:creator>
      <pubDate>Tue, 12 May 2026 02:23:05 +0000</pubDate>
      <link>https://dev.to/qingluan/python-decorators-explained-simply-33o4</link>
      <guid>https://dev.to/qingluan/python-decorators-explained-simply-33o4</guid>
      <description>&lt;h1&gt;
  
  
  Python Decorators Explained Simply
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Python Decorators Explained Simply is essential knowledge for every developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Points
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start with the basics&lt;/li&gt;
&lt;li&gt;Practice regularly&lt;/li&gt;
&lt;li&gt;Build real projects&lt;/li&gt;
&lt;li&gt;Share your knowledge&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The best way to learn is by doing. Set up a test environment and experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Follow official documentation&lt;/li&gt;
&lt;li&gt;Join community forums&lt;/li&gt;
&lt;li&gt;Contribute to open source&lt;/li&gt;
&lt;li&gt;Write about what you learn&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Mastering python opens many career opportunities. Start today!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow for more python content!&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;More at &lt;a href="https://%E9%9D%92.%E5%A4%B1%E8%90%BD.%E4%B8%96%E7%95%8C" rel="noopener noreferrer"&gt;https://青.失落.世界&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Daily Puzzle Guides Keep Casual Games Replayable</title>
      <dc:creator>Kotty Jan</dc:creator>
      <pubDate>Tue, 12 May 2026 02:23:04 +0000</pubDate>
      <link>https://dev.to/kotty_jan_bcb9d38b943b76b/why-daily-puzzle-guides-keep-casual-games-replayable-199a</link>
      <guid>https://dev.to/kotty_jan_bcb9d38b943b76b/why-daily-puzzle-guides-keep-casual-games-replayable-199a</guid>
      <description>&lt;p&gt;Daily puzzle features give casual games a reason to come back. Instead of playing randomly, players get one focused challenge that can become part of a quick routine.&lt;/p&gt;

&lt;p&gt;The useful part of a daily guide is convenience. A player may not want to browse hundreds of levels every day. A single &lt;a href="https://arrowspuzzleguide.com/daily-puzzle" rel="noopener noreferrer"&gt;Arrows Puzzle daily puzzle&lt;/a&gt; page gives them a clear place to start, especially when they only have a few minutes.&lt;/p&gt;

&lt;p&gt;This works well for puzzle games because the challenge is short but still satisfying. You can open the daily puzzle, think through the board, check a hint or walkthrough if needed, and move on.&lt;/p&gt;

&lt;p&gt;Daily guides also help players improve. If you compare your first plan with the walkthrough, you can see whether you missed a safer opening move or cleared a blocking arrow too early.&lt;/p&gt;

&lt;p&gt;For casual players, that balance matters. The guide is there when needed, but the daily format still keeps the game light, repeatable, and easy to fit into a normal day.&lt;/p&gt;

</description>
      <category>gaming</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
