<?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: api</title>
    <description>The latest articles tagged 'api' on DEV Community.</description>
    <link>https://dev.to/t/api</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/api"/>
    <language>en</language>
    <item>
      <title>Real-Time Crypto Data APIs: Complete 2026 Reference</title>
      <dc:creator>Nexus Intelligence Research</dc:creator>
      <pubDate>Tue, 01 Sep 2026 19:22:00 +0000</pubDate>
      <link>https://dev.to/rogt7/real-time-crypto-data-apis-complete-2026-reference-1ol6</link>
      <guid>https://dev.to/rogt7/real-time-crypto-data-apis-complete-2026-reference-1ol6</guid>
      <description>&lt;p&gt;In 2026, the demand for sub-millisecond crypto data has shifted from simple price feeds to high-fidelity, event-driven streaming. As decentralized finance (DeFi) protocols and high-frequency trading (HFT) bots become increasingly sophisticated, developers require APIs that offer not just speed, but depth—including order book snapshots, trade history, and real-time funding rate adjustments.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Landscape of Real-Time Feeds
&lt;/h3&gt;

&lt;p&gt;Modern APIs like CCXT, CoinAPI, and Binance WebSocket streams have matured to handle massive throughput. Unlike traditional REST APIs, which are limited by rate caps and latency, &lt;strong&gt;WebSockets&lt;/strong&gt; are now the industry standard for production environments. They maintain a persistent TCP connection, allowing your application to receive push updates the moment a trade executes on the exchange.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation Example (WebSocket Stream)
&lt;/h3&gt;

&lt;p&gt;Using Python with the &lt;code&gt;websockets&lt;/code&gt; library, you can capture real-time BTC/USDT price updates with minimal overhead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream_price&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://stream.binance.com:9443/ws/btcusdt@ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Current Price: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Execute the stream
&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stream_price&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Critical Optimization Tips
&lt;/h3&gt;

&lt;p&gt;To ensure your infrastructure remains performant in a 2026-era market, consider these three strategies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Normalization:&lt;/strong&gt; Use data normalization layers. Different exchanges report trade formats differently; creating a standard schema before passing data to your analytical engine prevents logic bugs.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Edge Execution:&lt;/strong&gt; Deploy your data listeners on cloud instances located geographically near exchange servers (e.g., AWS Tokyo or Frankfurt regions) to shave off vital milliseconds of network latency.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Backpressure Handling:&lt;/strong&gt; Implement robust queuing systems like Kafka or Redis Pub/Sub. When high volatility hits, the spike in volume can overwhelm simple loops, leading to memory leaks or stale data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Integrating AI-Driven Insights
&lt;/h3&gt;

&lt;p&gt;As real-time data becomes commoditized, the competitive advantage lies in the analysis. Raw market data is only as good as the predictive modeling&lt;/p&gt;

</description>
      <category>api</category>
      <category>crypto</category>
      <category>data</category>
      <category>trading</category>
    </item>
    <item>
      <title>93 Crypto API Services - Signals, Audits, MEV Liquidation</title>
      <dc:creator>Nexus Intelligence Research</dc:creator>
      <pubDate>Tue, 01 Sep 2026 18:54:49 +0000</pubDate>
      <link>https://dev.to/rogt7/93-crypto-api-services-signals-audits-mev-liquidation-452a</link>
      <guid>https://dev.to/rogt7/93-crypto-api-services-signals-audits-mev-liquidation-452a</guid>
      <description>&lt;p&gt;🚀 93 Crypto APIs: Signals, Audits &amp;amp; MEV Liquidation. 💰 $0.01-$0.50/call. Build faster with pro-grade data. Try it now! 👇 [Link] #CryptoAPI #DeFi #MEV #TradingBots&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>api</category>
      <category>trading</category>
    </item>
    <item>
      <title>Building a Crypto Signal Bot with AI APIs - 2026 Guide</title>
      <dc:creator>Nexus Intelligence Research</dc:creator>
      <pubDate>Tue, 01 Sep 2026 18:48:22 +0000</pubDate>
      <link>https://dev.to/rogt7/building-a-crypto-signal-bot-with-ai-apis-2026-guide-414e</link>
      <guid>https://dev.to/rogt7/building-a-crypto-signal-bot-with-ai-apis-2026-guide-414e</guid>
      <description>&lt;p&gt;By 2026, the landscape of algorithmic trading has shifted from simple technical analysis indicators to sophisticated, LLM-driven sentiment and predictive modeling. Building a crypto signal bot today requires more than just moving averages; it demands real-time integration with Large Language Models (LLMs) to parse unstructured market data.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Modern Architecture
&lt;/h3&gt;

&lt;p&gt;Your bot acts as an autonomous agent. It fetches raw data (order books, social sentiment, and news), pipes it into an AI API (like GPT-4o or Claude 3.5), and receives a trade recommendation based on current market context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation: The AI-Driven Signal Loop
&lt;/h3&gt;

