Evaluate

Submit work for INVEST scoring. Choose a synchronous call that returns the score inline, or an asynchronous submit that delivers the score to a webhook. A separate CSV flow powers the public evaluator.

Choosing a method

MethodEndpointReturnsUse when
SynchronousPOST /api/v1/score200 with the score inlineYou can hold the connection open — scripts, CI gates, request/response tooling.
AsynchronousPOST /api/v1/evaluate/{source}202 + webhook callbackEvent-driven integrations like the Jira Forge app, or high-volume back-pressure-friendly scoring.

Synchronous scoring

POST/api/v1/score

Submit a single ticket and receive the completed INVEST score in the same response. Authenticate with the bearer token issued to your account when your integration is provisioned — the request is authenticated at the edge and proxied to the scoring engine, so your credentials never reach the engine directly.

Request headers
http
Authorization: Bearer <your_api_key>
Content-Type: application/json
POST /api/v1/score
request
{
  "ticket_id": "PROJ-101",
  "customer_id": "acme_jira_8f2c",
  "ticket_data": {
    "key": "PROJ-101",
    "summary": "As a site admin I want to export user activity",
    "description": "Full description of the story...",
    "acceptance_criteria": "Given a signed-in admin, when..."
  },
  "context_items": []
}
200 OK
response
{
  "status": "completed",
  "job_id": "4ebb0359-b8fb-4973-a04e-29414ab7d01f",
  "ticket_id": "PROJ-101",
  "overall_score": 3.7,
  "individual_scores": {
    "independent": 4,
    "negotiable": 5,
    "valuable": 6,
    "estimable": 2,
    "small": 3,
    "testable": 2
  },
  "scores": {
    "independent": {
      "score": 4,
      "reasoning": "The story likely depends on existing activity logging and permissions, but none of these dependencies are stated.",
      "improvement_suggestions": [
        "Explicitly state prerequisites and link/block on any required enabling stories.",
        "Define whether export is self-contained or requires backend changes."
      ],
      "reference_context": [
        "PROJ-101-title",
        "PROJ-101-description",
        "PROJ-101-acceptance-criteria"
      ]
    }
    // ... one entry per dimension: negotiable, valuable, estimable, small, testable
  },
  "reasoning_per_dimension": {
    "independent": "The story likely depends on existing activity logging and permissions...",
    "negotiable": "The story is phrased as a user goal, but the lack of detail forces hidden assumptions...",
    "valuable": "Exporting user activity can provide audit/compliance insights, but the value is unstated...",
    "estimable": "Key scope drivers are missing (fields, date ranges, volume, format)...",
    "small": "\"Export user activity\" is potentially large and unlikely to fit in a single sprint...",
    "testable": "Acceptance criteria are incomplete and do not define expected outputs or edge cases..."
  },
  "recommendations": [
    "Define the export scope precisely: event types, required fields, timezone, ordering, and default range.",
    "Complete acceptance criteria with concrete Given/When/Then scenarios and security checks.",
    "Add non-functional requirements: max dataset size, performance, sync vs async export.",
    "Split the story into smaller vertical slices so each fits in one sprint.",
    "Document dependencies and assumptions and link/block on prerequisite tickets."
  ],
  "context_references": {
    "independent": ["PROJ-101-title", "PROJ-101-description", "PROJ-101-acceptance-criteria"],
    "negotiable": ["PROJ-101-title", "PROJ-101-description"],
    "valuable": ["PROJ-101-title"],
    "estimable": ["PROJ-101-description", "PROJ-101-acceptance-criteria"],
    "small": ["PROJ-101-title", "PROJ-101-description"],
    "testable": ["PROJ-101-acceptance-criteria"]
  },
  "metadata": {
    "model": {
      "provider": "openai",
      "model_name": "gpt-5.2",
      "temperature": 0.3
    },
    "prompt_version": "v1.0",
    "timestamp": "2026-06-17T18:27:51.957675",
    "processing_time_ms": 19338.47,
    "token_usage": {
      "prompt_tokens": 1279,
      "completion_tokens": 951,
      "total_tokens": 2230
    },
    "service_version": "1.0.0",
    "enriched_context_references": {
      "independent": [], "negotiable": [], "valuable": [],
      "estimable": [], "small": [], "testable": []
    },
    "context_usage_stats": {
      "total_context_items_available": 0,
      "context_items_referenced": 3,
      "references_per_dimension": {
        "independent": 3, "negotiable": 2, "valuable": 1,
        "estimable": 2, "small": 3, "testable": 1
      }
    }
  }
}

