<?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: GREVE Malick</title>
    <description>The latest articles on DEV Community by GREVE Malick (@greve_malick_6bf326604dee).</description>
    <link>https://dev.to/greve_malick_6bf326604dee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4056713%2F97ca96db-c6e6-4cdb-a540-ca0a367cc5e4.jpeg</url>
      <title>DEV Community: GREVE Malick</title>
      <link>https://dev.to/greve_malick_6bf326604dee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/greve_malick_6bf326604dee"/>
    <language>en</language>
    <item>
      <title>How I Handle OAuth2 Token Refresh and Quotas in a Django API Gateway</title>
      <dc:creator>GREVE Malick</dc:creator>
      <pubDate>Mon, 24 Aug 2026 22:48:05 +0000</pubDate>
      <link>https://dev.to/greve_malick_6bf326604dee/how-i-handle-oauth2-token-refresh-and-quotas-in-a-django-api-gateway-4f3f</link>
      <guid>https://dev.to/greve_malick_6bf326604dee/how-i-handle-oauth2-token-refresh-and-quotas-in-a-django-api-gateway-4f3f</guid>
      <description>&lt;p&gt;My first post about Asstgr covered the "what" — a self-hosted gateway that lets you register third-party APIs and call them through one unified interface. This time I want to zoom into the "how": specifically, two problems that are deceptively annoying to get right — OAuth2 token lifecycle management and per-user quota enforcement.&lt;/p&gt;

&lt;p&gt;If you're building anything that proxies calls to multiple third-party APIs on behalf of your users, you'll hit both of these sooner or later.&lt;/p&gt;

&lt;p&gt;Problem 1: OAuth2 tokens expire at the worst possible time&lt;/p&gt;

&lt;p&gt;When you integrate a single OAuth2-protected API, refreshing a token by hand is annoying but manageable. When you're proxying an arbitrary number of APIs — each with its own grant type, scopes, and expiry — you need a system, not a snippet.&lt;/p&gt;

&lt;p&gt;Asstgr models this with one OAuthConfig per API:&lt;/p&gt;

&lt;p&gt;Field   Purpose&lt;br&gt;
grant_type  client_credentials, authorization_code, or password&lt;br&gt;
token_url   Where to fetch/refresh tokens&lt;br&gt;
client_id / client_secret_encrypted Credentials, secret stored encrypted at rest&lt;br&gt;
scope   Space-separated scopes&lt;br&gt;
access_token / refresh_token    Cached values&lt;br&gt;
token_expires_at    Expiry timestamp — null means the token never expires&lt;/p&gt;

&lt;p&gt;The actual work happens in a dedicated OAuthService, kept separate from the views. Its job boils down to three responsibilities:&lt;/p&gt;

&lt;p&gt;Fetch a token the first time an API is used, based on its grant_type.&lt;br&gt;
Check expiry before every call — if token_expires_at is in the past (or close to it), refresh proactively instead of waiting for a 401 from the upstream API.&lt;br&gt;
Persist the new access_token / refresh_token / token_expires_at back onto the OAuthConfig so the next call reuses it.&lt;/p&gt;

&lt;p&gt;The detail that matters most here: refresh happens at the gateway level, once, and every caller benefits from it. Without a shared gateway, every single client integrating that API has to reimplement this same refresh logic — and inevitably some of them get it wrong (usually: reacting to a 401 instead of checking expiry ahead of time, which causes the first request after expiry to fail).&lt;/p&gt;

&lt;p&gt;Encrypting client_secret at rest is non-negotiable once you're storing credentials for APIs you don't own. It's a small thing, but it's the difference between "gateway" and "liability."&lt;/p&gt;

&lt;p&gt;Problem 2: quotas need to be atomic, not eventually-consistent&lt;/p&gt;

&lt;p&gt;The second problem is subtler. A naive quota check looks like this:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
if quota.call_count + api.quota_cost &amp;gt; quota.monthly_limit:&lt;br&gt;
    raise QuotaExceeded()&lt;br&gt;
