← All docs
🚀

Quickstart

From zero to first semantic query: sign up → API Key → MCP → result.

RAGfly — Integration Quickstart

Full walkthrough from scratch: sign up → API Key → MCP → first semantic query.

Codex supports MCP via codex mcp add. Step 4 covers both paths: MCP (recommended) and direct REST.


Prerequisites

  • Python 3.11+
  • pip install httpx
  • RAGfly account (see step 1)

Step 1 — Sign up

curl -X POST https://api.ragfly.ai/auth/registro \
  -H "Content-Type: application/json" \
  -d '{"email": "you@company.com", "nombre": "Your Name", "empresa": "Your Company"}'

Expected response:

{"mensaje": "Invitación enviada. Revisa tu correo para confirmar tu cuenta."}

Confirm the link received by email. Then continue with step 2.


Step 2 — Get JWT and create API Key

# 2a. Login
source .env   # loads RAGFLY_API_URL, RAGFLY_EMAIL, RAGFLY_PASSWORD

JWT=$(curl -s -X POST $RAGFLY_API_URL/auth/login \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"$RAGFLY_EMAIL\", \"password\": \"$RAGFLY_PASSWORD\"}" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

echo "JWT obtained"

# 2b. Create API Key (persists until revoked)
curl -X POST $RAGFLY_API_URL/auth/api-key \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"nombre": "quickstart-eval", "rol_solicitado": "DOCS-USUARIO-FINAL"}'

Response:

{
  "api_key": "slm_live_...",
  "nombre": "quickstart-eval",
  "rol": "DOCS-USUARIO-FINAL"
}

Save api_key in .env as RAGFLY_API_KEY. Shown only once.

echo 'RAGFLY_API_KEY=slm_live_...' >> .env
source .env

Step 3 — Verify session

curl $RAGFLY_API_URL/auth/me \
  -H "Authorization: Bearer $RAGFLY_API_KEY"

Expected result:

{
  "grupo_activo": "<your group>",
  "tipo_acceso": "USUARIO",
  "rol_principal": "DOCS-USUARIO-FINAL"
}

If you see grupo_activo with a value, the API Key is working correctly.


Step 4 — Configure MCP

RAGfly exposes an MCP server at https://api.ragfly.ai/mcp-http/. All MCP clients (Codex, Claude Code, Cursor, Cline) connect with URL + Bearer credential.

Codex

codex mcp add ragfly \
  --url https://api.ragfly.ai/mcp-http/ \
  --bearer-token-env-var RAGFLY_API_KEY

Verify:

codex mcp list
# ragfly   https://api.ragfly.ai/mcp-http/

Then ask Codex to call estado_sesion to confirm the connection.

Claude Code / Cursor / Cline

Add to ~/.mcp.json or .mcp.json in the project:

{
  "mcpServers": {
    "ragfly": {
      "url": "https://api.ragfly.ai/mcp/sse",
      "headers": {
        "Authorization": "Bearer <RAGFLY_API_KEY>"
      }
    }
  }
}

Restart the client. Verify:

mcp__ragfly__estado_sesion()

Expected result: same JSON as GET /auth/me.


Step 5 — First semantic query

From the terminal

curl -X POST $RAGFLY_API_URL/documentos/buscar-semantico \
  -H "Authorization: Bearer $RAGFLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q": "active contracts in the organization", "limit": 5}'

Expected result: list of document chunks with nombre_documento, tipo_documento, and similitud_max score.

If it returns an empty list, there are probably no documents in VECTORIZADO state yet (see step 6).

From Python

import os, httpx

BASE    = os.environ["RAGFLY_API_URL"]
HEADERS = {"Authorization": f"Bearer {os.environ['RAGFLY_API_KEY']}"}

# Verify session
me = httpx.get(f"{BASE}/auth/me", headers=HEADERS)
me.raise_for_status()
print("Group:", me.json()["grupo_activo"])

# First search
r = httpx.post(
    f"{BASE}/documentos/buscar-semantico",
    headers=HEADERS,
    json={"q": "active contracts in the organization", "limit": 5},
)
r.raise_for_status()
for doc in r.json().get("resultados", []):
    print(f"  [{doc.get('tipo_documento', '?')}] {doc.get('nombre_documento', '?')}")

Step 6 — List available documents

# Documents with embeddings (ready for search)
curl "$RAGFLY_API_URL/documentos/paginado?codigo_estado_doc=VECTORIZADO&limit=20" \
  -H "Authorization: Bearer $RAGFLY_API_KEY"

If the corpus is empty, upload documents from app.ragfly.ai and wait for the pipeline to process them (final state: VECTORIZADO). Check progress with:

curl "$RAGFLY_API_URL/cola-estados-docs/paginado?limit=10" \
  -H "Authorization: Bearer $RAGFLY_API_KEY"

Full test script

#!/usr/bin/env python3
"""Test RAGfly — requires RAGFLY_API_URL and RAGFLY_API_KEY in the environment."""

import os, sys, httpx

BASE    = os.environ.get("RAGFLY_API_URL", "https://api.ragfly.ai")
API_KEY = os.environ.get("RAGFLY_API_KEY", "")
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

