<?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: Muhammad Azlaan Zubair</title>
    <description>The latest articles on DEV Community by Muhammad Azlaan Zubair (@mdazlaanzubair).</description>
    <link>https://dev.to/mdazlaanzubair</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F820665%2Fd4e80dec-bb09-4cf2-86bf-62322f57da44.png</url>
      <title>DEV Community: Muhammad Azlaan Zubair</title>
      <link>https://dev.to/mdazlaanzubair</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mdazlaanzubair"/>
    <language>en</language>
    <item>
      <title>LLM Input vs. Output Token Pricing: How to Calculate the Difference</title>
      <dc:creator>Muhammad Azlaan Zubair</dc:creator>
      <pubDate>Sat, 24 Oct 2026 11:42:37 +0000</pubDate>
      <link>https://dev.to/mdazlaanzubair/llm-input-vs-output-token-pricing-how-to-calculate-the-difference-3cng</link>
      <guid>https://dev.to/mdazlaanzubair/llm-input-vs-output-token-pricing-how-to-calculate-the-difference-3cng</guid>
      <description>&lt;p&gt;Input and output tokens are often billed at different rates, so &lt;strong&gt;the total number of tokens alone is not enough to estimate an LLM API bill&lt;/strong&gt;. To calculate the cost of a request, multiply input tokens by the input rate, output tokens by the output rate, then add any applicable cached-input or other usage charges.&lt;/p&gt;

&lt;p&gt;The exact rates depend on the provider, model, service tier, and billing conditions. OpenAI, Anthropic, and Google publish pricing tables that distinguish input from output and document additional pricing rules for features such as caching or tools. Check the &lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI API pricing page&lt;/a&gt;, &lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Anthropic Claude pricing&lt;/a&gt;, or &lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini API pricing&lt;/a&gt; for the model and tier you actually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input tokens&lt;/strong&gt; are the tokens the model receives, including relevant instructions, conversation history, retrieved text, and the current user message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output tokens&lt;/strong&gt; are the tokens the model generates. Depending on the model and API, billed output can include reasoning or thinking tokens as well as visible response text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached input tokens&lt;/strong&gt; may be charged at a separate rate when a provider supports prompt or context caching. Cache writes and storage can have their own charges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other billable operations&lt;/strong&gt;—such as web search, tool use, audio, image processing, or agent runtime—may sit outside a simple text-token calculation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the &lt;a href="https://letinix.com/tools/llm-api-cost-calculator" rel="noopener noreferrer"&gt;Letinix LLM API Cost Calculator&lt;/a&gt; to estimate a text-token scenario by entering the current rates from your provider's pricing page. It does not automatically fetch rates, so update your inputs when prices or model choices change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why input and output rates differ
&lt;/h2&gt;

&lt;p&gt;Providers price model usage by the type and amount of processing being billed. Input tokens represent material supplied to the model; output tokens represent material generated by it. The provider sets separate rates for each category, and output rates are often higher—but that is not a universal rule across every model, modality, or service tier.&lt;/p&gt;

&lt;p&gt;The practical implication is simple: two requests with the same combined token count can cost different amounts if their input/output mix differs.&lt;/p&gt;

&lt;p&gt;For example, a request containing 8,000 input tokens and 500 output tokens should not be estimated as though it contained 8,500 tokens all billed at the input rate. Calculate the two categories separately. The model's current pricing table—not a general rule about output being more expensive—determines the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculate the cost of one request
&lt;/h2&gt;

&lt;p&gt;For a simple text request with no caching or separately billed tools, use this formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input cost  = input tokens  / 1,000,000 × input price per 1M tokens
output cost = output tokens / 1,000,000 × output price per 1M tokens
request cost = input cost + output cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prices in this formula must use the same currency and billing unit. If the vendor lists a rate per million tokens, divide token counts by 1,000,000. Do not multiply the full token count by a single blended rate unless you deliberately calculated that blended rate from your expected input/output mix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Worked example with hypothetical rates
&lt;/h3&gt;

&lt;p&gt;Assume one request uses 8,000 input tokens and 1,000 output tokens. For illustration only, suppose the input rate is &lt;strong&gt;$2 per million tokens&lt;/strong&gt; and the output rate is &lt;strong&gt;$8 per million tokens&lt;/strong&gt;. These are hypothetical values, not a current provider quote.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Calculation&lt;/th&gt;
&lt;th&gt;Estimated cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;8,000 ÷ 1,000,000 × $2&lt;/td&gt;
&lt;td&gt;$0.016&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;1,000 ÷ 1,000,000 × $8&lt;/td&gt;
&lt;td&gt;$0.008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;$0.016 + $0.008&lt;/td&gt;
&lt;td&gt;$0.024&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here, input costs twice as much as output in absolute dollars because the request contains eight times more input tokens, even though the assumed output rate is four times higher. A higher per-token rate does not necessarily mean that category dominates the bill; the token volume matters too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estimate a month of usage
&lt;/h2&gt;

&lt;p&gt;Once you have an estimate per request, multiply it by expected request volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monthly cost = cost per request × requests per month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the hypothetical $0.024 request above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1,000 requests per month: $24&lt;/li&gt;
&lt;li&gt;10,000 requests per month: $240&lt;/li&gt;
&lt;li&gt;100,000 requests per month: $2,400&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These totals assume every request has the same token profile and that the only charges are the two token categories in the example. Real traffic varies. Measure representative requests, estimate a range, and separate common, long-context, and long-output cases if their token profiles differ materially.&lt;/p&gt;