quota.call_count += api.quota_cost&lt;br&gt;
quota.save()&lt;/p&gt;

&lt;p&gt;This works fine until two requests from the same user hit the gateway at nearly the same time — which, with a burst limit of 30 requests/second, is not a rare edge case, it's Tuesday. Both requests read the same call_count, both pass the check, both increment, and you've let the user go over budget.&lt;/p&gt;

&lt;p&gt;Asstgr's APICallQuota model tracks call_count, monthly_limit, and the current month/year, and the increment is wrapped so the check-and-increment happens atomically at the database level rather than in Python. Two practical rules came out of building this:&lt;/p&gt;

&lt;p&gt;Never trust an in-memory read for a value you're about to write back. Use F() expressions or select_for_update() so the increment happens where the data lives, not in application code that can race.&lt;br&gt;
Reset by period, not by cron job. Rather than a scheduled task that zeroes out counters on the 1st of the month (and inevitably fails silently once), the quota check itself looks up-or-creates the APICallQuota row for the current month/year. If it doesn't exist yet, it's created fresh. No cron, no midnight job that can fail, no stale counter.&lt;/p&gt;

&lt;p&gt;HasSufficientQuota is a DRF permission class, checked before execute/ even reaches the view logic — which means a user who's out of credits gets a clean 403 before the gateway wastes a call to the upstream API on their behalf.&lt;/p&gt;

&lt;p&gt;Where this leaves the request lifecycle&lt;/p&gt;

&lt;p&gt;Putting the two together, a single call to /api/v1/apis/{id}/endpoints/{id}/execute/ goes through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate the caller (API key)&lt;/li&gt;
&lt;li&gt;Check burst + sustained rate limits (DRF throttling)&lt;/li&gt;
&lt;li&gt;Check quota (HasSufficientQuota) — atomic check against APICallQuota&lt;/li&gt;
&lt;li&gt;Resolve auth for the target API:

&lt;ul&gt;
&lt;li&gt;static API key → attach directly&lt;/li&gt;
&lt;li&gt;OAuth2 → OAuthService checks expiry, refreshes if needed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Build the request from registered Endpoint/Parameter/Header/Method&lt;/li&gt;
&lt;li&gt;Call the upstream API&lt;/li&gt;
&lt;li&gt;Format the response (JSONCleaner: json/compact/standard/verbose)&lt;/li&gt;
&lt;li&gt;Log the call (APILog) + increment quota&lt;/li&gt;
&lt;li&gt;Return the formatted response + quota status to the caller&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing here is exotic — it's mostly about making sure steps 3 and 4 don't have races, and that a failure in step 6 doesn't leave the quota incremented for a call that never actually succeeded.&lt;/p&gt;

&lt;p&gt;Takeaways if you're building something similar&lt;br&gt;
Centralize token refresh. One place that owns "is this token still good" saves every downstream integration from reimplementing (and getting wrong) the same logic.&lt;br&gt;
Treat quota checks as a database problem, not an application problem. Anything read-then-write under concurrency needs to happen atomically where the data lives.&lt;br&gt;
Derive state from time, don't schedule it. A quota keyed by (user, month, year) that's lazily created is more robust than a monthly reset job.&lt;br&gt;
Fail before you spend. Check quota and rate limits before making the upstream call, not after — so a rejected request costs you nothing.&lt;/p&gt;

&lt;p&gt;Asstgr is open source (Django + DRF) if you want to see the full implementation: github.com/asstgr/asstgropensource. Happy to dig into any of these pieces further in the comments.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>oauth</category>
      <category>backend</category>
    </item>
    <item>
      <title>How to Handle OAuth 2.0 Authentication to Third-Party APIs with Asstgr (self-hosted)</title>
      <dc:creator>GREVE Malick</dc:creator>
      <pubDate>Thu, 13 Aug 2026 13:53:32 +0000</pubDate>
      <link>https://dev.to/greve_malick_6bf326604dee/how-to-handle-oauth-20-authentication-to-third-party-apis-with-asstgr-self-hosted-1b3h</link>
      <guid>https://dev.to/greve_malick_6bf326604dee/how-to-handle-oauth-20-authentication-to-third-party-apis-with-asstgr-self-hosted-1b3h</guid>
      <description>&lt;p&gt;If you've ever had to integrate an OAuth 2.0-protected third-party API into multiple projects, you know the drill: handle the authorization flow, store the tokens, track their expiration, build automatic refresh logic... and start over on every new project.&lt;/p&gt;

