Skip to content

feat: add service API-key auth and rate limiting to protect Gemini quota - #73

Merged
zeemscript merged 4 commits into
Deen-Bridge:devfrom
BountySpaghetti:feat/api-key-auth-and-rate-limiting
Jul 30, 2026
Merged

feat: add service API-key auth and rate limiting to protect Gemini quota#73
zeemscript merged 4 commits into
Deen-Bridge:devfrom
BountySpaghetti:feat/api-key-auth-and-rate-limiting

Conversation

@BountySpaghetti

@BountySpaghetti BountySpaghetti commented Jul 26, 2026

Copy link
Copy Markdown

Closes #9

Summary

Adds shared-secret API-key authentication and per-client rate limiting to protect the Gemini quota and secure all chat endpoints against unauthorised access.

Changes

Authentication (main.py)

  • X-API-Key header required on POST /chat, POST /chat/stream, and DELETE /chat/{chat_id}
  • Key validated against SERVICE_API_KEY env var using secrets.compare_digest (constant-time comparison)
  • Returns 401 when key is missing or invalid
  • Implemented as a FastAPI Security dependency so the requirement shows up in /docs
  • Uses FastAPI's APIKeyHeader security scheme

Rate Limiting (main.py, requirements.txt)

  • 20 requests/minute per client on /chat and /chat/stream via slowapi
  • Rate limit key derived from X-API-Key header; falls back to client IP via X-Forwarded-For (Render sets this header)
  • Returns 429 with Retry-After header when limit exceeded
  • In-memory storage — fine for single-instance Render; a comment notes Redis is needed if multi-instance support is added

Startup Validation (main.py)

  • Service fails loudly at startup if SERVICE_API_KEY is not set when ENVIRONMENT=production
  • AUTH_DISABLED=true opts out of authentication for local development
  • Warns when SERVICE_API_KEY is unset in non-production environments

Health Endpoints

  • /ping, /cache/stats, /confidence/policy remain unauthenticated — Render health checks and the keep-awake workflow continue to work

Workflow Fix (.github/workflows/keep-render-awake.yml)

  • Updated pinger to hit /ping instead of root URL (root returns 404)

Config & Docs

  • SERVICE_API_KEY added to render.yaml (sync: false, like GEMINI_API_KEY)
  • .env.example updated with SERVICE_API_KEY and AUTH_DISABLED
  • README updated: new env vars table, auth & rate-limiting section, API table note

Acceptance Criteria

  • POST /chat without X-API-Key (or with a wrong key) returns 401; with the correct key it works as before
  • The 21st request within a minute from the same key returns 429 with Retry-After
  • Health/ping remains reachable without a key; keep-awake workflow still succeeds
  • Local development without any secret is possible via AUTH_DISABLED=true
  • README and render.yaml document SERVICE_API_KEY
  • All existing tests pass (541 passed, 1 skipped)
  • Lint and syntax checks pass

Summary by CodeRabbit

  • New Features

    • Added an API health endpoint (/health).
    • Added per-user memory endpoints (/memory/{user_id}) for storing and managing long-term context.
    • Added answer feedback endpoints (/feedback, plus admin stats/records) with rate-limited submissions.
    • Enhanced chat responses with SSE streaming, structured citations, and optional purchase-history context.
    • Introduced production API-key authentication via X-API-Key with a local-development toggle.
  • Documentation

    • Updated README with expanded API route coverage, streaming protocol details, and environment variable configuration.
  • Chores

    • Updated deployment checks to use /health and adjusted the keep-awake request to /ping.
    • Added SlowAPI and tightened the Redis version constraint.

- Require X-API-Key header on /chat, /chat/stream, and /chat/{chat_id}
- Validate key against SERVICE_API_KEY env var using secrets.compare_digest
- Return 401 when key is missing or invalid
- Add per-client rate limiting (20 req/min) via slowapi on chat endpoints
- Rate limit key derived from X-API-Key, falling back to X-Forwarded-For IP
- Return 429 with Retry-After header when limit exceeded
- Fail loudly at startup if SERVICE_API_KEY is unset in production
- Allow AUTH_DISABLED=true for local development without a key
- Leave /ping, /cache/stats, and /confidence/policy unauthenticated
- Update keep-awake workflow to use /ping endpoint
- Add SERVICE_API_KEY to render.yaml envVars
- Update .env.example and README with new env vars and auth docs
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e72493cf-cceb-4f5a-a6d4-fcd30a0237c1