Response fields

FieldTypeDescription
statusstringAlways “completed” for a successful synchronous score.
job_idstringUnique identifier for this scoring run.
ticket_idstringEchoes the ticket_id you submitted.
overall_scorenumberAggregate INVEST score out of 10.
individual_scoresobjectThe six INVEST dimensions mapped to an integer score (0–10).
scoresobjectPer-dimension detail: score, reasoning, improvement_suggestions[], and reference_context[] (the context item IDs cited for that dimension).
reasoning_per_dimensionobjectConvenience map of each dimension to its reasoning string (mirrors scores[dimension].reasoning).
recommendationsstring[]Prioritized, ticket-level suggestions to raise the score.
context_referencesobjectEach dimension mapped to the context item IDs that informed it.
metadataobjectRun metadata: model (provider, model_name, temperature), prompt_version, timestamp, processing_time_ms, token_usage, service_version, and context usage stats.
  • • This endpoint scores against the default INVEST rubric. customer_id is resolved from your API key, so the value in the body is optional.
  • individual_scores carries the six INVEST dimensions, each scored 0–10. overall_scoreis the aggregate out of 10. These are the stored values — user-facing surfaces always display them as whole percentages (a stored 7 displays as 70%).
  • • Only summary is strictly required inside ticket_data; the other fields improve scoring accuracy when present. context_items is optional and, when supplied, must be an array of retrieval context objects.

Asynchronous scoring

POST/api/v1/evaluate/{source}

Fire-and-forget evaluation for event-driven integrations. {source} is your integration channel — e.g. jiraforge. The request authenticates with the per-installation shared secret in the X-Vindex-Secret header. Vindex acknowledges immediately and POSTs the completed score to your registered callback URL.

Request headers
http
X-Vindex-Secret: <per-install shared secret>
Content-Type: application/json
POST /api/v1/evaluate/jiraforge
request
{
  "cloudId": "<Jira Cloud ID>",
  "issue": {
    "key": "PROJ-101",
    "fields": {
      "summary": "As a user, I want to...",
      "description": { "...": "ADF or plain text" },
      "issuetype": { "name": "Story" }
    }
  }
}
202 Accepted
response
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "accepted",
  "message": "Evaluation request queued for processing",
  "estimated_completion_time": "30-60 seconds"
}

The completed score is delivered to your webhook as a structured payload — see the callbacks reference for the shape, signature verification, and retry behaviour.

StatusMeaning
202Accepted and queued for scoring. Also returned when an over-limit request is dropped (the request is silently ignored, not rejected).
400Invalid JSON, or missing required fields (e.g. cloudId).
401Missing secret, or unknown/disabled installation — triggers re-registration.
403Shared secret does not match the registered value.
500Unexpected error while processing the request.

Rate limits

EndpointLimitWindow
POST /api/v1/scoreMonthly plan quotaper calendar month
POST /api/v1/evaluate/{source}Monthly plan quotaper calendar month
POST /evaluate/upload10 requests1 hour (per IP)
POST /evaluate/score5 requests1 hour (per IP)
GET /evaluate/results/{session_id}60 requests1 minute

Responses that exceed the limit return 429 Too Many Requests with a Retry-After header.

Continue reading