&lt;p&gt;Asstgr is a self-hosted API gateway (Django + DRF) that centralizes all of this. The idea: you register a third-party API once in Asstgr, describe its endpoints, and then call it through a unified REST interface — Asstgr takes care of authentication, quota, and logging on your behalf.&lt;/p&gt;

&lt;p&gt;In this article, we'll focus on one specific use case: how to connect an OAuth 2.0-protected API to Asstgr, and call it without ever handling a token by hand.&lt;/p&gt;

&lt;p&gt;The concept&lt;br&gt;
Your app  ──►  Asstgr (/api/v1/...execute/)  ──►  OAuth2-protected third-party API&lt;br&gt;
                  │&lt;br&gt;
                  ├─ Auth (API Key or OAuth2)&lt;br&gt;
                  ├─ Quota&lt;br&gt;
                  ├─ Logs&lt;br&gt;
                  └─ Response formatting&lt;/p&gt;

&lt;p&gt;Your application only needs to know one thing: your Asstgr API key (sk-...). Asstgr internally handles all exchanges with the third-party API's OAuth server (fetching, caching, and refreshing tokens).&lt;/p&gt;

&lt;p&gt;Supported flows&lt;/p&gt;

&lt;p&gt;Asstgr supports the three most common OAuth 2.0 grants, with automatic token refresh:&lt;/p&gt;

&lt;p&gt;client_credentials — for server-to-server integrations (the most common case)&lt;br&gt;
authorization_code — for APIs requiring explicit user authorization&lt;br&gt;
password — for legacy ROPC-based APIs&lt;/p&gt;

&lt;p&gt;Step 1 — Register the API&lt;/p&gt;