if not API_KEY:
    print("ERROR: define RAGFLY_API_KEY in .env")
    sys.exit(1)

_results: list[tuple[str, bool, str]] = []

def ok(label: str, condition: bool, detail: str = "") -> bool:
    estado = "✓" if condition else "✗"
    print(f"  {estado} {label}" + (f" — {detail}" if detail else ""))
    _results.append((label, condition, detail))
    return condition


# ── 1. Session ────────────────────────────────────────────────────────────────
print("1. Verify session")
r = httpx.get(f"{BASE}/auth/me", headers=HEADERS)
ok("HTTP 200", r.status_code == 200, str(r.status_code))
if r.status_code == 200:
    ctx = r.json()
    ok("grupo_activo present", bool(ctx.get("grupo_activo")), ctx.get("grupo_activo", ""))
    ok("rol_principal present", bool(ctx.get("rol_principal")), ctx.get("rol_principal", ""))

# ── 2. List documents ─────────────────────────────────────────────────────────
print("\n2. Vectorized documents")
r = httpx.get(
    f"{BASE}/documentos/paginado",
    headers=HEADERS,
    params={"codigo_estado_doc": "VECTORIZADO", "limit": 5},
)
ok("HTTP 200", r.status_code == 200)
if r.status_code == 200:
    items = r.json().get("items", [])
    ok("Paginated response", "total" in r.json())
    ok("At least 1 document", len(items) > 0, f"{len(items)} docs")
    for doc in items[:3]:
        print(f"     [{doc.get('tipo_documento','?')}] {doc.get('nombre_documento','?')}")

# ── 3. Semantic search ────────────────────────────────────────────────────────
print("\n3. Semantic search")
QUERIES = [
    "contracts in the organization",
    "financial reports",
    "human resources documents",
]
for q in QUERIES:
    r = httpx.post(
        f"{BASE}/documentos/buscar-semantico",
        headers=HEADERS,
        json={"q": q, "limit": 3},
    )
    resultados = r.json().get("resultados", []) if r.status_code == 200 else []
    ok(f"'{q[:40]}'", r.status_code == 200, f"{len(resultados)} results")

# ── 4. Empty query must return 400 ───────────────────────────────────────────
print("\n4. Input validation")
r = httpx.post(
    f"{BASE}/documentos/buscar-semantico",
    headers=HEADERS,
    json={"q": "", "limit": 3},
)
ok("Empty query → 400", r.status_code == 400, str(r.status_code))

# ── 5. Invalid token must return 401 ─────────────────────────────────────────
print("\n5. Security — invalid token")
r = httpx.get(f"{BASE}/auth/me", headers={"Authorization": "Bearer slm_live_INVALID"})
ok("Invalid token → 401", r.status_code == 401, str(r.status_code))

# ── Final result ──────────────────────────────────────────────────────────────
failures = [l for l in _results if not l[1]]
print(f"\n── Tests completed: {len(_results) - len(failures)}/{len(_results)} OK ──")
if failures:
    print("Failed:")
    for label, _, detail in failures:
        print(f"  ✗ {label}" + (f" — {detail}" if detail else ""))
    sys.exit(1)

Expected result: all lines with . If any shows , see the troubleshooting section.


Reproducible tests — expected results

These tests are independent of the corpus (work with any document set):

Test Input Expected result
Valid session GET /auth/me with correct API Key HTTP 200, grupo_activo present
Invalid session GET /auth/me with wrong key HTTP 401
Paginated list GET /documentos/paginado HTTP 200, object with items, total, page, limit
Search with query POST /buscar-semantico with non-empty q HTTP 200, list (may be empty if no docs)
Empty search POST /buscar-semantico with q: "" HTTP 400
limit parameter POST /buscar-semantico with limit: 3 Maximum 3 results

Secure secrets handling

# .gitignore — always add
echo ".env" >> .gitignore

# Verify .env is not tracked
git check-ignore -v .env

In CI/CD: use the provider's environment variables (GitHub Secrets, GitLab CI Variables, etc.)
Never include RAGFLY_API_KEY in source code, logs, or issues.

To revoke a compromised key:

curl -X DELETE $RAGFLY_API_URL/auth/api-key/<prefix> \
  -H "Authorization: Bearer $JWT"

Differences between clients

Aspect Codex Claude Code / Cursor
Setup codex mcp add with --bearer-token-env-var .mcp.json with URL + header
Calls MCP tools (mcp__ragfly__*) MCP tools (mcp__ragfly__*)
Authentication --bearer-token-env-var RAGFLY_API_KEY Declared in .mcp.json
Discovery MCP protocol automatic MCP protocol automatic

Troubleshooting

Error Cause Solution
401 Unauthorized Invalid or revoked API Key Regenerate at app.ragfly.ai or with POST /auth/api-key
400 on buscar-semantico Empty q field Ensure the query is not empty
Empty list on search No vectorized documents Upload docs from app.ragfly.ai and wait for pipeline
RAGFLY_API_KEY not defined .env not loaded source .env
422 Unprocessable Entity Malformed body Verify q is a string and limit is an integer