&lt;p&gt;For a repeatable estimate, enter your input and output token counts, current rates, and request volume into the &lt;a href="https://letinix.com/tools/llm-api-cost-calculator" rel="noopener noreferrer"&gt;LLM API Cost Calculator&lt;/a&gt;. Use the result as a scenario estimate, not as an invoice or a substitute for the provider's usage records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where cached input fits
&lt;/h2&gt;

&lt;p&gt;Caching can change the cost of repeated prompt material. When a provider supports prompt caching, some input tokens may be billed at a cache-read rate rather than the standard input rate. Depending on the service, writing content to a cache or keeping it stored can also have a separate charge.&lt;/p&gt;

&lt;p&gt;A simple model of cached input is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uncached input cost = uncached input tokens / 1,000,000 × input rate
cached input cost   = cached input tokens   / 1,000,000 × cache-read rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add those components to the output cost, and include cache-write or storage charges where applicable. Do not assume all cached tokens are free, that every model supports caching, or that the same cache lifetime and write/read multipliers apply across vendors. See the official &lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI pricing documentation&lt;/a&gt; and &lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Anthropic prompt-caching pricing&lt;/a&gt; for provider-specific rules; Google's &lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini pricing documentation&lt;/a&gt; also lists context-caching terms for supported models.&lt;/p&gt;

&lt;p&gt;The current Letinix calculator can model a cache-read price and cache-hit percentage when you supply those values. Its result is only as accurate as the token counts, hit-rate assumption, and prices you enter. If cache writes or storage are separately charged in your configuration, add those costs separately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa260e29xey20yvigxryq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa260e29xey20yvigxryq.webp" alt="Diagram showing standard input, optional cached input, and output cost components feeding an estimated request total." width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The categories and rates must match the selected provider and model.&lt;/em&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Treating every token as the same price
&lt;/h3&gt;

&lt;p&gt;A blended rate can hide important changes in the input/output mix. Keep the categories separate until you know the workload's typical distribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Counting only the visible prompt and answer
&lt;/h3&gt;

&lt;p&gt;Conversation history, system instructions, retrieved documents, tool definitions, and other request context can add input tokens. Some models also bill reasoning or thinking tokens as output even when those tokens are not shown as ordinary response text. Check the model's official billing documentation and use token usage reported by the API when available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assuming caching is always cheaper overall
&lt;/h3&gt;

&lt;p&gt;Cache reads may be cheaper than ordinary input, but cache creation, storage, eligibility, minimum cacheable prompt sizes, and retention windows can affect the economics. Use the actual provider's rules and your observed cache-hit rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forgetting charges outside text tokens
&lt;/h3&gt;

&lt;p&gt;A text-token estimate may omit separately priced search or other tools, image and audio processing, hosting, orchestration, retries, and monitoring. Google documents separate pricing for some grounding tools; other providers also have feature-specific pricing. Add these components when your application uses them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a price from an old model page
&lt;/h3&gt;

&lt;p&gt;Pricing and model availability can change. Before estimating or comparing providers, confirm the exact model, tier, region, processing mode, and cache rules on the official pricing page. Record when you checked the rate if the estimate will be used for a budget or business case.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reliable estimation workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose the exact model and service tier.&lt;/strong&gt; Do not mix a model's standard rate with a batch, priority, regional, or other tier's rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure representative token counts.&lt;/strong&gt; Include system prompts, conversation history, retrieved content, and generated output. Use API-reported usage where possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split input, output, and cached input.&lt;/strong&gt; Keep each billing category separate and account for cache writes or storage if the provider charges them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiply by realistic request volume.&lt;/strong&gt; Estimate normal and high-usage scenarios rather than assuming every request is identical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add non-token charges.&lt;/strong&gt; Include paid tools, modalities, runtime, and infrastructure that your workload actually uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recheck the official rates before making a commitment.&lt;/strong&gt; A calculator is useful for the arithmetic; provider documentation remains authoritative for current billing rules.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Limitations and next steps
&lt;/h2&gt;

&lt;p&gt;The Letinix calculator estimates text-token costs from the rates and usage assumptions you enter. It does not automatically synchronize provider prices or model your complete agent graph, retries, every modality, cache-write/storage fees, or hosting and observability costs. Treat those as separate line items where relevant.&lt;/p&gt;

&lt;p&gt;If you are planning an application, start with a representative request and calculate the input/output split before extrapolating to monthly volume. For broader planning, use the &lt;a href="https://letinix.com/articles/build/how-to-estimate-llm-api-costs" rel="noopener noreferrer"&gt;guide to estimating LLM API costs before launch&lt;/a&gt; alongside this calculation, then add the costs that sit outside simple text-token billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI API pricing — OpenAI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Pricing — Anthropic Claude Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini API pricing — Google AI for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letinix.com/tools/llm-api-cost-calculator" rel="noopener noreferrer"&gt;LLM API Cost Calculator — Letinix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://letinix.com/articles/build/llm-input-output-token-pricing" rel="noopener noreferrer"&gt;Letinix&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Validate API Responses in TypeScript</title>
      <dc:creator>Muhammad Azlaan Zubair</dc:creator>
      <pubDate>Sat, 24 Oct 2026 11:42:12 +0000</pubDate>
      <link>https://dev.to/mdazlaanzubair/how-to-validate-api-responses-in-typescript-4ci6</link>
      <guid>https://dev.to/mdazlaanzubair/how-to-validate-api-responses-in-typescript-4ci6</guid>
      <description>&lt;p&gt;TypeScript types do not validate API responses at runtime. They help the compiler check how your code uses values, but a remote server can still return malformed JSON, missing fields, or values that do not match your declared type. To check the data you actually receive, validate it at the boundary where it enters your application.&lt;/p&gt;