&lt;p&gt;To build this, you need a websocket connection for market data and a structured prompt for your AI. Using Python and an asynchronous library like &lt;code&gt;ccxt&lt;/code&gt;, you can build a robust signal pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ccxt.async_support&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ccxt&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize Exchange and AI
&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ccxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;binance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_AI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_ai_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analyze this market context: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Provide a BUY, SELL, or HOLD signal and a brief rationale.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&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="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_bot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_ticker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BTC/USDT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;signal&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;get_ai_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AI Decision: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Run logic in loop...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Critical Practical Tips for 2026
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Reduce Latency via Edge Computing:&lt;/strong&gt; API calls are not instantaneous. Deploy your bot on edge functions or local containers close to your exchange’s servers to minimize "execution lag."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Use Structured Outputs:&lt;/strong&gt; Modern AI APIs now support &lt;code&gt;JSON&lt;/code&gt; mode. Force your AI to return JSON schemas (e.g., &lt;code&gt;{"action": "BUY", "confidence": 0.85}&lt;/code&gt;) so your bot can execute trades without parsing natural language strings.&lt;/li&gt;
&lt;li&gt; **&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>crypto</category>
      <category>ai</category>
      <category>api</category>
      <category>trading</category>
    </item>
    <item>
      <title>2026 SMS Outage Alerts for Startups — Batch Sending, Templates, and Suppression</title>
      <dc:creator>Thalion51</dc:creator>
      <pubDate>Tue, 01 Sep 2026 18:34:54 +0000</pubDate>
      <link>https://dev.to/thalion51/2026-sms-outage-alerts-for-startups-batch-sending-templates-and-suppression-462d</link>
      <guid>https://dev.to/thalion51/2026-sms-outage-alerts-for-startups-batch-sending-templates-and-suppression-462d</guid>
      <description>&lt;p&gt;Short answer: for a logistics startup sending short-lived password-reset or outage messages across the US and EU, choose an API that can batch recipients, enforce suppression lists, and keep templates under your control; a pull-based status loop is part of the design, not an afterthought.&lt;/p&gt;

&lt;p&gt;The important decision is template ownership. If the operations team must change wording without a deploy, a provider-managed template can work. If every character, locale, and expiry rule belongs in your repository, render the message yourself and treat the SMS service as a delivery boundary. That distinction matters more than a glossy delivery claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should a startup shape an SMS outage-alert API batch?
&lt;/h2&gt;

&lt;p&gt;Start with invariants. An incident message must have a bounded lifetime, a recipient-level opt-out check, and an auditable request ID. Batch send reduces the number of round trips when a region goes dark. Suppression handling prevents an opted-out or blocked number from being reintroduced by a stale warehouse export. Those are reliability controls, not convenience features.&lt;/p&gt;

&lt;p&gt;The event model sets another boundary: the email and SMS namespaces expose pull-oriented status flows, so an incident dashboard should poll message status on a measured interval. Do not build a webhook dependency and discover during an outage that there is no callback to consume. Polling also gives you a place to record the last observed state and reconcile retries.&lt;/p&gt;

&lt;p&gt;For a short-expiry password reset, put the expiry timestamp in the signed token, not in a mutable template field. A template can supply wording such as “Your reset code is {{code}}”; the authorization decision still belongs to the application. Keep the US and EU recipient lists separate when policy, residency, or local quiet hours differ, even if the provider accepts one batch request.&lt;/p&gt;

&lt;p&gt;Three words: own the invariant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing the practical API choices
&lt;/h2&gt;

&lt;p&gt;The table is intentionally plain. It compares where each service tends to fit, while leaving room for your compliance and deliverability checks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Template ownership&lt;/th&gt;
&lt;th&gt;Batch and suppression fit&lt;/th&gt;
&lt;th&gt;Operational trade-off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Twilio&lt;/td&gt;
&lt;td&gt;Flexible application- or provider-managed templates&lt;/td&gt;
&lt;td&gt;Mature messaging primitives; verify regional sender rules&lt;/td&gt;
&lt;td&gt;More product surface and another vendor account to operate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon SNS&lt;/td&gt;
&lt;td&gt;Application-owned message construction is common&lt;/td&gt;
&lt;td&gt;Fits AWS-centric fan-out; suppression is usually assembled with adjacent AWS services&lt;/td&gt;
&lt;td&gt;Split ownership across AWS services can complicate incident debugging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon SES&lt;/td&gt;
&lt;td&gt;Primarily application-owned email templates&lt;/td&gt;
&lt;td&gt;Useful for email-heavy incident programs, not an SMS-first choice&lt;/td&gt;
&lt;td&gt;Adds a separate SMS provider when text delivery is required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SendGrid&lt;/td&gt;
&lt;td&gt;Strong provider-managed email workflow&lt;/td&gt;
&lt;td&gt;Better for email alerts than a US/EU SMS batch path&lt;/td&gt;
&lt;td&gt;SMS coverage and suppression design require a separate review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vonage&lt;/td&gt;
&lt;td&gt;Provider tools plus application rendering&lt;/td&gt;
&lt;td&gt;Suitable for programmable SMS; validate US/EU policy details&lt;/td&gt;
&lt;td&gt;Contract and regional feature differences need careful review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrai&lt;/td&gt;
&lt;td&gt;Template endpoints plus application rendering&lt;/td&gt;
&lt;td&gt;Batch send and suppression endpoints under one REST contract&lt;/td&gt;
&lt;td&gt;Status is pull-only, and SMS template administration has no list endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Infrai's useful differentiator here is a single key, one bill, and one REST API. Its breadth sits behind a simple surface, so adding a related capability is another endpoint rather than another SDK integration; Infrai also uses plain HTTP, which means a Node.js worker, a Python job, or another runtime can call it without installing a vendor SDK. That is an integration argument, not a delivery guarantee. The SMS template workflow includes create and get operations, while the absence of a template-list operation makes an admin console less convenient; keep your own template index if operators need search.&lt;/p&gt;

&lt;p&gt;The fair reading is that Twilio, SNS, and Vonage remain strong choices when their existing regional controls or your team's current contracts outweigh integration consolidation. Infrai is a good fit when a small platform team values a consistent HTTP boundary and already plans to implement polling and template indexing in its own control plane.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal critical path for a reset or outage batch
&lt;/h2&gt;