📥 Commits

Reviewing files that changed from the base of the PR and between 30927bd and 4a78adb.

📒 Files selected for processing (5)
  • .env.example
  • README.md
  • main.py
  • render.yaml
  • requirements.txt

Walkthrough

The service adds API-key authentication and rate-limiting infrastructure, documents related configuration, provisions SERVICE_API_KEY, constrains Redis versions, and changes the keep-awake workflow to call /ping. The chat and delete routes are not connected to the new authentication utilities in this diff.

Changes

API access controls

Layer / File(s) Summary
Configuration and dependency wiring
.env.example, render.yaml, requirements.txt
Documents and provisions SERVICE_API_KEY and AUTH_DISABLED, adds SlowAPI, and caps the Redis dependency below version 6.
Authentication and rate-limit infrastructure
main.py
Defines API-key validation, client-key selection, and 429 handling, but removes authentication and rate-limit injection from the chat and delete handlers.
Documentation and service monitoring
README.md, .github/workflows/keep-render-awake.yml
Documents API-key settings and changes scheduled requests to /ping.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • Deen-Bridge/dnb-ai#38: Both changes overlap in the /chat implementation and its request/response handling.

Suggested reviewers: zeemscript, flor125, fury03

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the requested API-key auth, rate limiting, startup guard, unauthenticated ping, and docs/config updates.
Out of Scope Changes check ✅ Passed The changes shown are all directly related to auth, throttling, health checks, or their documentation and deployment config.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: service API-key authentication plus per-client rate limiting to protect Gemini quota.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@main.py`:
- Around line 72-79: Update the startup validation around AUTH_DISABLED so it
raises RuntimeError whenever AUTH_DISABLED is enabled outside development,
local, or test environments, regardless of SERVICE_API_KEY. Preserve the
existing missing-SERVICE_API_KEY validation for non-disabled authentication in
production.
- Around line 308-309: Update the async handlers chat() and chat_stream() so
Gemini operations never block the event loop: use send_message_async() for the
non-safety chat path, and asynchronously create and consume the Gemini stream
with async for in chat_stream(), or offload any unavoidable synchronous calls to
a worker thread. Apply the changes at main.py lines 308-309 and 543-544.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b46148c1-2215-49c1-8c9e-23b44582ba41

📥 Commits

Reviewing files that changed from the base of the PR and between e1a020a and b3c8706.

📒 Files selected for processing (6)
  • .env.example
  • .github/workflows/keep-render-awake.yml
  • README.md
  • main.py
  • render.yaml
  • requirements.txt

Comment thread main.py
Comment on lines +72 to +79
AUTH_DISABLED = os.getenv("AUTH_DISABLED", "false").lower() in {"1", "true", "yes"}

if not AUTH_DISABLED and not SERVICE_API_KEY:
if os.getenv("ENVIRONMENT", "production").lower() not in {"development", "local", "test"}:
raise RuntimeError(
"SERVICE_API_KEY must be set in production. "
"Set AUTH_DISABLED=true to run without authentication locally."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject AUTH_DISABLED in production.

AUTH_DISABLED=true skips this startup check and makes verify_api_key return success for every request. Fail startup when that flag is enabled outside development, local, or test; otherwise a production configuration mistake fully disables the new access control.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 72 - 79, Update the startup validation around
AUTH_DISABLED so it raises RuntimeError whenever AUTH_DISABLED is enabled
outside development, local, or test environments, regardless of SERVICE_API_KEY.
Preserve the existing missing-SERVICE_API_KEY validation for non-disabled
authentication in production.

Comment thread main.py Outdated
Comment on lines +308 to +309
@limiter.limit("20/minute")
async def chat(request: ChatRequest, http_request: Request, fastapi_response: Response, _key: str = Security(verify_api_key)):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp_dir="$(mktemp -d)"
python -m pip download --no-deps --dest "$tmp_dir" google-generativeai==0.8.3
unzip -q "$tmp_dir"/google_generativeai-0.8.3-*.whl -d "$tmp_dir/pkg"
rg -n -C 2 'send_message_async|send_message\(' "$tmp_dir/pkg"

Repository: Deen-Bridge/dnb-ai

Length of output: 4515


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant endpoints and surrounding helper code.
python3 - <<'PY'
from pathlib import Path
p = Path("main.py")
lines = p.read_text().splitlines()
for start, end in [(260, 360), (500, 610)]:
    print(f"\n--- main.py:{start}-{end} ---")
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4d}: {lines[i-1]}")
PY

Repository: Deen-Bridge/dnb-ai

Length of output: 10392


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the 0.8.3 SDK for async chat/stream entrypoints and whether stream iteration is async.
tmp_dir="$(mktemp -d)"
python3 -m pip download --no-deps --dest "$tmp_dir" google-generativeai==0.8.3 >/dev/null
unzip -q "$tmp_dir"/google_generativeai-0.8.3-*.whl -d "$tmp_dir/pkg"
python3 - <<'PY' "$tmp_dir/pkg"
import sys
from pathlib import Path

root = Path(sys.argv[1])
files = [
    root / "google/generativeai/generative_models.py",
    root / "google/generativeai/client.py",
]
for f in files:
    print(f"\n--- {f.relative_to(root)} ---")
    text = f.read_text().splitlines()
    for needle in ["def send_message(", "async def send_message_async(", "stream=True", "def generate_content(", "async def generate_content_async(", "__aiter__", "async for"]:
        hits = [i + 1 for i, line in enumerate(text) if needle in line]
        if hits:
            print(f"{needle}: {hits[:10]}")
PY

Repository: Deen-Bridge/dnb-ai

Length of output: 421


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp_dir="$(mktemp -d)"
python3 -m pip download --no-deps --dest "$tmp_dir" google-generativeai==0.8.3 >/dev/null
unzip -q "$tmp_dir"/google_generativeai-0.8.3-*.whl -d "$tmp_dir/pkg"

python3 - <<'PY' "$tmp_dir/pkg"
from pathlib import Path
import sys

root = Path(sys.argv[1])
text = (root / "google/generativeai/generative_models.py").read_text().splitlines()

# Print the async/chat/stream related method signatures and nearby context.
needles = [
    "async def send_message_async(",
    "def send_message(",
    "stream=True",
    "async def generate_content_async(",
    "def generate_content(",
    "class ChatSession",
]
for needle in needles:
    print(f"\n### {needle}")
    for i, line in enumerate(text):
        if needle in line:
            start = max(0, i - 8)
            end = min(len(text), i + 35)
            for j in range(start, end):
                print(f"{j+1:4d}: {text[j]}")
            break
PY

Repository: Deen-Bridge/dnb-ai

Length of output: 12537


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact provider-call sites in main.py.
python3 - <<'PY'
from pathlib import Path
p = Path("main.py")
lines = p.read_text().splitlines()
for start, end in [(360, 520), (590, 660)]:
    print(f"\n--- main.py:{start}-{end} ---")
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4d}: {lines[i-1]}")
PY

Repository: Deen-Bridge/dnb-ai

Length of output: 11880


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp_dir="$(mktemp -d)"
python3 -m pip download --no-deps --dest "$tmp_dir" google-generativeai==0.8.3 >/dev/null
unzip -q "$tmp_dir"/google_generativeai-0.8.3-*.whl -d "$tmp_dir/pkg"

python3 - <<'PY' "$tmp_dir/pkg"
from pathlib import Path
import sys

root = Path(sys.argv[1])
files = [
    root / "google/generativeai/types/generation_types.py",
    root / "google/generativeai/generative_models.py",
]
for f in files:
    print(f"\n--- {f.relative_to(root)} ---")
    text = f.read_text().splitlines()
    for needle in [
        "class AsyncGenerateContentResponse",
        "__aiter__",
        "__anext__",
        "async for",
        "class GenerateContentResponse",
        "yield",
    ]:
        hits = [i + 1 for i, line in enumerate(text) if needle in line]
        if hits:
            print(f"{needle}: {hits[:20]}")
PY

Repository: Deen-Bridge/dnb-ai

Length of output: 455


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp_dir="$(mktemp -d)"
python3 -m pip download --no-deps --dest "$tmp_dir" google-generativeai==0.8.3 >/dev/null
unzip -q "$tmp_dir"/google_generativeai-0.8.3-*.whl -d "$tmp_dir/pkg"

python3 - <<'PY' "$tmp_dir/pkg"
from pathlib import Path
import sys

root = Path(sys.argv[1])
path = root / "google/generativeai/types/generation_types.py"
text = path.read_text().splitlines()
needles = ["class AsyncGenerateContentResponse", "class GenerateContentResponse", "class BaseGenerateContentResponse"]
for needle in needles:
    for i, line in enumerate(text):
        if needle in line:
            start = max(0, i - 12)
            end = min(len(text), i + 120)
            print(f"\n### {needle}")
            for j in range(start, end):
                print(f"{j+1:4d}: {text[j]}")
            break
PY

Repository: Deen-Bridge/dnb-ai

Length of output: 13667


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the SDK pin and any internal helper wrappers that may already bridge sync calls.
rg -n --hidden --glob '!**/.git/**' 'google-generativeai|generativeai|genai|send_message_async|generate_content_async|asyncio\.to_thread|run_in_executor' .

Repository: Deen-Bridge/dnb-ai

Length of output: 1495


Move Gemini calls off the event loop. chat() still hits the sync send_message() path when the safety pipeline is disabled, and chat_stream() synchronously creates and iterates the Gemini stream inside an async handler. Use send_message_async()/async for, or offload the sync path to a worker thread.

📍 Affects 1 file
  • main.py#L308-L309 (this comment)
  • main.py#L543-L544
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 308 - 309, Update the async handlers chat() and
chat_stream() so Gemini operations never block the event loop: use
send_message_async() for the non-safety chat path, and asynchronously create and
consume the Gemini stream with async for in chat_stream(), or offload any
unavoidable synchronous calls to a worker thread. Apply the changes at main.py
lines 308-309 and 543-544.

Source: Path instructions

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.py (1)

440-461: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Rename the FastAPI Request parameter on both chat endpoints

slowapi needs the Starlette request in a parameter literally named request; here both handlers use request: ChatRequest and rename the real Request to http_request, so the limiter/key function sees the body model instead of a Request and rate limiting breaks on both /chat and /chat/stream. Rename the body model variable (for example chat_request) and use request: Request for the FastAPI request, then update the internal request.* / http_request.headers references accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 440 - 461, The /chat and /chat/stream handlers use the
body model as request, preventing slowapi from receiving the Starlette Request.
In both main.py sites (lines 440-461 and 758-761), rename the ChatRequest
parameter (for example, chat_request), use request: Request for the HTTP
request, and update all body-model references and header accesses accordingly.
🧹 Nitpick comments (1)
main.py (1)

707-746: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Chain the original exception with raise ... from exc.

Ruff/flake8-bugbear (B904) flags all five re-raises here. Since CI enforces flake8 for this service, worth confirming whether bugbear's opinionated B9 codes are selected in your config — if so this currently fails the build; either way, chaining preserves the original traceback for debugging instead of presenting the HTTPException as if it arose independently.

🔧 Suggested fix (repeat for all 5 blocks)
     except ResourceExhausted as exc:
         logger.warning("Gemini rate limit exceeded for chat %s: %s", chat_id, exc)
         raise HTTPException(
             status_code=429,
             detail="Rate limit exceeded. Please try again later.",
             headers={"X-Trace-Id": trace.trace_id},
-        )
+        ) from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 707 - 746, Update the five HTTPException re-raises in
the ResourceExhausted, InvalidArgument, timeout, ServiceUnavailable, and generic
Exception handlers to use explicit exception chaining with the corresponding
caught exception variable. Preserve each existing status code, detail, headers,
and logging behavior.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@main.py`:
- Around line 440-461: The /chat and /chat/stream handlers use the body model as
request, preventing slowapi from receiving the Starlette Request. In both
main.py sites (lines 440-461 and 758-761), rename the ChatRequest parameter (for
example, chat_request), use request: Request for the HTTP request, and update
all body-model references and header accesses accordingly.