&lt;p&gt;A practical pattern is to treat the payload as unknown, validate it with a runtime schema, and only then use the validated result. This guide uses Zod for a concise example; JSON Schema with a validator such as Ajv is another option when a portable schema is important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a TypeScript annotation is not enough
&lt;/h2&gt;

&lt;p&gt;This assertion does not inspect the response:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;unsafe.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The assertion tells TypeScript to trust the programmer. It does not check that id is a number or that name exists. TypeScript's handbook documents that type assertions are removed at compile time and have no runtime checking.&lt;/p&gt;

&lt;p&gt;Parsing JSON is a separate step. JSON.parse() checks whether a string is syntactically valid JSON, but valid JSON can still have the wrong shape for your application. For example, {"id":"not-a-number","name":false} is valid JSON but does not match the User contract above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate at the boundary with Zod
&lt;/h2&gt;

&lt;p&gt;Install Zod in the project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;terminal&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define a schema for the shape your application expects. The example below is illustrative and follows the API documented in Zod's basic-usage guide:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;user-schema.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema defines runtime checks, and z.infer derives the corresponding static TypeScript type. In this example, email must be present, but its value can be either a string or null. If the API may omit the property too, make that rule explicit with an optional schema.&lt;/p&gt;

&lt;p&gt;Use safeParse() to handle validation failures as data rather than relying on an exception:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;get-user.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./user-schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User request failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The API returned an unexpected user shape&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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;The result's success branch contains validated data that TypeScript can treat as the inferred type. The error branch contains the validation details, which you can log or map into your application's error model. The example intentionally keeps error handling minimal; production code should choose how much detail is safe to expose to callers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what the schema should enforce
&lt;/h2&gt;

&lt;p&gt;A schema should reflect the contract your application needs, not just the first payload you happened to capture. Consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required properties: which keys must exist for the application to proceed?&lt;/li&gt;
&lt;li&gt;Nullability and optionality: can a key be absent, present with null, or both?&lt;/li&gt;
&lt;li&gt;Constraints: does an identifier need a particular format, or must a status be one of a known set?&lt;/li&gt;
&lt;li&gt;Unknown keys: should additional fields be accepted, stripped, or rejected?&lt;/li&gt;
&lt;li&gt;Variants: does the endpoint return a different object shape for different states?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runtime validation is only as strong as the rules you define. A schema that checks only id and name will not establish constraints it does not express. Keep validation aligned with the API's documentation and update it when the contract changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  When JSON Schema and Ajv are a better fit
&lt;/h2&gt;

&lt;p&gt;Zod is a TypeScript-first option that lets you define schemas in code and infer TypeScript types from them. JSON Schema is a declarative format for describing and validating JSON documents. It can be useful when a schema needs to be shared across services or used by tooling outside TypeScript.&lt;/p&gt;

&lt;p&gt;Ajv compiles JSON Schema or JSON Type Definition schemas into validation functions and can integrate validation results with TypeScript's type system. Choose based on your contract source, portability requirements, schema features, and validation workflow—not a blanket claim that one library is best for every application.&lt;/p&gt;

&lt;p&gt;If the API already publishes an OpenAPI schema, consider generating TypeScript declarations from that contract instead of manually maintaining a parallel type. For example, openapi-typescript generates types from OpenAPI 3.0 and 3.1 schemas. Generated declarations improve static checking, but they still do not replace runtime validation of data received over the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where JSON-to-TypeScript conversion fits
&lt;/h2&gt;

&lt;p&gt;A converter is useful when you have a sample response and need a starting type declaration. The &lt;a href="https://letinix.com/tools/json-to-typescript" rel="noopener noreferrer"&gt;Letinix JSON to TypeScript Converter&lt;/a&gt; handles documented structures such as nested objects, arrays, and unions inferred from mixed values.&lt;/p&gt;

&lt;p&gt;The generated declarations can improve editor feedback and make application code easier to maintain. They cannot prove that future responses match the sample or reject unexpected payloads at runtime. Use sample conversion to bootstrap the static model, then add validation when the boundary and consequences justify it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Casting a payload and calling it validation. Assertions change compiler assumptions, not runtime data.&lt;/li&gt;
&lt;li&gt;Confusing valid JSON with valid application data. Syntax parsing does not establish that required fields or constraints are satisfied.&lt;/li&gt;
&lt;li&gt;Maintaining a schema that is weaker than the contract. Unchecked fields remain unchecked.&lt;/li&gt;
&lt;li&gt;Ignoring validation failures. Decide how to handle invalid responses and avoid exposing sensitive internal details.&lt;/li&gt;
&lt;li&gt;Assuming generated types stay synchronized automatically. Keep generated declarations and runtime schemas aligned with the authoritative contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html" rel="noopener noreferrer"&gt;TypeScript Handbook: Everyday Types&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zod.dev/basics" rel="noopener noreferrer"&gt;Zod: Basic usage&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://json-schema.org/specification" rel="noopener noreferrer"&gt;JSON Schema: Specification&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ajv.js.org/guide/typescript.html" rel="noopener noreferrer"&gt;Ajv: Using with TypeScript&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openapi-ts.dev/cli" rel="noopener noreferrer"&gt;OpenAPI TypeScript: CLI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://letinix.com/tools/json-to-typescript" rel="noopener noreferrer"&gt;Letinix JSON to TypeScript Converter&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://letinix.com/articles/build/validate-api-responses-typescript" rel="noopener noreferrer"&gt;Letinix&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Estimate LLM API Costs Before Launch</title>
      <dc:creator>Muhammad Azlaan Zubair</dc:creator>
      <pubDate>Thu, 22 Oct 2026 17:17:12 +0000</pubDate>
      <link>https://dev.to/mdazlaanzubair/how-to-estimate-llm-api-costs-before-launch-2hdk</link>
      <guid>https://dev.to/mdazlaanzubair/how-to-estimate-llm-api-costs-before-launch-2hdk</guid>
      <description>&lt;p&gt;To estimate LLM API costs before launch, start with the price per million input and output tokens, estimate how many tokens each request uses, and multiply by expected request volume. Treat the result as a planning estimate, not a guaranteed invoice: real usage varies with prompt length, output length, caching, retries, model routing and separately billed tools.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://letinix.com/tools/llm-api-cost-calculator" rel="noopener noreferrer"&gt;LLM API Cost Calculator&lt;/a&gt; applies those assumptions to estimate cost per request, day, month and year. The hard part is not the multiplication. It is choosing assumptions that resemble the workload you intend to ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Define the workload before entering prices