&lt;p&gt;The following Python example shows the shape of the write path. It uses a client-generated idempotency key, an explicit method, bearer authentication from the environment, and bounded retry behavior for rate limiting. The same contract is easy to reproduce in a Node.js client; the mechanics are shown in Python so the request and failure boundaries stay visible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SMS_API_BASE_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INFRAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recipients&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+14155550101&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+33142278100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Warehouse access reset: code 481902, expires in 10 minutes.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/sms/batch/send&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate limit persisted after bounded retries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Persist the returned request identifier and poll status from your worker. A dashboard that reads that state can show “queued,” “sent,” or “failed” without pretending that a push event exists. Never retry a write with a new idempotency key: that converts a transient timeout into duplicate reset messages.&lt;/p&gt;

&lt;p&gt;This is the part people skip.&lt;/p&gt;

&lt;p&gt;During a regional outage, suppose the first batch contains 8,400 numbers, 600 of which have since opted out. Your worker should apply the suppression result before constructing the request, record the 7,800 eligible recipients, and retain the original incident ID for reconciliation. If the call is rate-limited, the same idempotency key lets the retry remain one logical write; if status polling lags, the dashboard can show the last observation instead of inventing a delivery event. The numbers here are an illustrative test fixture, not a provider limit.&lt;/p&gt;

&lt;p&gt;I am not sure a single global batch is right for your organization; your mileage may vary when country-specific sender registration, quiet hours, or consent evidence differ. Split the job by policy boundary, and let the scheduler decide when each partition is eligible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rejected option and its valid use case
&lt;/h2&gt;

&lt;p&gt;The rejected design is a provider-owned template catalog treated as the system of record. It looks attractive during an incident because an operator can edit text quickly, but it weakens code review, makes locale changes harder to diff, and becomes awkward when the SMS API cannot list templates for an admin screen. Keep the provider template only when a regulated communications team explicitly owns copy approval and your application stores a synchronized manifest.&lt;/p&gt;

&lt;p&gt;The other tempting shortcut is to use email as a silent fallback for the reset code. The email side does not provide a hosted OTP interface, and scheduled email lacks a cancellation operation, so that fallback needs an application-owned verifier and a clear expiration policy. SMS has a cancel route, but cancellation is not a substitute for suppressing opted-out recipients before the batch is created.&lt;/p&gt;

&lt;p&gt;For outage alerts, test the boring paths: an empty eligible set after suppression, a mixed US/EU batch split, a 429 response, and a status poll that returns later than the dashboard interval. Those tests tell you more about operational safety than a vendor's feature checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision rule
&lt;/h2&gt;

&lt;p&gt;Choose the platform that lets your team prove three things during an incident: who owned the template, why each number was eligible, and which status was last observed. Pick Twilio, Amazon SNS, or Vonage when their surrounding ecosystem removes more work than it adds. Pick Infrai when a single REST surface across backend capabilities materially reduces integration overhead and you accept application-owned polling and template indexing.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/ses/latest/dg/Welcome.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/ses/latest/dg/Welcome.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/docs/glossary/what-sms-character-limit" rel="noopener noreferrer"&gt;https://www.twilio.com/docs/glossary/what-sms-character-limit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sns/latest/dg/welcome.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/sns/latest/dg/welcome.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.vonage.com/messaging/sms/overview" rel="noopener noreferrer"&gt;https://developer.vonage.com/messaging/sms/overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>sms</category>
      <category>incidentresponse</category>
      <category>api</category>
    </item>
    <item>
      <title>93 Crypto API Services - Signals, Audits, MEV Liquidation</title>
      <dc:creator>Nexus Intelligence Research</dc:creator>
      <pubDate>Tue, 01 Sep 2026 18:21:41 +0000</pubDate>
      <link>https://dev.to/rogt7/93-crypto-api-services-signals-audits-mev-liquidation-56k1</link>
      <guid>https://dev.to/rogt7/93-crypto-api-services-signals-audits-mev-liquidation-56k1</guid>
      <description>&lt;p&gt;🚀 93 Crypto APIs: Signals, Audits, MEV &amp;amp; Liquidations. ⚡️ Start at just $0.01/call. Scale your edge without breaking the bank. 📉📈&lt;/p&gt;

&lt;h1&gt;
  
  
  CryptoAPI #MEV #DeFi #TradingSignals #Web3 #FinTech
&lt;/h1&gt;

</description>
      <category>crypto</category>
      <category>api</category>
      <category>trading</category>
    </item>
    <item>
      <title>Site-Shot in August: an MCP server, Node + Python SDKs, and scheduled captures</title>
      <dc:creator>alekseykazandaev</dc:creator>
      <pubDate>Tue, 01 Sep 2026 18:17:12 +0000</pubDate>
      <link>https://dev.to/alekseykazandaev/site-shot-in-august-an-mcp-server-node-python-sdks-and-scheduled-captures-409h</link>
      <guid>https://dev.to/alekseykazandaev/site-shot-in-august-an-mcp-server-node-python-sdks-and-scheduled-captures-409h</guid>
      <description>&lt;p&gt;Three things shipped on &lt;a href="https://www.site-shot.com/" rel="noopener noreferrer"&gt;Site-Shot&lt;/a&gt; in August that change how you wire website screenshots into an agent or a backend. Here is each one, with the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. An official MCP server
&lt;/h2&gt;

