<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.2">Jekyll</generator><link href="https://jenil.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jenil.net/" rel="alternate" type="text/html" /><updated>2026-05-29T05:08:50+00:00</updated><id>https://jenil.net/feed.xml</id><title type="html">Home</title><subtitle>Exploring and Sharing my Journey Through Hacks, Discoveries, and Learnings.</subtitle><author><name>Jenil Shah</name></author><entry><title type="html">My frictionless workflow to publish blog posts</title><link href="https://jenil.net/p/my-frictionless-workflow-to-publish-blog/" rel="alternate" type="text/html" title="My frictionless workflow to publish blog posts" /><published>2026-05-28T00:00:00+00:00</published><updated>2026-05-28T00:00:00+00:00</updated><id>https://jenil.net/p/my-frictionless-workflow-to-publish-blog</id><content type="html" xml:base="https://jenil.net/p/my-frictionless-workflow-to-publish-blog/"><![CDATA[<p>I had ideas. Plenty of them. I just never published them.</p>

<p>The pattern was always the same. Something interesting would happen, I’d think “I should write about this,” and then I’d open a browser tab, navigate to my CMS, stare at an empty editor, and close it twenty minutes later with nothing to show for it. Not because writing is hard. Because the setup before the writing was just enough friction to kill the momentum.</p>

<p>My blog runs on Jekyll. Publishing meant creating a markdown file with the right frontmatter, running Jekyll locally to check how it looked, fixing whatever was off, and pushing to GitLab. Each step is trivial on its own. Together, they added up to a ritual I kept skipping.</p>

<p>I run a personal AI assistant on a VPS, basically a remote Claude Code session I can connect to from any device. A few weeks ago I decided to treat blog publishing as a first-class workflow inside that environment rather than something I’d do separately.</p>

<p>The result is three commands.</p>

<p><code class="language-plaintext highlighter-rouge">/blog-draft</code> is where it starts. I describe what I want to write, or say “blog this conversation,” and Claude drafts the full post, saves it to my Jekyll drafts folder, and tells me the file path. The post exists on disk in under a minute.</p>

<p><code class="language-plaintext highlighter-rouge">/blog-preview</code> is the one that actually changed my behavior. It spins up Jekyll locally with the <code class="language-plaintext highlighter-rouge">--drafts</code> flag, then opens a Cloudflare Tunnel that gives me a public URL. No port forwarding, no ngrok setup. I can open that link on my phone and see exactly how the post will look to a reader before anything is committed. Jekyll’s watch mode means edits I make rebuild automatically, so I can tell Claude to tighten a paragraph, refresh the browser, and see the change immediately.</p>

<p><code class="language-plaintext highlighter-rouge">/blog-publish</code> moves the draft to <code class="language-plaintext highlighter-rouge">_posts/</code>, commits, and pushes to GitLab. Cloudflare Pages picks it up and the post is live within a minute.</p>

<p>What surprised me was how much the preview step mattered. I used to skip previewing entirely and just publish, which meant occasionally shipping posts that looked wrong on mobile. Now I read every post at the preview URL the way a visitor would, on an actual device, before it goes anywhere. That one change has made me more confident about hitting publish.</p>

<p>The workflow is not magic. It is just friction removal applied in the right places. The ideas were always there. They needed a shorter path to the page.</p>

<p>What tasks do you keep putting off not because they’re hard, but because the setup before them is annoying enough to make you stop?</p>]]></content><author><name>Jenil Shah</name></author><summary type="html"><![CDATA[I had ideas. I just never published them. So I built a workflow that gets out of the way.]]></summary></entry><entry><title type="html">Comparing Real-Time US Stock Options Data Providers - Which API is Right for You?</title><link href="https://jenil.net/p/compare-options-api/" rel="alternate" type="text/html" title="Comparing Real-Time US Stock Options Data Providers - Which API is Right for You?" /><published>2024-07-02T00:00:00+00:00</published><updated>2024-07-02T00:00:00+00:00</updated><id>https://jenil.net/p/compare-options-api</id><content type="html" xml:base="https://jenil.net/p/compare-options-api/"><![CDATA[<p>I am a hobbyist. I like to tinker with finacial instruments. And options are facinating instrument vehicles. Options provides leverage for your investments.</p>