&lt;/h2&gt;

&lt;p&gt;Start with a specific product action, not a vague label such as “one AI request.” A support reply, a document summary and an agent that searches a knowledge base can have very different token and tool-use patterns.&lt;/p&gt;

&lt;p&gt;For a first estimate, write down four inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requests per period:&lt;/strong&gt; the expected number of model requests per day or month. Make clear whether you mean user actions or actual model calls; one user action can trigger several calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input tokens per request:&lt;/strong&gt; the system prompt, conversation history, retrieved passages, tool results and other content sent to the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output tokens per request:&lt;/strong&gt; the generated response and, for reasoning models, any reasoning tokens that the provider bills as output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price per million tokens:&lt;/strong&gt; the selected model’s current input and output rates, using the same billing tier and modality as your workload.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use observed traces from a prototype when you have them. Before that, define a low, expected and high scenario rather than treating a single guess as a forecast. Keep each assumption visible so it can be replaced with measured usage later.&lt;/p&gt;

&lt;p&gt;If you need to estimate the token count of a particular prompt or context first, use a tokenizer appropriate to the model. Token counts are not reliably interchangeable across model families, and text length alone is only a rough proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Use the per-million-token formula
&lt;/h2&gt;

&lt;p&gt;When a provider quotes text input and output prices per million tokens, calculate the two components separately:&lt;/p&gt;

&lt;p&gt;Use these formulas (prices are in USD per million tokens):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C_input = T_input × P_input / 1,000,000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C_output = T_output × P_output / 1,000,000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C_request = C_input + C_output&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here, each &lt;code&gt;T&lt;/code&gt; is the token count for one request, and each &lt;code&gt;P&lt;/code&gt; is the price in USD per million tokens. Multiply the per-request estimate by the number of model requests in the period to estimate total inference charges for that period.&lt;/p&gt;

&lt;p&gt;The unit matters. A price entered as dollars per million tokens must be divided by one million; a price quoted per thousand tokens, per image, per audio unit or per request needs a different calculation. Confirm the unit on the provider’s pricing page before copying a rate into the calculator.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Work through an illustrative monthly estimate
&lt;/h2&gt;

&lt;p&gt;Suppose a hypothetical application makes 5,000 model requests each day. Assume each request uses 1,200 input tokens and generates 350 output tokens. For illustration only, enter an input price of $2.00 per million tokens and an output price of $8.00 per million tokens. These are example inputs, not current prices for any provider or model.&lt;/p&gt;

&lt;p&gt;The request-level arithmetic is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: 1,200 × $2.00 ÷ 1,000,000 = &lt;strong&gt;$0.0024&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Output: 350 × $8.00 ÷ 1,000,000 = &lt;strong&gt;$0.0028&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Total: $0.0024 + $0.0028 = &lt;strong&gt;$0.0052 per request&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 5,000 requests per day, that is $26.00 per day. With 30 days in the planning month, the estimate is &lt;strong&gt;$780 per month&lt;/strong&gt;, or &lt;strong&gt;$9,360 for twelve months&lt;/strong&gt; if the same usage and prices persisted. The monthly token volume is 232.5 million tokens across input and output.&lt;/p&gt;

&lt;p&gt;This example deliberately holds request volume, token counts and prices constant. It does not account for growth, day-to-day variation, caching, failed requests, tool fees or discounts. The annual figure is a straight multiplication of the monthly scenario, not a claim about future spend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj4d49rzkwn3v28qq04e2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj4d49rzkwn3v28qq04e2.webp" alt="Diagram showing input and output token costs added per request, then multiplied by monthly request volume to estimate monthly API cost." width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The output is a scenario estimate, not a provider invoice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Model cached input separately when the price is known
&lt;/h2&gt;

&lt;p&gt;If a provider charges a different rate for cached input tokens, the cost of those tokens depends on how much input is eligible for cache reads and how often the cache is hit. A simplified estimate is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C_input = T_input × [(1 − h) × P_input + h × P_cached] / 1,000,000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;h&lt;/code&gt; is the cache-hit fraction from 0 to 1, and &lt;code&gt;P_cached&lt;/code&gt; is the price for cached input tokens. A 30% cache-hit rate is entered as 30 in the calculator’s percentage field.&lt;/p&gt;