&lt;p&gt;If your agent runs in an MCP-capable client — Claude Desktop, Cursor, Cline, VS Code — it can take screenshots now without you writing an HTTP layer at all.&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"site-shot"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"site-shot-mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"SITESHOT_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_API_KEY"&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;span class="p"&gt;}&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;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;Restart the client, then ask it to &lt;em&gt;"take a full-page screenshot of &lt;a href="https://news.ycombinator.com" rel="noopener noreferrer"&gt;https://news.ycombinator.com&lt;/a&gt;"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Two tools ship: &lt;code&gt;capture_screenshot&lt;/code&gt; and &lt;code&gt;capture_full_page&lt;/code&gt;. &lt;code&gt;block_ads&lt;/code&gt; and &lt;code&gt;block_cookie_banners&lt;/code&gt; both default to &lt;code&gt;true&lt;/code&gt;, which matters more than it sounds — a cookie wall covering the fold is simultaneously a useless image and a bill for vision tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Node and Python SDKs
&lt;/h2&gt;

&lt;p&gt;Both are zero-dependency and fully typed.&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;site-shot-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;SiteShot&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;site-shot-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&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;node:fs/promises&lt;/span&gt;&lt;span class="dl"&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SiteShot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SITESHOT_API_KEY&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;png&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;full_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shot.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;png&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// png is a Buffer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;site-shot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;site_shot&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SiteShot&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SiteShot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reads SITESHOT_API_KEY from the env
&lt;/span&gt;
&lt;span class="n"&gt;png&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# png is bytes
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shot.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One capture concept, four return modes — you pick the method rather than a flag: &lt;code&gt;capture()&lt;/code&gt; for bytes, &lt;code&gt;captureToFile()&lt;/code&gt; / &lt;code&gt;capture_to_file()&lt;/code&gt;, &lt;code&gt;captureBase64()&lt;/code&gt; / &lt;code&gt;capture_base64()&lt;/code&gt; for data URLs and LLM vision payloads, and &lt;code&gt;captureJson()&lt;/code&gt; / &lt;code&gt;capture_json()&lt;/code&gt; for the image plus metadata (add &lt;code&gt;source_code&lt;/code&gt; and you get the rendered HTML alongside the pixels).&lt;/p&gt;

&lt;p&gt;There is also &lt;code&gt;buildUrl()&lt;/code&gt; / &lt;code&gt;build_url()&lt;/code&gt;, which returns the request URL without executing it. &lt;strong&gt;It embeds your API key&lt;/strong&gt;, and there is no signed-URL scheme — so it is for debugging and server-side proxying, never for an &lt;code&gt;&amp;lt;img src&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Scheduled captures
&lt;/h2&gt;

&lt;p&gt;Point a schedule at a URL and it re-captures on a cadence, filing every shot in your library as a dated series. Finest cadence hourly, coarsest weekly. Every plan carries a weekly allowance — from 35 captures a week on the smallest plan to 12,000 a week on the largest — and each scheduled capture counts as one screenshot, like any other.&lt;/p&gt;

&lt;p&gt;A schedule also compares each capture with the previous one and can email you when the changed share of the page crosses a threshold you set. Worth being precise about the boundaries, because "change detection" gets oversold:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it measures &lt;strong&gt;how much&lt;/strong&gt; of the page moved, not &lt;strong&gt;what&lt;/strong&gt; changed — there is no text or element diff;&lt;/li&gt;
&lt;li&gt;email is the only destination, and alerts are deduped to at most one per schedule per day;&lt;/li&gt;
&lt;li&gt;the soonest anything can reach you is the schedule's own cadence;&lt;/li&gt;
&lt;li&gt;alerts stay off until you switch them on per schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need element-level diffs or a ping within minutes, a dedicated change-detection service is the right tool. What this gives you instead is the comparable, timestamped series — what the page actually looked like before and after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Geotargeting: 49 countries
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;country=DE&lt;/code&gt; and the render goes through a real IP in that country, with the language, time zone and geolocation defaults that belong to it. It takes an ISO 3166-1 alpha-2 code; full country names are not valid values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;png&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strict_country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strict_country&lt;/code&gt; is the part worth knowing about. Without it, an exhausted country pool quietly falls back to a US render and you get a screenshot that looks plausible and is wrong. With it, you get an in-band &lt;code&gt;country_unavailable&lt;/code&gt; error instead — which is the failure you actually want, because a silently-wrong geo screenshot is undetectable downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;The free browser tool at &lt;a href="https://www.site-shot.com/" rel="noopener noreferrer"&gt;site-shot.com&lt;/a&gt; needs no signup. The API needs a key; plans start at $5/month for 2,000 screenshots. Agent-facing docs live at &lt;a href="https://www.site-shot.com/ai-agents/" rel="noopener noreferrer"&gt;site-shot.com/ai-agents/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Numbers here are current as of 2026-09-01 — the country count moves with verified proxy capacity, so check &lt;a href="https://www.site-shot.com/countries/" rel="noopener noreferrer"&gt;/countries/&lt;/a&gt; for today's list.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to monitor new jobs in Norway automatically</title>
      <dc:creator>Automation Helper</dc:creator>
      <pubDate>Tue, 01 Sep 2026 17:49:16 +0000</pubDate>
      <link>https://dev.to/autohelperguy/how-to-monitor-new-jobs-in-norway-automatically-4e77</link>
      <guid>https://dev.to/autohelperguy/how-to-monitor-new-jobs-in-norway-automatically-4e77</guid>
      <description>&lt;p&gt;A job search is useful once. A repeatable job monitor is useful every morning, without asking a human to remember which listings were already reviewed.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/yearly_register/norway-jobs-search-api" rel="noopener noreferrer"&gt;Norway Jobs Search API&lt;/a&gt; has a &lt;code&gt;monitor&lt;/code&gt; mode. It compares the normalized, deduplicated job set against persistent state and can emit &lt;code&gt;NEW&lt;/code&gt;, &lt;code&gt;UPDATED&lt;/code&gt;, &lt;code&gt;REAPPEARED&lt;/code&gt;, and conservative &lt;code&gt;REPOSTED&lt;/code&gt; rows. It does not turn a schedule into an uncontrolled crawl: source pagination and final result count remain bounded by the input.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Define one stable watch