<p>Exploratory space for options is vast. Taking variables of call, put, strike prices and expiry dates, there are more than 1k+ option contracts for each underlying asset. This results in explosion of exploratory space if you consider all the option contracts available in market. At any given point of time, there are more than 100k+ live option contracts.</p>

<p>What better way to explore and scan this space than using custom code? There are multiple ways to get insights or devise your algorithmic startegy or backtest your strategy for option contracts. You can use snasphots for backtesting or getting insights. However, in this post, we will focus on real time or near real time option data. We start with analysis of data provider for real time options for hobbyists. This blog isnt focused on data provider APIs for instituions or enterprises, but focuses more on hobbyists and hackers who are on limited budget.</p>

<p>Before we start to analyze different data providers, let me setup provide context on requirements through which lens I will analyze these data providers. These requirements are based on one of the option strategy I experimented with.</p>

<p><strong>Requirements lens</strong></p>

<p><em>Freshness:</em> 
Realtime or Near-realtime options data. I consider 15 mins delay as near-real time data.</p>

<p><em>Blanket API for Option chain</em>
I should be able to get all option contracts of underlying ticker with a single API call. The API shouldnt restrict me to only get option contracts of a ticker for single expiry.</p>

<p><em>[Nice to have] Websocket feed for Option contracts</em>
For streaming information, getting live feed is preferred. Its fragile architecture to call option chain API every 5 mins and do processing. In longer run, I would like to use streaming feed for option chain.</p>

<p><em>Price</em>
This will be deal breaker. I am hobbyist. I will be more conservative and say maximum I am willing to pay for Options API is $50 per month. Tbh, once you put your strategy in production, $50 is dirt cheap.</p>

<p><em>Backtesting</em>
How would you gain confidence in startegy, if you cannot backtest? There must be support of historical option chain API to help backtest</p>

<p><em>Option Entities as part of API call</em>
    1. Option ticker    <br />
    2. Expiry
    3. Call or Put<br />
    4. OHLC
    5. Last transaction price 
    6. Strike price
    7. Greeks
    8. IV
    9. Underlying instrument price
    10. Open Interest and Volume</p>

<p><em>Ease of implementation</em>
I would classify this as Low/Medium/High</p>

<p>Getting all these requirements for $50 is hard find. We will explore some data providers, and try to maintain balance between API features and requirements.</p>

<h3 id="data-providers-comparision">Data providers comparision:</h3>

<ol>
  <li><a href="https://www.thetadata.net/"><strong>Thetadata</strong></a></li>
</ol>

<table>
  <thead>
    <tr>
      <th>Requirement</th>
      <th>Data Provider</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Freshness</td>
      <td>Realtime</td>
    </tr>
    <tr>
      <td>Blanket API for Option chain</td>
      <td>Separate API call for Options chain and Greeks</td>
    </tr>
    <tr>
      <td>Websocket feed for Option contracts</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Price</td>
      <td>$80 for standard subscription</td>
    </tr>
    <tr>
      <td>Historical data</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Option Entities as part of API call</td>
      <td>Everything except second order Greeks</td>
    </tr>
    <tr>
      <td>Ease of implementation</td>
      <td>Hard</td>
    </tr>
  </tbody>
</table>

<h4 id="notes">Notes</h4>
<ul>
  <li>Thetadata is more suited for algo traders focusing on volatility and requiring millisecond updates.</li>
  <li>Thetadata requires to setup <a href="https://http-docs.thetadata.us/docs/theta-data-rest-api-v2/4g9ms9h4009k0-getting-started">Theta Termina</a> which is constantly running. I havent looked into how it works with details but that could add more complexity</li>
</ul>

<ol>
  <li><a href="https://www.marketdata.app/"><strong>MarketData</strong></a></li>
</ol>

<table>
  <thead>
    <tr>
      <th>Requirement</th>
      <th>Data Provider</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Freshness</td>
      <td>Realtime</td>
    </tr>
    <tr>
      <td>Blanket API for Option chain</td>
      <td><a href="https://www.marketdata.app/docs/api/options/chain">Single call</a></td>
    </tr>
    <tr>
      <td>Websocket feed for Option contracts</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Price</td>
      <td>$30/month for annual subscription<br />$100/month for monthly subscriotion</td>
    </tr>
    <tr>
      <td>Historical</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Option Entities as part of API call</td>
      <td>Everything</td>
    </tr>
    <tr>
      <td>Ease of implementation</td>
      <td>Easy</td>
    </tr>
  </tbody>