&lt;p&gt;We start like with any other API in Asstgr:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http
POST /api/v1/apis/
{
  "name": "My Protected API",
  "url": "https://api.example.com/v1",
  "auth_required": true,
  "quota_cost": 2
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2 — Configure OAuth 2.0&lt;/p&gt;

&lt;p&gt;This is where it gets interesting. We attach an OAuth configuration to the API through the dedicated endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http
POST /api/v1/apis/{api_id}/oauth/
{
  "grant_type": "client_credentials",
  "token_url": "https://api.example.com/oauth/token",
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "scope": "read write"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client_secret is stored encrypted server-side (client_secret_encrypted in the database). Once this configuration is saved, Asstgr knows how to obtain a token for this API.&lt;/p&gt;

&lt;p&gt;Checking token status&lt;/p&gt;

&lt;p&gt;You can check at any time whether a valid token is currently cached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http
GET /api/v1/apis/{api_id}/oauth/token/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forcing a refresh&lt;/p&gt;

&lt;p&gt;If needed (debugging, secret rotation on the provider's side, etc.), you can manually force a token refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http
POST /api/v1/apis/{api_id}/oauth/token/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under normal circumstances this isn't necessary: Asstgr's internal service (OAuthService) checks token expiration (token_expires_at) before every call and refreshes it automatically if needed, completely transparently.&lt;/p&gt;

&lt;p&gt;Step 3 — Describe the endpoint and its parameters&lt;/p&gt;

&lt;p&gt;Just like with a regular API, we add the endpoint and its parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http
POST /api/v1/apis/{api_id}/endpoints/
{
  "path": "/protected-resource",
  "description": "OAuth2-protected resource"
}
http
POST /api/v1/apis/{api_id}/endpoints/{endpoint_id}/parameters/
{
  "name": "resource_id",
  "param_type": "query",
  "data_type": "STRING",
  "required": true
}
http
POST /api/v1/apis/{api_id}/endpoints/{endpoint_id}/methods/
{ "method": "GET" }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4 — Execute the call&lt;/p&gt;

&lt;p&gt;And here's the main payoff: from your application, a single call, using your Asstgr key — no OAuth token to manage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http
POST /api/v1/apis/{api_id}/endpoints/{endpoint_id}/execute/
Authorization: Api-Key sk-xxxxxxxxxxxxxxxxxxxxxxxx

{
  "method": "GET",
  "params": { "resource_id": "42" },
  "display_format": "standard"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Asstgr will, in order:&lt;/p&gt;

&lt;p&gt;Verify your API key and remaining quota&lt;br&gt;
Fetch (or refresh) the OAuth2 token associated with that third-party API&lt;br&gt;
Build the actual HTTP request with that token in the Authorization header&lt;br&gt;
Call the third-party API&lt;br&gt;
Log the call (APILog: user, endpoint, method, status, response size)&lt;br&gt;
Format and return the response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;json&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;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"quota"&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;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"remaining"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"usage_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.0&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;Why centralize this instead of handling OAuth in every service?&lt;br&gt;
A single place to store secrets — your client_id / client_secret don't end up scattered across ten different microservices' codebases&lt;br&gt;
Automatic, shared refresh — the token is cached and refreshed once, even if multiple services call the same API&lt;br&gt;
Unified quota and logging — you know exactly who's calling what, and how much it costs in credits&lt;br&gt;
Transparent provider changes — if the third-party API changes its token URL or scopes, there's only one place to update&lt;br&gt;
Going further&lt;/p&gt;

&lt;p&gt;Everything else (API keys, quotas, rate limiting, response formats) follows the same simple pattern: register declaratively once, then call it through /execute/.&lt;/p&gt;

&lt;p&gt;The project is open source (Django 5.x + DRF, PostgreSQL):&lt;/p&gt;

&lt;p&gt;👉 github.com/asstgr/asstgropensource&lt;/p&gt;

&lt;p&gt;If you find the project useful, a ⭐ goes a long way, and you can follow updates on @asstgrio.&lt;/p&gt;

</description>
      <category>django</category>
      <category>oauth</category>
      <category>api</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building a Light API Integration Proxy in Django (Why &amp; How)</title>
      <dc:creator>GREVE Malick</dc:creator>
      <pubDate>Thu, 06 Aug 2026 13:06:55 +0000</pubDate>
      <link>https://dev.to/greve_malick_6bf326604dee/building-a-light-api-integration-proxy-in-django-why-how-4495</link>
      <guid>https://dev.to/greve_malick_6bf326604dee/building-a-light-api-integration-proxy-in-django-why-how-4495</guid>
      <description>&lt;h1&gt;
  
  
  Why another API tool?
&lt;/h1&gt;

&lt;p&gt;As I built more complex applications, I noticed a pattern: every API has its own auth, rules, and formats. Setting up a full API Gateway like Kong felt like overkill for my needs.&lt;/p&gt;

&lt;p&gt;I wanted a single tool to handle &lt;strong&gt;execution, authentication, and logging&lt;/strong&gt; so my app only deals with business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture (PoC Stage)
&lt;/h2&gt;

&lt;p&gt;To validate the concept quickly, I built a synchronous MVP using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Django &amp;amp; PostgreSQL:&lt;/strong&gt; To easily model endpoints, params, and keys.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requests / Urllib:&lt;/strong&gt; To dynamic construct downstream calls.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;em&gt;Note: I chose Django for rapid prototyping. I plan to migrate to Async (HTTPX / FastAPI or Go) once the execution logic is stable.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How it Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Register the API:&lt;/strong&gt; Define endpoints, base URLs, and OAuth/API Keys in the DB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Execute via a single endpoint:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="c1"&gt;# Instead of handling OAuth + custom headers manually in your app:
&lt;/span&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;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="c1"&gt;# Load variables from the .env file
&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="c1"&gt;# ==========================================
# CONFIGURATION
# ==========================================
# 1. Your Asstgr API Key (generated from your admin panel or the API)
&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="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ASSTGR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. The endpoint ID of '/user' linked to the GitHub API (ID: 36)
# (If you don't know it, run a GET request to http://localhost:8000/api/v1/apis/36/ to find it)
&lt;/span&gt;&lt;span class="n"&gt;ENDPOINT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# 3. The base URL of your local Asstgr instance
&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# ==========================================
# REQUEST SETUP
# ==========================================
# Target URL for Asstgr's unified execution route
&lt;/span&gt;&lt;span class="n"&gt;url&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="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;/apis/36/endpoints/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ENDPOINT_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/execute/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Required HTTP headers (Asstgr API Key Authentication)
&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;Api-Key &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="c1"&gt;# Request body payload
# Requesting 'json' format to retrieve the full raw GitHub user profile
&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;method&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;GET&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;params&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;display_format&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;standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ==========================================
# EXECUTION
# ==========================================
&lt;/span&gt;&lt;span class="k"&gt;try&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;Connecting to Asstgr: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&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;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="n"&gt;url&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;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="c1"&gt;# Check if the HTTP request to Asstgr was successful (status code 2xx)
&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;data&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="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Response Received Successfully ---&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ensure_ascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;http_err&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[HTTP Error] Server returned status code: &lt;/span&gt;&lt;span class="si"&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;status_code&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;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Try to print the detailed error JSON payload from Django REST Framework
&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[Error] An unexpected error occurred: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;err&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;This project is in its early stages. Here is what I'm working on next:&lt;/p&gt;

&lt;p&gt;[ ] Payload Encryption&lt;/p&gt;

&lt;p&gt;[ ] Async Execution (HTTPX)&lt;/p&gt;

&lt;p&gt;[ ] Caching with Redis&lt;/p&gt;

&lt;p&gt;If you've built similar integration layers or have feedback on the approach, I'd love to hear your thoughts in the comments!&lt;/p&gt;

&lt;p&gt;Check out the code on &lt;a href="https://github.com/asstgr/asstgropensource" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;    &lt;/p&gt;

</description>
      <category>django</category>
      <category>architecture</category>
      <category>python</category>
      <category>api</category>
    </item>
    <item>
      <title>Under the Hood: How My API Abstraction Layer Executes Requests (Django + DRF)</title>
      <dc:creator>GREVE Malick</dc:creator>
      <pubDate>Mon, 03 Aug 2026 19:10:52 +0000</pubDate>
      <link>https://dev.to/greve_malick_6bf326604dee/under-the-hood-how-my-api-abstraction-layer-executes-requests-django-drf-4p9d</link>
      <guid>https://dev.to/greve_malick_6bf326604dee/under-the-hood-how-my-api-abstraction-layer-executes-requests-django-drf-4p9d</guid>
      <description>&lt;p&gt;Before diving into the code, here is a quick overview of how the system works:&lt;/p&gt;

&lt;p&gt;In my abstraction layer, API specifications are registered in the database. At runtime, the core logic queries these details while python services (powered by requests) handle the actual call construction and execution.&lt;/p&gt;

&lt;p&gt;To keep things digestible, this post will focus strictly on two core components:&lt;/p&gt;

&lt;p&gt;API Registration logic&lt;/p&gt;

&lt;p&gt;Dynamic URL construction&lt;/p&gt;

&lt;p&gt;(Note: I'll cover methods, endpoint parameters, headers, and authentication like OAuth 2.0 / API Keys in Part 2!)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Core Data Model (API)
To store the configuration for each registered API, I built a dedicated Django model. It acts as the single source of truth for base URLs, authentication requirements, access rights, and quota management.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is what it looks like:&lt;br&gt;
`class API(models.Model):&lt;br&gt;
    name = models.CharField(max_length=255)&lt;br&gt;
    description = models.TextField(blank=True, null=True)&lt;br&gt;
    url = models.URLField(validators=[URLValidator()])&lt;br&gt;
    auth_required = models.BooleanField(default=True)&lt;br&gt;
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_apis', null=True, blank=True)&lt;br&gt;
    api_key_encrypted = models.CharField(blank=True, null=True)&lt;br&gt;
    is_blocked = models.BooleanField(default=False)&lt;br&gt;
    is_active = models.BooleanField(default=True)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 🆕 NEW FIELD: Quota cost per call
quota_cost = models.PositiveIntegerField(
    default=1,
    help_text="Number of credits consumed per call to this API"
)

def can_be_accessed_by(self, user):
    """Only creator or admin can access an API."""
    if not user or not user.is_authenticated:
        return False
    if user.is_superuser:
        return True
    return self.created_by == user

def __str__(self):
    return f"{self.name} (Coût: {self.quota_cost} crédit{'s' if self.quota_cost &amp;gt; 1 else ''})"`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;URL Construction &amp;amp; Parameter Injection (build_url)
The heart of the execution logic relies on a dynamic URL builder. It takes the base API URL, injects dynamic path parameters (e.g., /users/{user_id}), and encodes query string parameters cleanly using Python's urllib.parse.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;`from urllib.parse import quote_plus, urlencode&lt;/p&gt;

&lt;p&gt;def build_url(api_url: str, endpoint_path: str, parameters: list, user_params: dict) -&amp;gt; str:&lt;br&gt;
    # Clean up trailing/leading slashes to avoid double-slash issues (e.g., api.com//v1)&lt;br&gt;
    url = api_url.rstrip("/") + "/" + endpoint_path.lstrip("/")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1. Replace path parameters (e.g., {user_id} -&amp;gt; 42)
for param in parameters:
    if param.param_type == 'path':
        if param.name not in user_params:
            raise ValueError(f"Missing required path parameter: '{param.name}'")
        value = quote_plus(str(user_params[param.name]))
        url = url.replace(f"{{{param.name}}}", value)

# 2. Build and append query parameters
query_params = {
    param.name: user_params.get(param.name, param.default_value)
    for param in parameters
    if param.param_type == 'query' and user_params.get(param.name) is not None
}

if query_params:
    connector = '&amp;amp;' if '?' in url else '?'
    url += connector + urlencode(query_params)

return url`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;The Execution Entry Point (ExecuteEndpointView)
Once the URL logic is ready, the execution flow is triggered through a dedicated Django REST Framework view. Notice how access control and quota checks are enforced at the permission layer before any execution prep happens:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;`from rest_framework.views import APIView&lt;br&gt;
from rest_framework.response import Response&lt;br&gt;
from rest_framework import status&lt;br&gt;
from django.shortcuts import get_object_or_404&lt;/p&gt;

&lt;p&gt;class ExecuteEndpointView(APIView):&lt;br&gt;
    """&lt;br&gt;
    POST /api/v1/apis//endpoints//execute/&lt;br&gt;
    """&lt;br&gt;
    authentication_classes = [APIKeyAuthentication]&lt;br&gt;
    permission_classes = [IsAPIKeyAuthenticated, HasSufficientQuota]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def post(self, request, api_id, endpoint_id):
    # 1. Fetch and validate access to the target API
    api = get_object_or_404(API, pk=api_id, is_active=True, is_blocked=False)
    if not api.can_be_accessed_by(request.user):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)

    # 2. Build URL &amp;amp; execute request
    # (URL construction -&amp;gt; Headers injection -&amp;gt; HTTP Call via requests)`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Well, I think I've covered enough for today—I don't want to make this article too long!&lt;/p&gt;

&lt;p&gt;Want to check out the full code? The open-source repository link is in my bio.&lt;/p&gt;

&lt;p&gt;Want to follow the rest of the series? Feel free to hit the follow button so you don't miss Part 2!&lt;/p&gt;

&lt;p&gt;Thanks to everyone for reading, and happy coding!&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