&lt;p&gt;This is a simplified model, not a universal cache-billing formula. Provider rules can distinguish cache writes from reads, impose minimum cacheable prompt lengths or durations, and charge for cache storage. The calculator’s single cached-input price and hit-rate field does not model all those details. If writes or storage are material, estimate them separately and add them to the scenario.&lt;/p&gt;

&lt;p&gt;Do not assume that all repeated text is automatically billed at the cached rate. Check the provider’s caching documentation and the API behavior for the model you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Check the provider’s current pricing rules
&lt;/h2&gt;

&lt;p&gt;Use official pricing documentation to obtain rates for the exact model and service tier. The three major providers document different pricing details, so avoid copying one provider’s cache or tool assumptions into another provider’s estimate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI API pricing&lt;/a&gt; lists model-specific input and output prices and separate cached-input and cache-write rates for supported models.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Anthropic Claude pricing&lt;/a&gt; documents input/output pricing and prompt-cache read and write multipliers, including different cache durations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Google Gemini API pricing&lt;/a&gt; distinguishes model/service tiers, documents context caching and separately priced tools, and notes that agent usage can include intermediate input or reasoning tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These pages are references for current provider rules, not fixed values embedded in this article. Prices and model availability change. Before budgeting, check the page for your chosen model, API, modality, service tier, region or other applicable pricing modifier. Record the date and assumptions alongside any estimate you intend to compare later.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Turn one estimate into a useful forecast
&lt;/h2&gt;

&lt;p&gt;A single scenario is a starting point. A budget becomes more useful when it makes uncertainty visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use low, expected and high cases
&lt;/h3&gt;

&lt;p&gt;Vary the inputs that actually drive your workload. For example, you can model fewer or more requests, shorter or longer prompts, and lower or higher output lengths. Do not label these cases “typical” unless you have measurements supporting that claim.&lt;/p&gt;

&lt;p&gt;If request volume is uncertain, forecast it separately from cost per request. If token usage is uncertain, keep input and output estimates separate. This makes it easier to see whether the budget is sensitive to traffic, long context or generated output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure actual traces when a prototype exists
&lt;/h3&gt;

&lt;p&gt;Capture token usage returned by the API, along with the model, request type and relevant cache or tool metadata. Use a representative sample of real product tasks. Compare the observed distribution—not only the average—with your planning assumptions. Long-tail prompts and unusually large outputs can matter even when most requests are small.&lt;/p&gt;

&lt;p&gt;Update the estimate when you change the system prompt, retrieval strategy, model, output limit, routing policy or agent loop. These changes can alter token use without changing the number of user-visible actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate user actions from model calls
&lt;/h3&gt;

&lt;p&gt;For a simple chat turn, one user action may correspond to one model call. In a workflow, the same action may lead to a planning call, a tool-selection call, one or more follow-up calls and a final response. Estimate each call class separately when you know the workflow. Do not multiply every user action by an assumed fixed number of calls without checking traces or explicitly labelling the assumption.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://letinix.com/tools/llm-api-cost-calculator" rel="noopener noreferrer"&gt;calculator&lt;/a&gt; models an average request and a request count. It does not model a workflow graph, retries or tool fees as separate line items. For a multi-step agent, calculate each step or call class independently, then sum the results and add separately billed components.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Know what the estimate excludes
&lt;/h2&gt;

&lt;p&gt;A token-based estimate is not the same as the full cost of operating an AI feature. Depending on the provider and architecture, a first-pass estimate may exclude:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tool calls, search grounding, code execution or other separately billed services;&lt;/li&gt;
&lt;li&gt;cache writes, cache storage or other cache-specific charges not represented by one cached-input rate;&lt;/li&gt;
&lt;li&gt;image, audio, video or other non-text billing units;&lt;/li&gt;
&lt;li&gt;batch, priority, committed-use or other service-tier adjustments;&lt;/li&gt;
&lt;li&gt;retries, failed requests, validation calls or secondary model calls;&lt;/li&gt;
&lt;li&gt;hosting, databases, retrieval infrastructure, logging, observability, evaluation and human review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The calculator does not automatically fetch provider rates. It uses the prices and workload assumptions entered by the user, runs in the browser, and does not send those inputs elsewhere. Its results are an estimate of the token charges represented by those fields—not a complete invoice, total product cost or guarantee of future spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. A pre-launch checklist
&lt;/h2&gt;