</table>

<p>Some of the Marketdata features:</p>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Paid vs Trial plans</td>
      <td>Marketdata provides ability to have try out plans without paying for 30 days. <br />Some features differ between Paid and Trial plans</td>
    </tr>
    <tr>
      <td>Standard vs Premium APIs</td>
      <td>Premium APIs and featrues can only be used in paid plans. <br />Standard APIs can be used in Trail plans</td>
    </tr>
    <tr>
      <td>Premium APIs</td>
      <td>Earnings api<br />Live vs Cached feed feature</td>
    </tr>
    <tr>
      <td>Cached feed feature</td>
      <td>Cached feed allows to get data from cache. Not guaranteed that its going to be realtime. <br />Delay could be between seconds to days. <br />Advantage is it will only charge 1 credit for responses that return multiple quotes, like option chains. That would be huge cost savings</td>
    </tr>
    <tr>
      <td>SDKs</td>
      <td>Developed Go SDK. Python SDK in development</td>
    </tr>
    <tr>
      <td>Option chain filters</td>
      <td><a href="https://www.marketdata.app/docs/api/options/chain#request-parameters">Multiple filters</a></td>
    </tr>
  </tbody>
</table>

<h4 id="notes-1">Notes</h4>

<ul>
  <li>Their credit based pricing model isnt much favorable for option chains. Everytime one ticker(option contract) is included in the response, its considered one credit. Lets say, we get an option chain with 40 option contracts data. This will be considered as usage of 40 credits. Credits can be exhausted really quick in this manner.
    <ul>
      <li>There is an option to retrieve cached feed vs live feed. If you use cached feed, you are only charged 1 credit per response irrespective of number of quotes returned. Using cached feed is a premium feature.</li>
    </ul>
  </li>
</ul>

<h4 id="playing-around-with-option-chain-api">Playing around with Option Chain API</h4>

<ul>
  <li>Option Chain API only support a single expiration date per call. It cannot support multiple expiration date. ie. The filters need to resolve to single expiration date</li>
  <li>I am running the code on 07/04/2024. If no paramater is specified, it returns option chain on 07/19. Not sure, why?</li>
  <li>If I set “weekly” to “true”, it returns option chain of immediate week. This makes sense. If I set “weekly” to “false”, it returns option chain on 07/19. Why is it not returning 07/12 option chain?</li>
  <li>Setting “monthly” to “true”, doesnt return monthly option chain. It still returns 07/19 option chain for some reason.</li>
  <li>For my usecase, I should call the expiration endpoint first. Take top x dates and call option chain endpoint simultaneously.</li>
</ul>]]></content><author><name>Jenil Shah</name></author><summary type="html"><![CDATA[I am a hobbyist. I like to tinker with finacial instruments. And options are facinating instrument vehicles. Options provides leverage for your investments.]]></summary></entry><entry><title type="html">Microsoft Todo Graph API bug and its cascading effects</title><link href="https://jenil.net/p/ms-todo-api-bug/" rel="alternate" type="text/html" title="Microsoft Todo Graph API bug and its cascading effects" /><published>2024-02-24T00:00:00+00:00</published><updated>2024-02-24T00:00:00+00:00</updated><id>https://jenil.net/p/ms-todo-api-bug</id><content type="html" xml:base="https://jenil.net/p/ms-todo-api-bug/"><![CDATA[<p>For those familiar with <a href="https://en.wikipedia.org/wiki/Wunderlist">Wunderlist</a>, it played an essential role in my approach to managing tasks. Following its acquisition by Microsoft, I smoothly shifted to using Microsoft Todo. This transition was straightforward. Although Microsoft removed some functionalities after rebranding Wunderlist as MS Todo, they introduced the “My Day” feature, which enhanced the user experience with its intuitive design.</p>