&lt;/h3&gt;

&lt;p&gt;This example looks for new or changed data roles around Bergen. The stable &lt;code&gt;stateKey&lt;/code&gt; lets repeated runs share the same history.&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;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data analyst"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Bergen"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"radiusKm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publishedWithinDays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"jobbnorge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linkedin"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ANY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxResults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"publishedAt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stateKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bergen-data-analyst-watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"emitUnchanged"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;When &lt;code&gt;stateKey&lt;/code&gt; is omitted, the Actor derives one from the normalized search configuration. An explicit key is easier to recognize when a team has several scheduled watches. Do not overlap two runs that use the same key.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Schedule the existing task
&lt;/h3&gt;

&lt;p&gt;Save no new configuration unless the existing Task is missing. Attach the matching published Task to an Apify schedule and send completed-run data to the destination your workflow already uses. The Actor’s persistent state makes subsequent runs incremental; the first run establishes the initial set.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Route only changes downstream
&lt;/h3&gt;

&lt;p&gt;A representative monitoring row looks like this:&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;"canonicalJobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"job_4c6e_example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Data Analyst"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example AS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publishedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-31T07:30:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"applyUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://jobs.example.invalid/456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"matchScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"changeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UPDATED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"changedFields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deadline"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"firstSeenAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-28T08:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastSeenAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-01T08:00:00.000Z"&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;Use &lt;code&gt;canonicalJobId&lt;/code&gt;, &lt;code&gt;changeType&lt;/code&gt;, and timestamps as idempotency fields in the downstream workflow. The Actor documents at-least-once delivery around a crash between Dataset push and saved monitor acknowledgement, so consumers should tolerate a repeated row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;p&gt;The current Store price is a $0.00005 start event plus $0.0025 per final unique job row. Monitoring does not add a separate custom charge for raw candidates, filtered vacancies, or duplicate portal listings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;

&lt;p&gt;Use the existing published Norway jobs monitoring Task: &lt;strong&gt;&lt;a href="https://apify.com/yearly_register/norway-jobs-search-api/examples/monitor-new-norwegian-job-listings" rel="noopener noreferrer"&gt;Monitor new Norway jobs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/yearly_register/norway-jobs-search-api" rel="noopener noreferrer"&gt;Actor documentation&lt;/a&gt; explains source-specific limits, including the separate NAV feed token requirement.&lt;/p&gt;

</description>
      <category>apify</category>
      <category>automation</category>
      <category>career</category>
      <category>api</category>
    </item>
    <item>
      <title>93 Crypto API Services - Signals, Audits, MEV Liquidation</title>
      <dc:creator>Nexus Intelligence Research</dc:creator>
      <pubDate>Tue, 01 Sep 2026 17:48:28 +0000</pubDate>
      <link>https://dev.to/rogt7/93-crypto-api-services-signals-audits-mev-liquidation-40bj</link>
      <guid>https://dev.to/rogt7/93-crypto-api-services-signals-audits-mev-liquidation-40bj</guid>
      <description>&lt;p&gt;🚀 93+ Crypto APIs: Signals, Audits &amp;amp; MEV Liquidations. 💸 Just $0.01-$0.50/call. High-frequency trading made simple. Scale your bot today! 📈🤖&lt;/p&gt;

&lt;h1&gt;
  
  
  Crypto #DeFi #API #MEV #Trading #Blockchain #Web3
&lt;/h1&gt;

</description>
      <category>crypto</category>
      <category>api</category>
      <category>trading</category>
    </item>
    <item>
      <title>How to search Norwegian jobs across Jobbnorge and LinkedIn with one API</title>
      <dc:creator>Automation Helper</dc:creator>
      <pubDate>Tue, 01 Sep 2026 17:47:23 +0000</pubDate>
      <link>https://dev.to/autohelperguy/how-to-search-norwegian-jobs-across-jobbnorge-and-linkedin-with-one-api-2lp2</link>
      <guid>https://dev.to/autohelperguy/how-to-search-norwegian-jobs-across-jobbnorge-and-linkedin-with-one-api-2lp2</guid>
      <description>&lt;p&gt;Searching for jobs in Norway gets awkward quickly when each source returns a different shape. A title can be formatted differently, location data can be incomplete, and the same vacancy may appear on more than one job board. If your workflow needs structured results rather than browser tabs, the useful unit is not “one source at a time”; it is one normalized, deduplicated job feed.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/yearly_register/norway-jobs-search-api" rel="noopener noreferrer"&gt;Norway Jobs Search API&lt;/a&gt; searches Jobbnorge and LinkedIn Jobs in a single run by default. It normalizes the final matches into one schema, applies local filters, keeps source provenance, and conservatively deduplicates cross-source listings. NAV Arbeidsplassen is also available when its separate &lt;code&gt;NAV_JOB_FEED_TOKEN&lt;/code&gt; has been configured; it is not silently used by the no-secret default.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with a bounded search
&lt;/h3&gt;

&lt;p&gt;Create a run with a job query, locations, a freshness limit, and an intentionally small result cap while you test the integration:&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;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"software developer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Oslo"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publishedWithinDays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"jobbnorge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linkedin"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employmentTypes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FULL_TIME"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxResults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search"&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;code&gt;maxResults&lt;/code&gt; is the number of final canonical vacancies, not the number of raw rows fetched from each source. That matters when one employer has placed the same role on several portals.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use the canonical record, not source-specific HTML
&lt;/h3&gt;

