Skip to main content

API reference

The Vindex HTTP API covers registration, ticket evaluation, score callbacks, and aggregate stats. It's the same API the Jira Forge app uses; build directly on it to wire story health scoring into whatever tooling your team already relies on.

Base URL and conventions

base url
https://edge.vindexgateway.com
  • JSON only. All requests and responses use application/json, except file uploads which use multipart/form-data.
  • UTC timestamps. All dates are ISO 8601 in UTC.
  • Customer ID scoping. Every ticket you submit is tagged with the customer_id you were assigned when your integration was provisioned. Scores are only visible to that customer and to your linked company.
  • Standard HTTP codes. 2xx for success, 4xx for client errors, 5xx for server errors. Error bodies include a detail field describing what went wrong.

Authentication

Authenticated endpoints take an API key as a bearer token. A Client Admin creates keys in the Vindex portal under API keys(a Vindex Admin can do it from the company page), names each one for the agent or system that will use it, and can show or revoke it at any time. The same key works for Vindex MCP when an agent can't sign in with OAuth. Pass it on every request:

http
Authorization: Bearer <your_api_key>

The /evaluate/* endpoints that power the public CSV flow are unauthenticated but rate-limited per IP; see the evaluate reference for limits.

Synchronous vs. asynchronous scoring

Vindex exposes the scoring engine in two shapes. Pick whichever fits how your integration is wired; both run the identical INVEST analysis and return the same dimensions.

Synchronous

POST /api/v1/score blocks until scoring finishes and returns the full score in the response body. Best for request/response contexts: CI checks, scripts, and the CSV evaluator. No callback endpoint required.

Asynchronous

POST /api/v1/evaluate/{source} acknowledges immediately with 202 and delivers the completed score to your webhook. This is what the Jira Forge app uses.

See the evaluate reference for full request and response payloads for both models.

Endpoint groups

Rate limits

Endpoint groupLimitWindow
/api/v1/scoreMonthly story quota (new unique stories)per calendar month (UTC)
/evaluate/upload10 requests1 hour (per IP)
/evaluate/score5 requests1 hour (per IP)

The monthly quota counts new unique stories: re-scoring a story that has already been counted is free forever and is admitted even when the quota is exhausted. When a new story is refused, the response is 429 with code: "QUOTA_EXHAUSTED" and a machine-readable body:

Quota-exhausted response (429)
json
{
  "error": "Quota exhausted",
  "code": "QUOTA_EXHAUSTED",
  "message": "The monthly story quota is reached. New stories are paused until the quota resets; previously scored stories can still be re-scored at no cost.",
  "plan": "starter",
  "quota": 200,
  "used": 200,
  "reset_at": "2026-09-01T00:00:00Z",
  "upgrade_url": "https://register.vindexgateway.com/portal",
  "already_counted": false
}

Per-IP limits on the public CSV endpoints return 429 Too Many Requests with a Retry-After header. Distinguish quota exhaustion by the code field, never by the status alone.

Story identity & billing

Billing is per unique story, once. Send a stable external_id with each request (your own record id) and every later score of that id is a free re-score, in any month. Without an external_id, Vindex falls back to a normalized fingerprint of the story's content: unchanged content stays free, edited content bills as a new story. Responses disclose the applied rule in identity_resolution (external_id or content_hash) and echo the resolved story_identity, so the billing unit is always visible at the point of use.

Error shape

Statistics endpoints (/stats/*) return every error in one envelope, with nothing else in the body:

Statistics error body
json
{
  "error": {
    "code": "DATE_RANGE_TOO_LONG",
    "message": "The date range cannot be longer than 365 days.",
    "request_id": "req_2f9c1a7e4b8d4c0f9a3e6d5b7c1f2a3b"
  }
}

Branch on code, never on message: codes are stable, and each always comes with the same HTTP status. The same request_idis returned in theX-Request-Idheader on every response. Include it when you contact support.

StatusCodeMeaning
400IDENTIFIER_INVALIDThe issue or project key is not in a valid format for your platform. Treat it as not found.
401UNAUTHENTICATEDNo valid credentials were supplied.
403ACCESS_DENIEDThe credentials are valid but do not permit this tenant or resource.
404NOT_FOUNDThe resource does not exist.
422DATE_RANGE_INCOMPLETEOnly one of from or to was sent. Send both, or neither for the last 30 days.
422DATE_RANGE_INVALIDfrom is after to.
422DATE_RANGE_TOO_LONGThe range is longer than 365 days.
422DATE_FORMAT_INVALIDA date is not in YYYY-MM-DD format.
422PAGINATION_INVALIDpage is below 1, or pageSize is outside 1 to 200.
422FILTER_INVALIDA filter value, such as the activity status, is not supported.
429RATE_LIMITEDToo many requests. Wait for the number of seconds in the Retry-After header.
500INTERNAL_ERRORAn unexpected error. Quote the request_id when reporting it.

Scoring endpoints (/api/v1/*) still use their earlier error body:

Scoring error body
json
{
  "error": "Validation failed",
  "message": "Invalid request format",
  "details": ["Missing required fields: ticket_id or ticket_data"]
}

The unauthenticated CSV endpoints (/evaluate/*) are served directly by the scoring backend and use the framework default shape instead:

CSV endpoint error body
json
{
  "detail": "CSV must have at least 2 columns"
}