---

Nitpick comments:
In `@main.py`:
- Around line 707-746: Update the five HTTPException re-raises in the
ResourceExhausted, InvalidArgument, timeout, ServiceUnavailable, and generic
Exception handlers to use explicit exception chaining with the corresponding
caught exception variable. Preserve each existing status code, detail, headers,
and logging behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bb5265b6-f663-4ec0-a748-24b43a8266a8

📥 Commits

Reviewing files that changed from the base of the PR and between b3c8706 and db60fdd.

📒 Files selected for processing (2)
  • main.py
  • requirements.txt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
README.md (3)

44-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

List /chat/stream in the API table.

The new authentication note covers the streaming route, but the API table omits POST /chat/stream. Add it so the documented endpoint and authentication surface matches main.py.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 44 - 45, Add POST /chat/stream to the API table in
README.md, alongside the existing chat endpoints, and document it consistently
with the route defined in main.py and the required X-API-Key authentication.

127-128: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Enforce or explicitly warn about the production impact of AUTH_DISABLED.

main.py bypasses authentication whenever AUTH_DISABLED=true, including in production; the flag is not actually restricted to local development. Either reject this setting when ENVIRONMENT=production, or document prominently that enabling it disables production authentication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 127 - 128, Update the AUTH_DISABLED documentation in
README.md to prominently state that enabling it disables API-key authentication
even in production, or explicitly document the required production restriction
if main.py enforces one. Ensure the documented behavior matches the actual
production handling and clearly retain its local-development-only intent.