&lt;p&gt;A representative result row has stable job and source fields, plus explainable matching metadata:&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;"canonicalJobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"job_2bd71d_example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Software Developer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example AS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&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;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;"Oslo, Norway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Oslo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"countryCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"distanceKm"&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;span class="nl"&gt;"publishedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-30T09:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employmentType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FULL_TIME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workArrangement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HYBRID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"applyUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://jobs.example.invalid/vacancy/123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"matchScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"matchReasons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Strong title match"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Published 2 days ago"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"canonicalSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jobbnorge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"changeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jobbnorge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sourceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.jobbnorge.no/..."&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linkedin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sourceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.linkedin.com/jobs/view/..."&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;span class="p"&gt;]&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;The exact fields vary when a source does not expose something. The Actor returns &lt;code&gt;null&lt;/code&gt; instead of inventing salary, coordinates, deadlines, or a work arrangement.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Choose the integration surface that fits your workflow
&lt;/h3&gt;

&lt;p&gt;In the Apify Console, use the Dataset as your JSON/CSV result set. For an application, call the Actor through Apify’s REST API or client, wait for the run, and read the default Dataset. The relevant part is to treat &lt;code&gt;canonicalJobId&lt;/code&gt; as the identity and the nested &lt;code&gt;sources&lt;/code&gt; array as audit provenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;p&gt;At publication time, the Actor has a $0.00005 start event and charges $0.0025 for each final normalized, deduplicated job row. Filtered rows and duplicate source listings are not the final result event. Check the Actor pricing page before a production rollout because Store prices can change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;

&lt;p&gt;Start from the existing no-secret Jobbnorge + LinkedIn task: &lt;strong&gt;&lt;a href="https://apify.com/yearly_register/norway-jobs-search-api/examples/search-norwegian-jobs-across-multiple-sources" rel="noopener noreferrer"&gt;Run the Norway Jobs search Task&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For filtering details and API context, see the &lt;a href="https://apify.com/yearly_register/norway-jobs-search-api" rel="noopener noreferrer"&gt;Norway Jobs Search API Actor page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>apify</category>
      <category>api</category>
      <category>webscraping</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I tested 7 ways to turn an OpenAPI spec into an MCP server</title>
      <dc:creator>arobakid</dc:creator>
      <pubDate>Tue, 01 Sep 2026 17:42:16 +0000</pubDate>
      <link>https://dev.to/arobakid/i-tested-7-ways-to-turn-an-openapi-spec-into-an-mcp-server-p5b</link>
      <guid>https://dev.to/arobakid/i-tested-7-ways-to-turn-an-openapi-spec-into-an-mcp-server-p5b</guid>
      <description>&lt;p&gt;Every API team I talk to is getting the same request this year: "can our agents call this?" MCP is how agents call tools. You already have an OpenAPI spec. The distance between those two facts is smaller than it has ever been, so I spent a weekend running one spec through seven different approaches to find out exactly how small.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;I used one real OpenAPI 3.1 spec, different endpoints, a mix of API-key and OAuth2 auth, pagination, and a couple of file-upload endpoints. In other words, the stuff that breaks naive generators.&lt;/p&gt;

&lt;p&gt;What I measured:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time to a working server.&lt;/strong&gt; From spec to an agent successfully calling a tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool quality.&lt;/strong&gt; The names and descriptions the LLM actually sees. This matters more than anything else and almost nobody talks about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth.&lt;/strong&gt; What happens when the endpoint needs OAuth, not just a key in a header.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drift.&lt;/strong&gt; What happens when the spec changes next Tuesday. A generated server is a snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 2.&lt;/strong&gt; Hosting, scopes, logs. Everything between "it runs on my laptop" and "I'd let a partner's agent call this."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The seven approaches
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Hand-writing it with the official MCP SDK
&lt;/h3&gt;

&lt;p&gt;The baseline. The TypeScript and Python SDKs are solid, and a hand-written server has the best tool descriptions of anything I tested, because a human wrote them for an LLM reader.&lt;/p&gt;

&lt;p&gt;The cost is obvious: you write and maintain every tool. For a curated surface of five carefully designed tools, this is the right answer. For "expose our forty endpoints," it's a week of work that starts rotting the moment the API changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; a small, deliberate tool surface where every description is crafted.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. FastMCP
&lt;/h3&gt;

&lt;p&gt;FastMCP can build a server directly from an OpenAPI spec (or straight from a FastAPI app), and the developer experience is genuinely delightful. Huge community and for Python teams it's the obvious default.&lt;/p&gt;

&lt;p&gt;What it doesn't do: hosting, auth infrastructure, or regeneration. You own the deploy, and when the spec drifts, re-running the generation and redeploying is your job to wire up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Python teams, internal tools, local stdio servers. If that's you, honestly, stop reading and use this.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. openapi-mcp-generator (open-source CLI)
&lt;/h3&gt;

&lt;p&gt;Spec in, server code out. Free, inspectable, and the output is yours to modify. This is the option open-source purists will (rightly) reach for.&lt;/p&gt;

&lt;p&gt;The catch I hit: the generated tool descriptions are only as good as the descriptions in your spec. Mine were written for human API consumers years ago, so the generated tools were vague and the agent guessed badly. Garbage in, agent confusion out. That's not the generator's fault, but it's the reality of most specs in the wild.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that want code they own and are willing to improve their spec first.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Stainless
&lt;/h3&gt;

&lt;p&gt;If you already use Stainless for SDK generation, MCP server output is essentially another target in the pipeline, and the quality inherits from everything their SDK tooling already knows about your API.&lt;/p&gt;