&lt;p&gt;Before using an estimate to set a budget, verify these items:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The input and output rates match the exact model, API and service tier.&lt;/li&gt;
&lt;li&gt;All rates use the units entered into the calculator: USD per million tokens.&lt;/li&gt;
&lt;li&gt;Input tokens include the system prompt, relevant history, retrieved context and other content sent to the model.&lt;/li&gt;
&lt;li&gt;Output tokens include any reasoning tokens that the provider bills as output.&lt;/li&gt;
&lt;li&gt;Requests per day or month represent model calls, or the difference between user actions and model calls is modelled explicitly.&lt;/li&gt;
&lt;li&gt;Cache assumptions reflect the provider’s rules, and unmodelled writes or storage are added separately if relevant.&lt;/li&gt;
&lt;li&gt;Tool charges, retries and non-text usage are included separately where applicable.&lt;/li&gt;
&lt;li&gt;The forecast includes more than one scenario if workload uncertainty is material.&lt;/li&gt;
&lt;li&gt;The assumptions are dated and can be updated when pricing or product behavior changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical goal is not to predict an exact future invoice. It is to build a transparent estimate, understand which assumptions dominate it, and replace guesses with observed usage as the application matures.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI API pricing — OpenAI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Pricing — Claude Platform Docs, Anthropic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini API pricing — Google AI for Developers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://letinix.com/articles/build/how-to-estimate-llm-api-costs" rel="noopener noreferrer"&gt;Letinix&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>SaaS Unit Economics: Payment Fees and Usage Costs</title>
      <dc:creator>Muhammad Azlaan Zubair</dc:creator>
      <pubDate>Thu, 22 Oct 2026 09:36:39 +0000</pubDate>
      <link>https://dev.to/mdazlaanzubair/saas-unit-economics-payment-fees-and-usage-costs-4lc2</link>
      <guid>https://dev.to/mdazlaanzubair/saas-unit-economics-payment-fees-and-usage-costs-4lc2</guid>
      <description>&lt;p&gt;A SaaS customer’s subscription price is not the same as the revenue the business keeps. Payment processing, metered infrastructure, third-party APIs, storage, and other customer-driven expenses reduce the amount available to cover fixed costs and profit. A useful first unit-economics measure is monthly contribution per customer: revenue attributable to that customer minus the variable costs of serving and collecting payment from them.&lt;/p&gt;

&lt;p&gt;The exact calculation depends on your payment contract, product architecture, and what you classify as a variable cost. Treat the worked numbers below as an illustrative model, not a quote or a benchmark.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frtjgcjuu4g01c1obes0g.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frtjgcjuu4g01c1obes0g.webp" alt="Formula diagram showing monthly revenue minus payment fees and variable service costs equals contribution per customer, with contribution margin calculated as contribution divided by revenue." width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A customer-level contribution model separates payment costs from usage-driven service costs.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The per-customer contribution formula
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Contribution per customer = monthly revenue per customer − payment fees − variable service costs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Variable service costs can include cloud compute, storage, bandwidth, model or API calls, metered messaging, and support costs that rise with customer activity. Include only costs that are genuinely attributable to serving the customer in this calculation, and document any allocation method for shared resources.&lt;/p&gt;

&lt;p&gt;Contribution margin expresses that contribution as a share of revenue: contribution per customer ÷ monthly revenue per customer × 100. This is not necessarily the same as reported gross margin. The result depends on which costs your accounting policy includes in cost of revenue and whether customer acquisition, R&amp;amp;D, and other operating expenses are excluded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculate payment-processing fees
&lt;/h2&gt;

&lt;p&gt;Payment fees may combine a percentage of the charge with a fixed amount per successful transaction. The applicable rate can vary by provider, country, payment method, card type, currency conversion, and contract. Stripe’s published US standard pricing page lists 2.9% + $0.30 per successful domestic-card transaction; check the &lt;a href="https://stripe.com/pricing" rel="noopener noreferrer"&gt;current pricing page&lt;/a&gt; for the market and payment method you actually use. Do not apply a US rate to a different country without verification.&lt;/p&gt;

&lt;p&gt;For a single illustrative $49 monthly charge using that example rate: percentage fee: $49 × 2.9% = $1.421; fixed fee: $0.30; total processing fee: $1.721, or about 3.51% of the $49 charge. The effective percentage rises on smaller charges because the fixed fee takes a larger share. If customers make multiple billable transactions in a month, model the fixed component for each transaction rather than assuming one fee per account.&lt;/p&gt;

&lt;p&gt;Also check whether the original processing fee is returned after refunds; Stripe’s pricing page says processing fees from the original payment are not returned for its standard pricing. &lt;a href="https://stripe.com/pricing" rel="noopener noreferrer"&gt;Stripe’s pricing page&lt;/a&gt; documents this caveat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add customer-driven service costs
&lt;/h2&gt;

&lt;p&gt;Next, estimate the costs that change with usage. Separate these from fixed or step-fixed costs such as a baseline database, minimum cloud commitments, or a team member’s salary.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost driver&lt;/th&gt;
&lt;th&gt;Possible unit&lt;/th&gt;
&lt;th&gt;What to measure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compute and jobs&lt;/td&gt;
&lt;td&gt;Runtime, CPU, or job count&lt;/td&gt;
&lt;td&gt;Actual usage by tenant or workload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;GB-month or stored objects&lt;/td&gt;
&lt;td&gt;Average stored volume and retention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;GB transferred or requests&lt;/td&gt;
&lt;td&gt;Egress and request volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI or external APIs&lt;/td&gt;
&lt;td&gt;Tokens, calls, or task&lt;/td&gt;
&lt;td&gt;Provider-billed usage per customer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Messaging&lt;/td&gt;
&lt;td&gt;SMS, email, or event&lt;/td&gt;
&lt;td&gt;Billable volume and destination mix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support&lt;/td&gt;
&lt;td&gt;Tickets or handling time&lt;/td&gt;
&lt;td&gt;Only if a defensible variable allocation is available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Examples of usage-driven costs and useful measurement units.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For shared infrastructure, a cost-allocation rule is an estimate, not a direct invoice. Choose a measurable driver, apply it consistently, and revisit it when the architecture or customer mix changes. AWS cost guidance recommends cost-allocation tagging and using metrics to understand where spending occurs; see the &lt;a href="https://docs.aws.amazon.com/pdfs/decision-guides/latest/cost-management-on-aws-how-to-choose/cost-management-on-aws-how-to-choose.pdf" rel="noopener noreferrer"&gt;AWS cost optimization guidance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Avoid dividing the entire cloud bill by the number of customers and calling the result variable cost without checking the underlying cost behavior. That average can be useful for planning, but it can misstate the incremental cost of a new customer when much of the bill is fixed or shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: a $49 subscription
&lt;/h2&gt;