100-101: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the required service authentication configuration to runnable deployment examples.

  • README.md#L100-L101: include SERVICE_API_KEY in the inline Docker command, or mark it explicitly as a local AUTH_DISABLED=true example.
  • README.md#L109-L117: add SERVICE_API_KEY to the Render example with sync: false.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 100 - 101, Update README.md lines 100-101 to include
SERVICE_API_KEY in the inline Docker deployment command, or explicitly mark it
as a local AUTH_DISABLED=true example. Update README.md lines 109-117 to add
SERVICE_API_KEY to the Render environment configuration with sync: false.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@README.md`:
- Around line 44-45: Add POST /chat/stream to the API table in README.md,
alongside the existing chat endpoints, and document it consistently with the
route defined in main.py and the required X-API-Key authentication.
- Around line 127-128: Update the AUTH_DISABLED documentation in README.md to
prominently state that enabling it disables API-key authentication even in
production, or explicitly document the required production restriction if
main.py enforces one. Ensure the documented behavior matches the actual
production handling and clearly retain its local-development-only intent.
- Around line 100-101: Update README.md lines 100-101 to include SERVICE_API_KEY
in the inline Docker deployment command, or explicitly mark it as a local
AUTH_DISABLED=true example. Update README.md lines 109-117 to add
SERVICE_API_KEY to the Render environment configuration with sync: false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e0b3ad7-2a7b-4023-95c0-fc420dc20631

📥 Commits

Reviewing files that changed from the base of the PR and between db60fdd and 30927bd.

📒 Files selected for processing (3)
  • README.md
  • main.py
  • render.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • main.py

@zeemscript
zeemscript changed the base branch from dev to main July 29, 2026 17:11
@zeemscript
zeemscript changed the base branch from main to dev July 29, 2026 17:11
@zeemscript
zeemscript merged commit 8c0c395 into Deen-Bridge:dev Jul 30, 2026
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Add service API-key auth and rate limiting to protect the Gemini quota

2 participants