<p>When you put MS Todo side by side with other task management applications like Todoist or TickTick, it’s clear that MS Todo falls short in terms of features. Despite this, I’ve continued to use MS Todo for its simplicity. Over time, I’ve entered over 6000 tasks into the Microsoft Todo app. Moreover, the effort and time required to adapt to a new task management app are substantial, and in my view, the cost of switching and habit formation doesn’t justify the benefits. However, it appears that Microsoft’s focus isn’t so much on developing MS Todo as a standalone application but rather on enhancing its integration within the broader Microsoft 365 ecosystem.</p>

<p>In my automation workflows, I frequently utilize <a href="https://ifttt.com/">IFTTT</a> and <a href="https://zapier.com/">Zapier</a> to capture tasks into the MS Todo Inbox. For several months, I’ve been dealing with a persistent bug in MS Todo that seems to have gone unnoticed by many MS Todo users and their integration partners. This issue came to my attention while setting up an IFTTT Applet and using “Create task in task list” MS Todo action. Despite having more than 20 task lists, it only displays the “Flagged email” task list for me. At first, I suspected the problem was with IFTTT, so I contacted their support team. However, after a month of back-and-forth communication, we still didnt reach conclusion.</p>

<p><img src="/images/image-assets/flagged-emails.jpg" alt="" /></p>

<p>I noticed similar issues with Zapier and when using the <a href="https://github.com/johandebeurs/alfred-mstodo-workflow">MS Todo Alfred workflow</a>. This made it crystal clear that the problem lies with the Microsoft Graph APIs themselves, rather than with their client-side implementations.</p>

<p>My curiosity piqued and I started going through their <a href="https://learn.microsoft.com/en-us/graph/api/resources/todo-overview?view=graph-rest-1.0">API documentation</a>. It appears that Microsoft offers two similar APIs for retrieving all Task lists: <a href="https://learn.microsoft.com/en-us/graph/api/todo-list-lists?view=graph-rest-1.0&amp;tabs=http">List task lists</a> and <a href="https://learn.microsoft.com/en-us/graph/api/todotasklist-delta?view=graph-rest-1.0&amp;tabs=http">Get task list delta</a>. The former appears to be more definitive, while the latter is preferred for clients that wish to stay synchronized with the server to enhance performance. To examine the API responses, I utilized the <a href="https://developer.microsoft.com/en-us/graph/graph-explorer">Microsoft Graph Explorer</a> to make calls to both APIs. Below are my findings:</p>

<p><strong>List task lists API</strong></p>

<p><em>Request</em></p>

<p><code class="language-plaintext highlighter-rouge">https://graph.microsoft.com/v1.0/me/todo/lists/</code></p>