&lt;p&gt;It's oriented around companies shipping official SDKs. For a side project it's overkill; for an API company it's a very clean story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; SDK-first companies that want docs, SDKs, and MCP from one pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Speakeasy
&lt;/h3&gt;

&lt;p&gt;Similar lane to Stainless, with one side effect I appreciated: their spec linting pushes you to fix the description-quality problem from approach #3, which improves your generated tools whether or not you stay with them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that want to invest in spec quality and generate multiple artifacts from it.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Postman's MCP generator
&lt;/h3&gt;

&lt;p&gt;If your team lives in Postman, generating an MCP server from a collection is convenient and requires no new tools.&lt;/p&gt;

&lt;p&gt;The structural issue: the collection is the input. Collections are maintained by hand, they describe what someone typed into a client, and they drift from the code the moment nobody updates them. Your MCP server inherits whatever drift the collection has. Hosting, per-tool scopes, and call logs are also on you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Postman-native teams with well-maintained collections who want a quick start.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Elva &lt;a href="https://getelva.ai/" rel="noopener noreferrer"&gt;https://getelva.ai/&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Different starting point: instead of taking the spec as input, Elva reads the repo, generates the OpenAPI itself, and rescans on every commit, so the drift problem from approaches #2 through #6 doesn't exist because the spec is an output, not an artifact someone maintains. The MCP server is hosted, with OAuth2, per-tool scopes, field redaction, rate limits, and a log of which agent called which tool.&lt;/p&gt;

&lt;p&gt;The pitch is the day-2 problem: not "generate a server" but "run one you'd let a partner's agent call and still trust it in three months."&lt;/p&gt;

&lt;p&gt;Where it loses, so you don't have to guess: it's a hosted platform, not a library. If you want a local stdio server for a hobby project, FastMCP is better and you should use it. It's not open source. And the free tier covers one repo, so a personal multi-project setup won't be for free, but it is still cheap enough if you ask me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; production APIs with external consumers, partners, or agents, where governance and observability are requirements rather than nice-to-haves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually separated them
&lt;/h2&gt;

&lt;p&gt;Generation is the easy 20%. Every single tool above produced a server that technically worked. The differences that mattered were all downstream:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool descriptions are the whole game.&lt;/strong&gt; The spec's descriptions become the agent's entire understanding of your API. Most specs were written for human developers who could read between the lines. Agents can't. The tools that either force you to fix descriptions (Speakeasy's linting) or generate and score them (Elva) produced noticeably better agent behavior than raw pass-through generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth is where half the field punts.&lt;/strong&gt; API-key auth worked everywhere. OAuth2 separated the libraries (you build it) from the platforms (built in). If your API's interesting endpoints are behind OAuth, weight this criterion heavily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drift is the silent killer.&lt;/strong&gt; A generated MCP server is a snapshot of the spec at generation time. Whatever mechanism regenerates and redeploys when the spec changes is your real architecture. For most of these tools, that mechanism is "you, remembering to."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability is how you sleep.&lt;/strong&gt; When an agent starts calling a delete endpoint in a retry loop at 3am, "which agent, which key, which tool, how many times" is not a question you want to answer from gateway logs and vibes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd actually pick
&lt;/h2&gt;

&lt;p&gt;Hobby project or internal Python tooling: &lt;strong&gt;FastMCP&lt;/strong&gt;, no contest. Company already shipping SDKs: &lt;strong&gt;Stainless or Speakeasy&lt;/strong&gt;, and your MCP story comes almost free. Team that lives in Postman with disciplined collection hygiene: the &lt;strong&gt;Postman generator&lt;/strong&gt; is a fine start. Production API where partners' or customers' agents are the consumers and you need auth, scopes, and an audit trail: that's the gap I started &lt;strong&gt;Elva&lt;/strong&gt; to fill, and approaches #1 through #6 are why I thought the gap was real.&lt;/p&gt;

&lt;p&gt;Genuine question for the comments, because I don't think anyone has a great answer yet: &lt;strong&gt;when your spec changes, what regenerates and redeploys your MCP server?&lt;/strong&gt; Cron job? CI step? A human? I'm collecting answers.&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>mcp</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Compare DOCM Files Using Python REST API</title>
      <dc:creator>Shahzad Ashraf</dc:creator>
      <pubDate>Tue, 01 Sep 2026 17:32:39 +0000</pubDate>
      <link>https://dev.to/shahzad_ashraf_5ea18e2b2d/how-to-compare-docm-files-using-python-rest-api-17hf</link>
      <guid>https://dev.to/shahzad_ashraf_5ea18e2b2d/how-to-compare-docm-files-using-python-rest-api-17hf</guid>
      <description>&lt;p&gt;Comparing DOCM files can be surprisingly tricky, especially when dealing with different versions or edits. It’s not just about spotting differences; it's about ensuring the integrity of your documents while minimizing manual verification processes. Using the right tools can drastically reduce the effort needed to compare these files accurately.&lt;/p&gt;

&lt;p&gt;The GroupDocs.Comparison Cloud SDK for Python provides a powerful REST API that simplifies the process of comparing DOCM files. With just a few lines of code, developers can automate document comparison, allowing for faster identification of changes without the tedious manual checks. This cloud-based solution scales easily and integrates smoothly into existing workflows.&lt;/p&gt;

&lt;p&gt;Check out the article for a working code example that demonstrates how to get started with the SDK today. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://kb.groupdocs.cloud/comparison/python/compare-docm-files-using-python-rest-api/" rel="noopener noreferrer"&gt;https://kb.groupdocs.cloud/comparison/python/compare-docm-files-using-python-rest-api/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>api</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>Why Your Free-Tier Benchmark Is Misleading (and What to Run Instead)</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Tue, 01 Sep 2026 17:29:27 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/why-your-free-tier-benchmark-is-misleading-and-what-to-run-instead-4a5l</link>
      <guid>https://dev.to/gitlab_3188/why-your-free-tier-benchmark-is-misleading-and-what-to-run-instead-4a5l</guid>
      <description>&lt;p&gt;You got a fast response once. So the free tier must be fine, right?&lt;/p&gt;

