Skip to main content
POST
/
api
/
proposals
/
{proposal_id}
/
analyze-rfp
Analyze RFP
curl --request POST \
  --url https://api.example.com/api/proposals/{proposal_id}/analyze-rfp
{
  "status": "<string>",
  "message": "<string>",
  "started_at": "<string>",
  "cached": true,
  "rfp_analysis": {},
  "completed_at": "<string>",
  "error": "<string>"
}

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/AllianceBioversityCIAT/alliance-IGAD/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Triggers AI analysis of the uploaded Request for Proposal (RFP) document. This is the first step in the proposal workflow. The endpoint follows an async pattern: it returns immediately after starting the analysis, and you must poll the status endpoint for completion.

Workflow Pattern

This endpoint uses an asynchronous Lambda worker pattern:
  1. Trigger: POST to /analyze-rfp returns immediately with status: "processing"
  2. Lambda Invocation: Backend invokes AnalysisWorkerFunction with InvocationType: "Event"
  3. Polling: Frontend polls GET /analysis-status every 3 seconds
  4. Completion: Status changes to "completed" with analysis results

Request

proposal_id
string
required
The proposal ID or code (format: PROP-YYYYMMDD-XXXX)

Response

status
string
  • processing: Analysis started successfully
  • completed: Analysis already exists (cached)
message
string
Instruction to poll the status endpoint
started_at
string
ISO 8601 timestamp when analysis started
cached
boolean
true if returning cached results (no re-analysis)
rfp_analysis
object
RFP analysis data (only present if cached/completed)

Example Request

curl -X POST "https://api.igad-innovation.org/api/proposals/PROP-20260304-A1B2/analyze-rfp" \
  -H "Authorization: Bearer YOUR_TOKEN"

Example Response

First Call (Processing)

{
  "status": "processing",
  "message": "RFP analysis started. Poll /analysis-status for completion.",
  "started_at": "2026-03-04T10:30:00.000Z"
}

Subsequent Call (Cached)

{
  "status": "completed",
  "rfp_analysis": {
    "semantic_query": "Build a digital platform for farmer cooperatives...",
    "key_requirements": [...],
    "evaluation_criteria": [...]
  },
  "message": "RFP already analyzed",
  "cached": true
}

Status Values

The analysis_status_rfp field in DynamoDB tracks the analysis state:
StatusDescription
not_startedNo analysis has been triggered
processingLambda worker is analyzing the RFP
completedAnalysis finished successfully
failedAnalysis encountered an error

Polling for Status

After triggering the analysis, poll the status endpoint:

GET /api/proposals/{proposal_id}/analysis-status

Check RFP analysis completion status

Polling Example

const pollStatus = async (proposalId: string) => {
  const interval = setInterval(async () => {
    const response = await fetch(
      `/api/proposals/${proposalId}/analysis-status`,
      { headers: { Authorization: `Bearer ${token}` } }
    )
    const data = await response.json()

    if (data.status === 'completed') {
      clearInterval(interval)
      console.log('Analysis complete:', data.rfp_analysis)
    } else if (data.status === 'failed') {
      clearInterval(interval)
      console.error('Analysis failed:', data.error)
    }
  }, 3000) // Poll every 3 seconds
}

Lambda Worker Details

Environment Variables

worker_function_arn = os.environ.get("WORKER_FUNCTION_NAME")
# Example: "arn:aws:lambda:us-east-1:123456789012:function:AnalysisWorkerFunction"

Lambda Invocation

lambda_client.invoke(
    FunctionName=worker_function_arn,
    InvocationType="Event",  # Async invocation (non-blocking)
    Payload=json.dumps({
        "proposal_id": proposal_code,  # Uses PROP-YYYYMMDD-XXXX format
        "analysis_type": "rfp"
    })
)

DynamoDB Updates

Before Lambda invocation:
await db_client.update_item(
    pk=f"PROPOSAL#{proposal_code}",
    sk="METADATA",
    update_expression="SET analysis_status_rfp = :status, rfp_analysis_started_at = :started",
    expression_attribute_values={
        ":status": "processing",
        ":started": datetime.utcnow().isoformat()
    }
)
After Lambda completes (in worker):
db_client.update_item_sync(
    pk=f"PROPOSAL#{proposal_code}",
    sk="METADATA",
    update_expression="SET analysis_status_rfp = :status, rfp_analysis = :result, rfp_analysis_completed_at = :completed",
    expression_attribute_values={
        ":status": "completed",
        ":result": analysis_result,
        ":completed": datetime.utcnow().isoformat()
    }
)

Caching Behavior

No Re-analysis: If rfp_analysis already exists in DynamoDB, the endpoint returns cached data immediately without triggering a new analysis.
This prevents:
  • Duplicate Lambda invocations
  • Unnecessary AI API costs
  • Wasted processing time

Error Handling

Status Code 400

{
  "detail": "Proposal code not found"
}

Status Code 403

{
  "detail": "Access denied"
}

Status Code 404

{
  "detail": "Proposal not found"
}

Status Code 500

{
  "detail": "RFP analysis failed: WORKER_FUNCTION_NAME environment variable not set"
}

GET Analysis Status

Description

Poll this endpoint to check the RFP analysis completion status.

Request

proposal_id
string
required
The proposal ID or code

Response

status
string
Current analysis status: not_started, processing, completed, or failed
rfp_analysis
object
Analysis results (only when status is completed)
completed_at
string
ISO timestamp (only when completed)
started_at
string
ISO timestamp (only when processing)
error
string
Error message (only when failed)

Example Response (Completed)

{
  "status": "completed",
  "rfp_analysis": {
    "semantic_query": "Develop a digital platform for agricultural cooperatives in the IGAD region...",
    "key_requirements": [
      "Mobile-first design",
      "Offline functionality",
      "Multi-language support"
    ],
    "evaluation_criteria": [
      {
        "criterion": "Technical Approach",
        "weight": "30%",
        "description": "Quality and feasibility of proposed solution"
      }
    ],
    "budget_range": "$50,000 - $100,000",
    "timeline": "12 months"
  },
  "completed_at": "2026-03-04T10:32:45.000Z"
}

Example Response (Processing)

{
  "status": "processing",
  "started_at": "2026-03-04T10:30:00.000Z"
}

Example Response (Failed)

{
  "status": "failed",
  "error": "Failed to extract text from RFP document"
}

Best Practices

Recommended Polling Strategy:
  • Interval: 3 seconds
  • Timeout: 5 minutes
  • Show loading indicator with elapsed time
  • Handle both success and failure states
Check for cached results before showing loading UI - the endpoint may return completed data immediately if analysis already exists.

Build docs developers (and LLMs) love