<p><em>Response</em></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="s">"value"</span><span class="p">:</span> <span class="p">[</span>

		<span class="p">{</span>
		<span class="s">"@odata.etag"</span><span class="p">:</span> <span class="s">"dummyTag"</span><span class="p">,</span>
		<span class="s">"displayName"</span><span class="p">:</span> <span class="s">"Flagged Emails"</span><span class="p">,</span>
		<span class="s">"isOwner"</span><span class="p">:</span> <span class="n">true</span><span class="p">,</span>
		<span class="s">"isShared"</span><span class="p">:</span> <span class="n">false</span><span class="p">,</span>
		<span class="s">"wellknownListName"</span><span class="p">:</span> <span class="s">"flaggedEmails"</span><span class="p">,</span>
		<span class="s">"id"</span><span class="p">:</span> <span class="s">"dummyID"</span>
		<span class="p">}</span>
	<span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Even though I have 20+ lists in Microsoft Todo, it only gets me “Flagged Emails list”. This is a <strong>bug</strong></p>

<p><strong>Get Task Lists API</strong></p>

<p><em>Request</em>
<code class="language-plaintext highlighter-rouge">https://graph.microsoft.com/v1.0/me/todo/lists/microsoft.graph.delta()</code></p>

<p><em>Response</em></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="p">{</span>
	<span class="s">"displayName"</span><span class="p">:</span> <span class="s">"Flagged Emails"</span><span class="p">,</span>
	<span class="s">"isOwner"</span><span class="p">:</span> <span class="n">true</span><span class="p">,</span>
	<span class="s">"isShared"</span><span class="p">:</span> <span class="n">false</span><span class="p">,</span>
	<span class="s">"wellknownListName"</span><span class="p">:</span> <span class="s">"flaggedEmails"</span><span class="p">,</span>
	<span class="s">"id"</span><span class="p">:</span> <span class="s">"dummy"</span>
<span class="p">},</span>

<span class="p">{</span>
	<span class="s">"displayName"</span><span class="p">:</span> <span class="s">"Tasks"</span><span class="p">,</span>
	<span class="s">"isOwner"</span><span class="p">:</span> <span class="n">true</span><span class="p">,</span>
	<span class="s">"isShared"</span><span class="p">:</span> <span class="n">false</span><span class="p">,</span>
	<span class="s">"wellknownListName"</span><span class="p">:</span> <span class="s">"defaultList"</span><span class="p">,</span>
	<span class="s">"id"</span><span class="p">:</span> <span class="s">"dummy"</span>
<span class="p">},</span>

<span class="p">{</span>
	<span class="s">"displayName"</span><span class="p">:</span> <span class="s">"List A"</span><span class="p">,</span>
	<span class="s">"isOwner"</span><span class="p">:</span> <span class="n">true</span><span class="p">,</span>
	<span class="s">"isShared"</span><span class="p">:</span> <span class="n">false</span><span class="p">,</span>
	<span class="s">"wellknownListName"</span><span class="p">:</span> <span class="s">"none"</span><span class="p">,</span>
	<span class="s">"id"</span><span class="p">:</span> <span class="s">"dummy"</span>
<span class="p">},</span>

<span class="p">{</span>
	<span class="s">"displayName"</span><span class="p">:</span> <span class="s">"List B"</span><span class="p">,</span>
	<span class="s">"isOwner"</span><span class="p">:</span> <span class="n">true</span><span class="p">,</span>
	<span class="s">"isShared"</span><span class="p">:</span> <span class="n">false</span><span class="p">,</span>
	<span class="s">"wellknownListName"</span><span class="p">:</span> <span class="s">"none"</span><span class="p">,</span>
	<span class="s">"id"</span><span class="p">:</span> <span class="s">"dummy"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This correctly displays all my lists in MS Todo. I’m uncertain whether the Delta API was recently introduced or has been available since the beginning. However, it’s clear that the initial requests from the <code class="language-plaintext highlighter-rouge">list taskLists</code> API and the <code class="language-plaintext highlighter-rouge">list taskLists delta</code> API follow different patterns.</p>

<p>I found only a single mention of this problem online. Microsoft has yet to notice the issue, which negatively impacts the user experience for some customers. One reason this problem might have remained under the radar is that it primarily affects power users who have multiple task lists, numerous tasks, or have been using MS Todo for an extended period. Regular customers with minimal use of MS Todo are not affected by this issue.</p>

<p>Microsoft doesnt expose easy way to submit bug reports for Microsoft Graph API. However, I have submitted feedback <a href="https://feedbackportal.microsoft.com/feedback/idea/0cf859ac-f8d3-ee11-92bd-000d3a7a5824">here</a> in Microsoft Graph portal.</p>

<h3 id="cascading-effect">Cascading effect</h3>

<p>Incorporating abstractions undoubtedly offers benefits in terms of speed and convenience, yet it also introduces costs related to maintenance and traceability. Within a web of API integrations, a single bug at the system’s core can ripple through to all its clients and, ultimately, impact the business. Furthermore, the cost of diagnosing errors becomes significant when integration partners are in the dark about the root cause of an issue. As a customer, my instinct would be to report a problem to IFTTT, never suspecting that the real issue lies within the Microsoft API itself. When a minor bug occurs in a tool designed for further abstraction, it’s like tossing a pebble into a pond: the resulting ripples extend broadly, influencing not just an individual user but the entire network of those connected to the system.</p>

<p>This makes me wonder and appreciate how automation tools(eg. IFTTT, Zapier, Make, Tray.io) runs their operations while maintaining more than 5k+ API integrations accross more than 800+ different SaaS services.</p>]]></content><author><name>Jenil Shah</name></author><summary type="html"><![CDATA[For those familiar with Wunderlist, it played an essential role in my approach to managing tasks. Following its acquisition by Microsoft, I smoothly shifted to using Microsoft Todo. This transition was straightforward. Although Microsoft removed some functionalities after rebranding Wunderlist as MS Todo, they introduced the “My Day” feature, which enhanced the user experience with its intuitive design.]]></summary></entry></feed>