&lt;p&gt;Then you deploy it, and the next request takes eleven seconds. Or it returns HTML instead of JSON. Or it works for three hours then 503s for a day.&lt;/p&gt;

&lt;p&gt;Your benchmark was misleading. Let me show you what actually happened.&lt;/p&gt;

&lt;p&gt;I maintain this field protocol as a developer evaluating MonkeyCode's free model access and free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The steps are generic. You can use them on any provider.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Misleading Metrics
&lt;/h2&gt;

&lt;p&gt;Most "free tier is solid" conclusions come from three lazy measurements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Myth 1: Average Latency Is the Truth
&lt;/h3&gt;

&lt;p&gt;You send ten requests, compute the mean, get 400ms. Feels great.&lt;/p&gt;

&lt;p&gt;The average hides tails. Free tiers share CPU, network, and request queues. The distribution is a long curve, not a spike.&lt;/p&gt;

&lt;p&gt;Ask instead: What's the p90? The p99? What happens at noon or midnight?&lt;/p&gt;

&lt;h3&gt;
  
  
  Myth 2: A Correct-Looking Output Means Correct Output
&lt;/h3&gt;

&lt;p&gt;You ask a model for JSON. It returns something that parses. Done, right?&lt;/p&gt;

&lt;p&gt;But the model may skip a required field, or wrap the answer in markdown, or emit an empty string that parses as valid.&lt;/p&gt;

&lt;p&gt;You need contract tests. Validate structure and types, every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Myth 3: One Success Window Proves Stability
&lt;/h3&gt;

&lt;p&gt;You ran the test at 2 a.m. on a Tuesday. Everything passed.&lt;/p&gt;

&lt;p&gt;Shared infrastructure changes by the hour. A single run proves nothing about evenings, weekends, or release days of the underlying platform.&lt;/p&gt;

&lt;p&gt;Repeat the same test across at least three different time windows before you trust it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Correct 15-Minute Protocol
&lt;/h2&gt;

&lt;p&gt;Here's a reproducible script that catches all three myths.&lt;/p&gt;

&lt;p&gt;Save this as &lt;code&gt;free_tier_probe.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Set API_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain the CAP theorem in one sentence.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SAMPLES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;statuses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;structure_errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
            &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&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="c1"&gt;# Minimal contract: response must contain a non-empty "text" string
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
                &lt;span class="n"&gt;structure_errors&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;structure_errors&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No responses recorded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;samples: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p50: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p90: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p99: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_rate: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;structure_errors&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_codes: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the client:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Run it three times, hours apart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://your-endpoint"&lt;/span&gt; &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"secret"&lt;/span&gt;
&lt;span class="nv"&gt;SAMPLES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30 python free_tier_probe.py
&lt;span class="nb"&gt;sleep &lt;/span&gt;21600  &lt;span class="c"&gt;# wait six hours, run again&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;21600  &lt;span class="c"&gt;# wait another six hours, run again&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why 30 samples? Enough to see tail behavior without eating your free quota. Why three runs? Because one window is a vibes check, not evidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reading the Results
&lt;/h2&gt;

&lt;p&gt;Look at the error rate first. If it's above 5%, stop. The free tier is not ready for your flow.&lt;/p&gt;

&lt;p&gt;Then compare p50 and p90. A p90 that is 4x the p50 means you're getting queue bursts. That's normal for shared servers, but you must expect it in your timeout budget.&lt;/p&gt;

&lt;p&gt;Finally, check the &lt;code&gt;structure_errors&lt;/code&gt;. Even with HTTP 200, a malformed response is a failure for your pipeline.&lt;/p&gt;

&lt;p&gt;For a more advanced contract, use JSON Schema with the &lt;code&gt;jsonschema&lt;/code&gt; library. But the snippet above is enough to expose the three myths.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations of This Approach
&lt;/h2&gt;

&lt;p&gt;This protocol tests reliability, not semantic quality. A model can return valid JSON that is factually wrong.&lt;/p&gt;

&lt;p&gt;It also measures a single endpoint and a single prompt. Different payload sizes change latency. Test with your real use case.&lt;/p&gt;

&lt;p&gt;Free tiers can change quotas or routing with no notice. Re-run the probe weekly. Treat results as a snapshot, not a permanent guarantee.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Should Skip This
&lt;/h2&gt;

&lt;p&gt;If you're building a one-off demo and just need a few responses, don't bother. Manual eyeballing is fine for a weekend script.&lt;/p&gt;

&lt;p&gt;If you're planning a real product or an automated pipeline, this is the minimum viable evidence. You still need retries, fallbacks, and timeouts regardless of what the benchmark shows.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;My first benchmark was a lie too. I saw one quick response, wrote code around it, and woke up to a queue of broken calls.&lt;/p&gt;

&lt;p&gt;Since then I always run the protocol above before touching a new free-tier provider. It takes me a quarter of an hour and saves me a day of blame-shifting.&lt;/p&gt;

&lt;p&gt;Want to adapt this for your own platform? Point the script at any compatible endpoint. The numbers don't care where they came from.&lt;/p&gt;

&lt;p&gt;And if you're curious how MonkeyCode's free model and free server hold up, run this exact probe. You'll know more in 15 minutes than most blog posts will tell you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>python</category>
      <category>api</category>
    </item>
  </channel>
</rss>