&lt;p&gt;Assume one customer pays $49 once per month, the illustrative US domestic-card processing fee is 2.9% + $0.30, and that customer’s attributable variable service costs are estimated at $4.50 per month.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Monthly amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subscription revenue&lt;/td&gt;
&lt;td&gt;$49.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment-processing fee&lt;/td&gt;
&lt;td&gt;−$1.72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable service costs&lt;/td&gt;
&lt;td&gt;−$4.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contribution per customer&lt;/td&gt;
&lt;td&gt;$42.78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contribution margin&lt;/td&gt;
&lt;td&gt;87.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Illustrative monthly contribution calculation; amounts rounded.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These figures are rounded. The result excludes fixed operating costs, customer acquisition costs, taxes, refunds, discounts, failed payments, currency conversion, and any additional transactions. It should not be presented as gross margin unless the cost categories match the company’s gross-margin definition.&lt;/p&gt;

&lt;p&gt;If the same customer’s usage rises and attributable service costs increase from $4.50 to $18.00, contribution falls to $29.28 and contribution margin falls to about 59.8%, assuming the same revenue and processing fee. This illustrates why a flat subscription price can become less profitable as high-usage customers grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the calculation into a decision
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Does the price cover normal usage? Estimate costs at low, typical, and high usage rather than relying on a single average.&lt;/li&gt;
&lt;li&gt;Which customers or features create cost spikes? Compare cost drivers by plan, tenant, workflow, and usage cohort where the data supports it.&lt;/li&gt;
&lt;li&gt;What happens when volume changes? Recalculate with more transactions, more API calls, different payment methods, and different usage mixes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If usage is difficult to predict, pricing can combine a base subscription with included usage and an overage rate. Stripe documents several recurring and usage-based patterns, including flat-rate, per-seat, tiered, and usage-based pricing, in its &lt;a href="https://docs.stripe.com/billing/subscriptions/metered-billing/thresholds" rel="noopener noreferrer"&gt;recurring pricing documentation&lt;/a&gt;. The model should be selected for the product and customer experience—not just to transfer every cost fluctuation to the customer.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Using a provider’s headline fee as a universal rate. Fees vary by region, payment method, contract, and currency.&lt;/li&gt;
&lt;li&gt;Forgetting the fixed fee per transaction. This is especially material for low-priced plans and frequent small charges.&lt;/li&gt;
&lt;li&gt;Treating all cloud spending as variable. Distinguish baseline capacity, commitments, shared services, and incremental usage.&lt;/li&gt;
&lt;li&gt;Using average cost as marginal cost. Average cost helps allocate a bill; marginal cost estimates the additional expense of serving one more unit or customer.&lt;/li&gt;
&lt;li&gt;Ignoring usage distribution. A few heavy users can dominate API or infrastructure costs even when the average customer appears profitable.&lt;/li&gt;
&lt;li&gt;Mixing contribution margin with gross margin. State which costs are included and use the same definition when comparing periods or plans.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connect contribution to break-even
&lt;/h2&gt;

&lt;p&gt;Contribution per customer helps estimate how many customers are needed to cover fixed costs. If monthly fixed costs are $12,000 and contribution per customer is $42.78, the simple break-even estimate is $12,000 ÷ $42.78 = 280.5, rounded up to 281 customers. This assumes the same contribution for each customer and ignores changes in costs, churn, taxes, and customer mix. Use the &lt;a href="https://letinix.com/tools/saas-pricing-calculator" rel="noopener noreferrer"&gt;SaaS Pricing Calculator&lt;/a&gt; to explore pricing and cost assumptions. Treat any calculator output as a model of the inputs provided, not as a substitute for your processor invoices, cloud usage records, or accounting definitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and next steps
&lt;/h2&gt;

&lt;p&gt;A per-customer model is only as reliable as its cost attribution. Start with actual processor fees and provider bills, choose explicit allocation drivers for shared costs, and label assumptions. Reconcile estimates against monthly totals so the sum of customer-level allocations can be compared with the source invoices.&lt;/p&gt;

&lt;p&gt;For a useful review cadence, track contribution per customer, contribution margin, usage per account, payment fees as a percentage of revenue, and the distribution—not just the average—of variable costs. Revisit the model when pricing, provider contracts, architecture, or customer behavior changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Stripe — &lt;a href="https://stripe.com/pricing" rel="noopener noreferrer"&gt;Pricing &amp;amp; Fees&lt;/a&gt;. Published standard pricing and fee caveats; rates depend on the actual market and payment method.&lt;/p&gt;

&lt;p&gt;AWS — &lt;a href="https://docs.aws.amazon.com/pdfs/decision-guides/latest/cost-management-on-aws-how-to-choose/cost-management-on-aws-how-to-choose.pdf" rel="noopener noreferrer"&gt;Choosing a cost management strategy&lt;/a&gt;. Cost allocation and usage visibility guidance.&lt;/p&gt;

&lt;p&gt;Stripe Docs — &lt;a href="https://docs.stripe.com/billing/subscriptions/metered-billing/thresholds" rel="noopener noreferrer"&gt;Recurring pricing models&lt;/a&gt;. Overview of flat-rate, per-seat, tiered, and usage-based subscription patterns.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://letinix.com/articles/build/saas-unit-economics-payment-fees-usage-costs" rel="noopener noreferrer"&gt;Letinix&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>TypeScript Optional vs. Nullable: Model API Fields Correctly</title>
      <dc:creator>Muhammad Azlaan Zubair</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:17:05 +0000</pubDate>
      <link>https://dev.to/mdazlaanzubair/typescript-optional-vs-nullable-model-api-fields-correctly-da6</link>
      <guid>https://dev.to/mdazlaanzubair/typescript-optional-vs-nullable-model-api-fields-correctly-da6</guid>
      <description>&lt;p&gt;In TypeScript, an optional property and a nullable property describe different API states. name?: string means the property may be absent; name: string | null means the property is required but its value may be null. Use name?: string | null only when both conditions are allowed by the contract.&lt;/p&gt;

&lt;p&gt;This distinction matters when you turn JSON into types: one sample can show a value of null, but it cannot prove whether the property may be omitted in other responses. If you have a sample ready, the &lt;a href="https://letinix.com/tools/json-to-typescript" rel="noopener noreferrer"&gt;Letinix JSON to TypeScript Converter&lt;/a&gt; can generate a starting declaration; review the result against the actual API contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three shapes
&lt;/h2&gt;

&lt;p&gt;Consider these illustrative API response shapes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;present.json&lt;/strong&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;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mina"&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;strong&gt;null.json&lt;/strong&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;"displayName"&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;strong&gt;missing.json&lt;/strong&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are not interchangeable. The first contains a string value. The second contains the property with a JSON null value. The third does not contain the property at all.&lt;/p&gt;

&lt;p&gt;Here are the corresponding TypeScript models:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;user.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserWithName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserWithNullableName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserWithOptionalName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserWithOptionalNullableName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;Choose the narrowest model that matches the documented contract. Making every field optional may quiet compiler errors, but it also weakens the guarantee your type provides.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ? means
&lt;/h2&gt;

&lt;p&gt;The ? modifier makes a property optional. An object typed with displayName?: string may omit that key:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;usage.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserWithOptionalName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserWithOptionalName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mina&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you read the property, TypeScript must account for the possibility that it is absent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;usage.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserWithOptionalName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// string | undefined when strictNullChecks is enabled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That undefined is a JavaScript read result; JSON itself has no undefined value. A missing JSON key becomes an undefined result when you access it as an object property.&lt;/p&gt;

&lt;p&gt;With exactOptionalPropertyTypes, TypeScript also distinguishes an omitted optional property from explicitly assigning undefined unless undefined is included in the declared value type. This option is useful when the presence of a property has meaning in your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What | null means
&lt;/h2&gt;

&lt;p&gt;A union with null allows a present property to hold a null value:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;profile.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserProfile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The property is still required. This object does not satisfy UserProfile because the key is missing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;profile.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="c1"&gt;// Type error: displayName is required&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use T | null when the API contract explicitly uses null to represent a known state, such as no assigned owner or no value available. Do not assume that a null in one example means the property can also be absent.&lt;/p&gt;

&lt;h2&gt;
  
  
  When both states are allowed
&lt;/h2&gt;

&lt;p&gt;Some APIs allow the field to be omitted and also return null when it is present but has no value. If the API documentation or observed contract supports both cases, model them explicitly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;profile.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserProfile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;Consumers must then handle all three outcomes: a string, null, or undefined after reading an omitted property.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;labels.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;labelFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unnamed&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayName&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;The == null check intentionally matches both null and undefined. If your logic needs to distinguish an absent key from an explicitly null key, test property presence separately with Object.hasOwn(user, "displayName") and then handle the value.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to infer the right type from JSON
&lt;/h2&gt;

&lt;p&gt;A converter can infer types from the values in the sample. It cannot infer every possible response state from a single object. A property that appears with a string in your sample may be absent in another response; a property that is null may be nullable, or the sample may simply not contain a non-null example.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the API's official schema or response documentation for required properties and nullability.&lt;/li&gt;
&lt;li&gt;Compare multiple realistic responses, including empty, partial, and error responses where relevant.&lt;/li&gt;
&lt;li&gt;Keep optionality and nullability separate in the type declaration.&lt;/li&gt;
&lt;li&gt;Add a runtime check when the incoming payload is not guaranteed to match the documented contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Letinix converter documents optional-key inference when objects in the same array have different keys, and it maps a field that is null in some array objects and has a value in others to a union with null. Those are useful inferences from the supplied sample; they do not replace the API specification.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Using ? to mean nullable. field?: string does not permit a present null value.&lt;/li&gt;
&lt;li&gt;Using | null to mean optional. field: string | null still requires the property to exist.&lt;/li&gt;
&lt;li&gt;Making every property optional. This can hide contract mismatches and make consumers handle states the API never permits.&lt;/li&gt;
&lt;li&gt;Trusting one sample as a complete schema. Use authoritative documentation or more representative responses.&lt;/li&gt;
&lt;li&gt;Assuming the type validates incoming JSON. Type declarations do not validate network data at runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.typescriptlang.org/docs/handbook/2/objects.html" rel="noopener noreferrer"&gt;TypeScript Handbook: Object Types&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html" rel="noopener noreferrer"&gt;TypeScript 4.4 release notes: exact optional property types&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://letinix.com/tools/json-to-typescript" rel="noopener noreferrer"&gt;Letinix JSON to TypeScript Converter&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://letinix.com/articles/build/typescript-optional-vs-nullable" rel="noopener noreferrer"&gt;Letinix&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
  </channel>
</rss>
