Skip to content

API Reference

Base URL: /api/v1

All endpoints except authentication require a valid JWT token in the Authorization header: Bearer <token>

Authentication

Login

POST /api/v1/auth/login

OAuth2 compatible token login, get an access token for future requests.

Request Body (Form Data): * username (string, required): The user's email or username. * password (string, required): The user's password.

Response (200 OK):

{
  "access_token": "string",
  "token_type": "bearer"
}

Response (401 Unauthorized): * Invalid credentials.

Test Token

POST /api/v1/auth/login/test-token

Test access token. Returns the current user.

Response (200 OK): * Returns a User object.

Register

POST /api/v1/auth/register

Register a new user account.

Request Body:

{
  "username": "string",
  "email": "string",
  "password": "string"
}

Response (201 Created): * Returns the created User object.

Response (400 Bad Request): * Username or email already exists.

Change Password

POST /api/v1/auth/change-password

Change the current user's password.

Request Body:

{
  "current_password": "string",
  "new_password": "string"
}

Response (204 No Content): * Password changed successfully.

Response (400 Bad Request): * Weak password.

Response (401 Unauthorized): * Wrong current password.

Refresh Token

POST /api/v1/auth/refresh

Refresh the access token without requiring the user to log in again.

When to Use: - When your access token is about to expire or has expired - To maintain a seamless user experience without forcing re-authentication - For long-running applications or sessions that exceed the token lifetime

Access tokens have a limited lifetime for security reasons. In this backend, access tokens expire after 30 minutes. When a token expires, API requests will return a 401 Unauthorized error. Instead of requiring users to log in again with their credentials, you can use the refresh token to obtain a new access token. This keeps your application secure while maintaining a smooth user experience.

Best Practice: Implement token refresh logic in your client application to automatically refresh tokens before they expire (e.g., refresh at 25 minutes to provide a buffer).

Request Body:

{
  "refresh_token": "string"
}

Response (200 OK):

{
  "access_token": "string",
  "refresh_token": "string"
}

Response (401 Unauthorized): * Refresh token is invalid or expired. User must log in again.

Get Current User

GET /api/v1/auth/me

Get the current authenticated user's profile.

Response (200 OK): * Returns a User object.

Update Profile

PATCH /api/v1/auth/me

Update the current user's profile.

Request Body:

{
  "email": "string",
  "display_name": "string",
  "avatar_url": "string"
}

Response (200 OK): * Returns the updated User object.

Logout

POST /api/v1/auth/logout

Logout the current user.

Response (204 No Content): * Successfully logged out.

Users

Get Users

GET /api/v1/users/

Retrieve users. Requires superuser privileges.

Parameters: * skip (integer, optional, default=0): Number of records to skip. * limit (integer, optional, default=100): Maximum number of records to return.

Response (200 OK): * List of User objects.

Response (403 Forbidden): * User does not have superuser privileges.

Create User

POST /api/v1/users/

Create new user. Requires superuser privileges.

Request Body:

{
  "username": "string",
  "email": "string",
  "password": "string",
  "is_active": true,
  "is_superuser": false
}

Response (200 OK): * Returns the created User object.

Get Current User

GET /api/v1/users/me

Get current user.

Response (200 OK): * Returns the current User object.

Update Current User

PUT /api/v1/users/me

Update own user.

Request Body:

{
  "password": "string",
  "username": "string",
  "email": "string"
}

Response (200 OK): * Returns the updated User object.

Get User by ID

GET /api/v1/users/{user_id}

Get a specific user by id.

Parameters: * user_id (string, required): The UUID of the user.

Response (200 OK): * Returns a User object.

Response (404 Not Found): * User not found.

Update User

PUT /api/v1/users/{user_id}

Update a user. Requires superuser privileges.

Parameters: * user_id (string, required): The UUID of the user.

Request Body:

{
  "username": "string",
  "email": "string",
  "is_active": true,
  "is_superuser": false
}

Response (200 OK): * Returns the updated User object.

Delete User

DELETE /api/v1/users/{user_id}

Delete a user. Requires superuser privileges.

Parameters: * user_id (string, required): The UUID of the user. * transfer_to (string, query, optional): UUID of user to transfer ownership to.

Response (204 No Content): * User deleted successfully.

Response (404 Not Found): * User not found.

Assign Project to User

POST /api/v1/users/{user_id}/assignments

Assign a project to a user.

Parameters: * user_id (string, required): The UUID of the user.

Request Body:

{
  "project_id": "uuid"
}

Response (200 OK): * Project assigned successfully.

Response (404 Not Found): * User or Project not found.

Update User Role

PATCH /api/v1/users/{user_id}/role

Update a user's role. Requires superuser privileges.

Parameters: * user_id (string, required): The UUID of the user.

Request Body:

{
  "role": "root" | "user"
}

Response (200 OK): * Returns the updated User object.

Response (403 Forbidden): * Insufficient permissions.

Get User Projects

GET /api/v1/users/{user_id}/projects

List all projects for a specific user.

Parameters: * user_id (string, required): The UUID of the user.

Response (200 OK): * List of Project objects.

Response (403 Forbidden): * Insufficient permissions.

Projects

Get Projects

GET /api/v1/projects/

Retrieve projects for the current user.

Parameters: * skip (integer, optional, default=0) * limit (integer, optional, default=100) * include_stats (boolean, optional): When true, includes statistics for each project (feature_count, comparison_count, progress for each dimension).

Response (200 OK): * List of Project objects.

When include_stats=true, each project includes additional fields:

{
  "id": "uuid",
  "name": "string",
  "description": "string",
  "created_at": "datetime",
  "feature_count": 10,
  "comparison_count": {
    "complexity": 15,
    "value": 12
  },
  "progress": {
    "complexity": {
      "progress_percent": 75.5,
      "transitive_coverage": 0.8
    },
    "value": {
      "progress_percent": 60.0,
      "transitive_coverage": 0.65
    }
  }
}

Create Project

POST /api/v1/projects/

Create new project.

Request Body:

{
  "name": "string",
  "description": "string",
  "comparison_mode": "binary" | "graded"  // optional, default "binary"
}

Comparison Modes: | Mode | Description | Use Case | |------|-------------|----------| | binary | Simple A vs B choices (feature_a, feature_b, tie) | Quick decisions, low cognitive load | | graded | 5-point scale (a_much_better, a_better, equal, b_better, b_much_better) | More expressive, 30-40% fewer comparisons needed |

Response (201 Created): * Returns the created Project object with comparison_mode field.

Example: Creating a Graded Project

curl -X POST "https://api.oneselect.example.com/v1/projects/" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Graded Project", "description": "Using 5-point scale", "comparison_mode": "graded"}'

Get Project

GET /api/v1/projects/{id}

Get project by ID.

Parameters: * id (string, required): The UUID of the project.

Response (200 OK): * Returns a Project object.

Response (404 Not Found): * Project not found.

Update Project

PUT /api/v1/projects/{id}

Update a project.

Parameters: * id (string, required): The UUID of the project.

Request Body:

{
  "name": "string",
  "description": "string"
}

Response (200 OK): * Returns the updated Project object.

Delete Project

DELETE /api/v1/projects/{id}

Delete a project.

Parameters: * id (string, required): The UUID of the project.

Response (204 No Content): * Project deleted successfully.

Get Project Summary

GET /api/v1/projects/{id}/summary

Get a summary of project statistics and progress.

Parameters: * id (string, required): The UUID of the project.

Response (200 OK):

{
  "project": { ...Project Object... },
  "feature_count": 0,
  "comparison_count": 0,
  "progress": {
    "complexity": 0.0,
    "value": 0.0
  }
}

Get Project Collaborators

GET /api/v1/projects/{id}/collaborators

List all collaborators for a project.

Parameters: * id (string, required): The UUID of the project.

Response (200 OK): * List of collaborator objects with user information and roles.

Get Project Activity

GET /api/v1/projects/{id}/activity

Get activity log for a project.

Parameters: * id (string, required): The UUID of the project. * page (integer, query, optional): Page number. * per_page (integer, query, optional): Items per page. * action_type (string, query, optional): Filter by action type.

Response (200 OK):

{
  "items": [
    {
      "action": "string",
      "timestamp": "datetime",
      "user_id": "uuid"
    }
  ],
  "total": 0,
  "page": 1,
  "per_page": 20
}

Get Last Modified

GET /api/v1/projects/{id}/last-modified

Get the last modification timestamp for a project.

Parameters: * id (string, required): The UUID of the project.

Response (200 OK):

{
  "last_modified": "datetime",
  "modified_by": "uuid"
}

Get Comparison History

GET /api/v1/projects/{id}/history

Get complete audit trail of all comparisons made in a project, including both active and deleted comparisons. This provides a full history for auditing purposes.

Parameters: * id (string, required): The UUID of the project.

Response (200 OK):

{
  "project": {
    "id": "uuid",
    "name": "string",
    "description": "string"
  },
  "comparisons": [
    {
      "id": "uuid",
      "feature_a": {
        "id": "uuid",
        "name": "string"
      },
      "feature_b": {
        "id": "uuid",
        "name": "string"
      },
      "choice": "feature_a" | "feature_b" | "tie",
      "dimension": "complexity" | "value",
      "user": {
        "id": "uuid",
        "username": "string"
      },
      "created_at": "datetime"
    }
  ],
  "deleted_comparisons": [
    {
      "id": "uuid",
      "feature_a": {
        "id": "uuid",
        "name": "string"
      },
      "feature_b": {
        "id": "uuid",
        "name": "string"
      },
      "choice": "string",
      "dimension": "string",
      "user": {
        "id": "uuid",
        "username": "string"
      },
      "created_at": "datetime",
      "deleted_at": "datetime",
      "deleted_by": {
        "id": "uuid",
        "username": "string"
      }
    }
  ]
}

Response (404 Not Found): * Project not found.

Features

All feature endpoints are nested under projects: /api/v1/projects/{project_id}/features

Get Features

GET /api/v1/projects/{project_id}/features

Retrieve features for a project.

Parameters: * project_id (string, required): The UUID of the project. * page (integer, query, optional, default=1) * per_page (integer, query, optional, default=50) * search (string, query, optional): Search term to filter features. * include_scores (boolean, query, optional): When true, includes Bayesian scores (mu, sigma) for each feature in both dimensions.

Response (200 OK):

{
  "items": [ ...Feature Objects... ],
  "total": 0,
  "page": 1,
  "per_page": 50
}

When include_scores=true, each feature includes additional score fields:

{
  "id": "uuid",
  "name": "string",
  "description": "string",
  "tags": ["string"],
  "scores": {
    "complexity": {
      "mu": 0.5,
      "sigma": 1.2
    },
    "value": {
      "mu": 0.8,
      "sigma": 0.9
    }
  }
}

Create Feature

POST /api/v1/projects/{project_id}/features

Create new feature in a project.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "name": "string",
  "description": "string",
  "tags": ["string"]
}

Response (201 Created): * Returns the created Feature object.

Bulk Create Features

POST /api/v1/projects/{project_id}/features/bulk

Create multiple features at once.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "features": [
    {
      "name": "string",
      "description": "string",
      "tags": ["string"]
    }
  ]
}

Response (201 Created):

{
  "count": 0,
  "features": [ ...Feature Objects... ]
}

Bulk Delete Features

POST /api/v1/projects/{project_id}/features/bulk-delete

Delete multiple features at once.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "feature_ids": ["uuid", "uuid"]
}

Response (200 OK):

{
  "deleted_count": 0
}

Get Feature

GET /api/v1/projects/{project_id}/features/{feature_id}

Get feature by ID.

Parameters: * project_id (string, required): The UUID of the project. * feature_id (string, required): The UUID of the feature.

Response (200 OK): * Returns a Feature object.

Response (404 Not Found): * Feature not found.

Update Feature

PUT /api/v1/projects/{project_id}/features/{feature_id}

Update a feature.

Parameters: * project_id (string, required): The UUID of the project. * feature_id (string, required): The UUID of the feature.

Request Body:

{
  "name": "string",
  "description": "string",
  "tags": ["string"]
}

Response (200 OK): * Returns the updated Feature object.

Delete Feature

DELETE /api/v1/projects/{project_id}/features/{feature_id}

Delete a feature.

Parameters: * project_id (string, required): The UUID of the project. * feature_id (string, required): The UUID of the feature.

Response (204 No Content): * Feature deleted successfully.

Comparisons

All comparison endpoints are nested under projects: /api/v1/projects/{project_id}/comparisons

Get Next Comparison Pair

GET /api/v1/projects/{project_id}/comparisons/next

Get the next pair of features to compare. This is the primary endpoint for the comparison workflow.

The endpoint uses a chain-building algorithm that leverages transitive closure for O(N log N) efficiency. When target_certainty is specified, the endpoint returns 204 once the transitive coverage reaches that threshold, enabling early stopping without exhaustive comparisons.

Parameters: * project_id (string, required): The UUID of the project. * dimension (string, query, required): One of "complexity", "value". * target_certainty (number, query, optional, default=0.0): Target transitive coverage level (0.0-1.0). When set to a value > 0, the endpoint returns 204 No Content once transitive coverage reaches this threshold. Common values: 0.7 (70%), 0.8 (80%), 0.9 (90%). When set to 0.0 (default), comparisons continue until all orderings are known via transitivity. * include_progress (boolean, query, optional): When true, includes progress metrics in the response to avoid a separate round-trip to the progress endpoint.

Response (200 OK):

{
  "comparison_id": "uuid",
  "feature_a": { ...Feature Object... },
  "feature_b": { ...Feature Object... },
  "dimension": "complexity" | "value",
  "progress": {  // Only included when include_progress=true
    "dimension": "complexity",
    "target_certainty": 0.9,
    "transitive_coverage": 0.75,
    "effective_confidence": 0.72,
    "progress_percent": 80.0,
    "comparisons_done": 15,
    "comparisons_remaining": 5
  }
}

Response (204 No Content): * Target certainty reached (when target_certainty > 0 and transitive coverage ≥ target), or * All orderings are known via transitive inference, or * No useful comparisons left to make.

Usage Example:

GET /api/v1/projects/{id}/comparisons/next?dimension=complexity&target_certainty=0.9&include_progress=true
This requests the next comparison pair with progress metrics and will return 204 once 90% transitive coverage is achieved.

Efficiency: With transitive closure optimization, reaching 90% certainty for N features typically requires approximately N × log₂(N) comparisons per dimension, rather than the theoretical maximum of N×(N-1)/2 pairwise comparisons.

Get Comparisons

GET /api/v1/projects/{project_id}/comparisons

Retrieve comparison history for a project.

Parameters: * project_id (string, required): The UUID of the project. * dimension (string, query, optional): Filter by dimension. * page (integer, query, optional) * per_page (integer, query, optional) * ids (string, query, optional): Comma-separated list of comparison UUIDs to fetch specific comparisons. When provided, pagination parameters are ignored.

Response (200 OK):

{
  "items": [ ...Comparison Objects... ],
  "total": 0,
  "page": 1,
  "per_page": 20
}

Batch Fetch Example:

GET /api/v1/projects/{id}/comparisons?ids=uuid1,uuid2,uuid3
Returns only the specified comparisons for efficient bulk retrieval.

Create Comparison

POST /api/v1/projects/{project_id}/comparisons

Submit a comparison result. This endpoint performs a Bayesian update to the feature rankings and returns the comparison along with current inconsistency statistics for immediate UI feedback.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "feature_a_id": "uuid",
  "feature_b_id": "uuid",
  "choice": "feature_a" | "feature_b" | "tie",
  "dimension": "complexity" | "value"
}

Response (201 Created):

{
  "id": "uuid",
  "project_id": "uuid",
  "feature_a": {
    "id": "uuid",
    "name": "string"
  },
  "feature_b": {
    "id": "uuid",
    "name": "string"
  },
  "choice": "feature_a" | "feature_b" | "tie",
  "dimension": "complexity" | "value",
  "created_at": "datetime",
  "inconsistency_stats": {
    "cycle_count": 0,
    "total_comparisons": 0,
    "inconsistency_percentage": 0.0,
    "dimension": "complexity" | "value"
  }
}

The inconsistency_stats object provides immediate feedback about the health of the comparison graph, allowing UIs to display warnings when inconsistencies are detected.

Create Binary Comparison

POST /api/v1/projects/{project_id}/comparisons/binary

Submit a binary comparison (A beats B, B beats A, or tie). This endpoint is for projects in binary comparison mode. For graded comparisons, use the /comparisons/graded endpoint.

Parameters: * project_id (string, required): The UUID of the project (must be in binary mode).

Request Body:

{
  "feature_a_id": "uuid",
  "feature_b_id": "uuid",
  "choice": "feature_a" | "feature_b" | "tie",
  "dimension": "complexity" | "value"
}

Response (201 Created):

{
  "id": "uuid",
  "project_id": "uuid",
  "feature_a": {
    "id": "uuid",
    "name": "string"
  },
  "feature_b": {
    "id": "uuid",
    "name": "string"
  },
  "choice": "feature_a" | "feature_b" | "tie",
  "dimension": "complexity" | "value",
  "created_at": "datetime",
  "inconsistency_stats": {
    "cycle_count": 0,
    "total_comparisons": 0,
    "inconsistency_percentage": 0.0,
    "dimension": "complexity" | "value"
  }
}

Response (400 Bad Request): * If the project is in graded mode: {"detail": "Project is in 'graded' mode. Use the graded comparison endpoint."}

Create Graded Comparison

POST /api/v1/projects/{project_id}/comparisons/graded

Submit a graded comparison using a 5-point scale. This endpoint is for projects in graded comparison mode. Graded comparisons provide more information per comparison, allowing 30-40% faster convergence with fewer total comparisons needed.

Strength Values: | Strength | Meaning | Derived Choice | Update Multiplier | |----------|---------|----------------|-------------------| | a_much_better | Feature A is significantly better than B | feature_a | 2.0x (configurable) | | a_better | Feature A is better than B | feature_a | 1.0x | | equal | Features are roughly equal | tie | 1.0x (configurable) | | b_better | Feature B is better than A | feature_b | 1.0x | | b_much_better | Feature B is significantly better than A | feature_b | 2.0x (configurable) |

The "a_much_better" and "b_much_better" options apply a 2.0x multiplier to the Bayesian update, causing faster convergence of scores and reduced variance. The multipliers are configurable in app/core/config.py: - GRADED_MUCH_BETTER_MULTIPLIER (default: 2.0) - for strong preferences - GRADED_EQUAL_MULTIPLIER (default: 1.0) - for ties; reduce if users select "equal" when uncertain

Parameters: * project_id (string, required): The UUID of the project (must be in graded mode).

Request Body:

{
  "feature_a_id": "uuid",
  "feature_b_id": "uuid",
  "dimension": "complexity" | "value",
  "strength": "a_much_better" | "a_better" | "equal" | "b_better" | "b_much_better"
}

Response (201 Created):

{
  "id": "uuid",
  "project_id": "uuid",
  "feature_a": {
    "id": "uuid",
    "name": "string"
  },
  "feature_b": {
    "id": "uuid",
    "name": "string"
  },
  "dimension": "complexity" | "value",
  "strength": "a_much_better" | "a_better" | "equal" | "b_better" | "b_much_better",
  "choice": "feature_a" | "feature_b" | "tie",
  "created_at": "datetime",
  "inconsistency_stats": {
    "cycle_count": 0,
    "total_comparisons": 0,
    "inconsistency_percentage": 0.0,
    "dimension": "complexity" | "value"
  }
}

Response (400 Bad Request): * If the project is in binary mode: {"detail": "Project is in 'binary' mode. Use the binary comparison endpoint."}

Example: Graded vs Binary Comparison Count

For a project with 20 features: - Binary mode: ~60 comparisons to reach 90% certainty - Graded mode: ~40 comparisons to reach 90% certainty (33% reduction)

The stronger signal from "a_much_better"/"b_much_better" judgments (2x multiplier) accelerates learning.

Get Comparison Estimates

GET /api/v1/projects/{project_id}/comparisons/estimates

Get current estimates for all features based on comparisons made.

Parameters: * project_id (string, required): The UUID of the project. * dimension (string, query, required): One of "complexity", "value".

Response (200 OK):

{
  "dimension": "complexity" | "value",
  "estimates": [
    {
      "feature_id": "uuid",
      "estimate": 0.0,
      "variance": 0.0
    }
  ]
}

Get Inconsistency Statistics

GET /api/v1/projects/{project_id}/comparisons/inconsistency-stats

Get a summary of inconsistency statistics without detailed cycle information. This lightweight endpoint is ideal for dashboard widgets, health checks, and polling for updates.

Parameters: * project_id (string, required): The UUID of the project. * dimension (string, query, optional): Filter by dimension ("complexity" or "value"). If omitted, returns stats for both dimensions.

Response (200 OK):

{
  "cycle_count": 0,
  "total_comparisons": 0,
  "inconsistency_percentage": 0.0,
  "dimension": "complexity" | "value"
}

Use Cases: * Dashboard widgets showing project health * Real-time polling for inconsistency alerts * Quick health checks without fetching full cycle details

Get Inconsistencies

GET /api/v1/projects/{project_id}/comparisons/inconsistencies

Detect and return detailed information about inconsistent comparison cycles (e.g., A > B > C > A). This endpoint performs a depth-first search on the comparison graph to identify all cycles, which represent logical inconsistencies where the transitive property of comparisons is violated.

Parameters: * project_id (string, required): The UUID of the project. * dimension (string, query, optional): Filter by dimension ("complexity" or "value"). If omitted, detects cycles in both dimensions.

Response (200 OK):

{
  "cycles": [
    {
      "feature_ids": ["uuid", "uuid", "uuid"],
      "feature_names": ["Feature A", "Feature B", "Feature C"],
      "length": 3,
      "dimension": "complexity" | "value"
    }
  ],
  "count": 0,
  "message": "Found N inconsistency cycles"
}

Algorithm: Uses depth-first search (DFS) with cycle detection. Time complexity: O(V + E) where V is the number of features and E is the number of comparisons. Typical performance: <1ms for 70 features.

Cycle Interpretation: A cycle like [A, B, C] means: A > B > C > A, which is logically inconsistent. The cycle represents a sequence of comparisons where following the "winner" edges leads back to the starting feature.

Get Resolution Pair

GET /api/v1/projects/{project_id}/comparisons/resolve-inconsistency

Get a specific pair of features to compare to resolve detected inconsistencies. Uses the "weakest link" strategy: identifies all cycles, examines pairs in those cycles, and suggests re-comparing the pair with the highest combined uncertainty (σ_i + σ_j). This approach targets the comparison most likely to be incorrect.

Parameters: * project_id (string, required): The UUID of the project. * dimension (string, query, required): The dimension to check ("complexity" or "value").

Response (200 OK):

{
  "comparison_id": null,
  "feature_a": {
    "id": "uuid",
    "name": "string",
    "description": "string"
  },
  "feature_b": {
    "id": "uuid",
    "name": "string",
    "description": "string"
  },
  "dimension": "complexity" | "value",
  "reason": "Weakest link in cycle: highest combined uncertainty",
  "combined_uncertainty": 2.5,
  "cycle_context": {
    "cycle_count": 2,
    "features_in_cycles": ["Feature A", "Feature B", "Feature C", "Feature D"]
  }
}

The cycle_context field provides additional information about the inconsistency state: - cycle_count: Total number of cycles detected in the comparison graph - features_in_cycles: List of unique feature names involved in any cycle, helping users understand which features have conflicting rankings

Response (204 No Content): * No inconsistencies detected in the specified dimension.

Strategy: The weakest link approach assumes that pairs with high uncertainty are more likely to have incorrect comparison results. By re-comparing these pairs, users can potentially break the cycle and restore consistency to the comparison graph.

Get Comparison Progress

GET /api/v1/projects/{project_id}/comparisons/progress

Get progress metrics for comparisons in a project using a hybrid confidence model that combines transitive coverage, Bayesian confidence, and consistency scoring.

Parameters: * project_id (string, required): The UUID of the project. * dimension (string, query, required): One of "complexity", "value". * target_certainty (number, query, optional, default=0.90): Target certainty level (0.0-1.0).

Response (200 OK):

{
  "dimension": "complexity",
  "target_certainty": 0.90,

  "transitive_coverage": 0.85,
  "transitive_known_pairs": 34,
  "uncertain_pairs": 6,

  "direct_coverage": 0.45,
  "unique_pairs_compared": 18,
  "total_possible_pairs": 40,

  "coverage_confidence": 0.45,
  "bayesian_confidence": 0.72,
  "consistency_score": 1.0,
  "effective_confidence": 0.88,
  "progress_percent": 88.0,

  "total_comparisons_done": 22,
  "comparisons_remaining": 3,
  "theoretical_minimum": 22,
  "practical_estimate": 27,

  "current_avg_variance": 0.28,
  "comparisons_done": 22,
  "cycle_count": 0
}

Key Fields: * transitive_coverage: Fraction of pairs with known ordering (0.0-1.0), including those inferred via transitivity. Primary progress metric. * effective_confidence: Combined confidence score considering transitivity, Bayesian updates, and consistency. * uncertain_pairs: Number of pairs whose ordering is still unknown. * theoretical_minimum: Information-theoretic lower bound: ⌈log₂(N!)⌉ * practical_estimate: Expected comparisons needed for target: ~0.77 × N × log₂(N) for 90% target. * cycle_count: Number of detected logical inconsistencies (A>B>C>A cycles).

Reset Comparisons

POST /api/v1/projects/{project_id}/comparisons/reset

Reset all comparisons for a project or dimension.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "dimension": "complexity" | "value"
}

Response (200 OK):

{
  "message": "string",
  "count": 0
}

Undo Last Comparison

POST /api/v1/projects/{project_id}/comparisons/undo

Undo the last comparison made. Returns updated progress metrics for immediate UI feedback.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "dimension": "complexity" | "value"
}

Response (200 OK):

{
  "undone_comparison_id": "uuid",
  "message": "Comparison undone",
  "updated_progress": {
    "comparisons_done": 14,
    "progress_percent": 75.5
  }
}

The updated_progress field provides post-undo progress metrics so the UI can update without an additional API call: - comparisons_done: Number of comparisons remaining after undo - progress_percent: Current progress percentage toward target certainty

Response (404 Not Found): * No comparisons to undo.

Skip Comparison

POST /api/v1/projects/{project_id}/comparisons/skip

Skip a comparison pair without recording a result.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "comparison_id": "uuid"
}

Response (200 OK):

{
  "status": "skipped"
}

Get Comparison

GET /api/v1/projects/{project_id}/comparisons/{comparison_id}

Get comparison by ID.

Parameters: * project_id (string, required): The UUID of the project. * comparison_id (string, required): The UUID of the comparison.

Response (200 OK): * Returns a Comparison object.

Update Comparison

PUT /api/v1/projects/{project_id}/comparisons/{comparison_id}

Update a comparison result.

Parameters: * project_id (string, required): The UUID of the project. * comparison_id (string, required): The UUID of the comparison.

Request Body:

{
  "choice": "feature_a" | "feature_b" | "tie"
}

Response (200 OK): * Returns the updated Comparison object.

Delete Comparison

DELETE /api/v1/projects/{project_id}/comparisons/{comparison_id}

Delete a comparison.

Parameters: * project_id (string, required): The UUID of the project. * comparison_id (string, required): The UUID of the comparison.

Response (204 No Content): * Comparison deleted successfully.

Statistics

All statistics endpoints are nested under projects: /api/v1/projects/{project_id}/statistics

Get Project Statistics

GET /api/v1/projects/{project_id}/statistics

Get comprehensive statistics for a project.

Parameters: * project_id (string, required): The UUID of the project.

Response (200 OK):

{
  "feature_count": 0,
  "comparison_count": 0,
  "dimensions": {
    "complexity": {
      "comparisons": 0,
      "progress": 0.0
    },
    "value": {
      "comparisons": 0,
      "progress": 0.0
    }
  }
}

Get Feature Scores

GET /api/v1/projects/{project_id}/statistics/scores

Get computed scores for all features in a project.

Parameters: * project_id (string, required): The UUID of the project.

Response (200 OK):

[
  {
    "feature_id": "uuid",
    "feature_name": "string",
    "complexity_score": 0.0,
    "value_score": 0.0,
    "ratio": 0.0
  }
]

Results

All results endpoints are nested under projects: /api/v1/projects/{project_id}/results

Get Ranked Results

GET /api/v1/projects/{project_id}/results

Get ranked and scored results for all features.

Parameters: * project_id (string, required): The UUID of the project. * sort_by (string, query, optional): One of "complexity", "value", "ratio". * include_quadrants (boolean, query, optional): When true, includes quadrant analysis in the response.

Response (200 OK):

[
  {
    "feature": { ...Feature Object... },
    "complexity": 0.0,
    "value": 0.0,
    "ratio": 0.0,
    "rank": 1
  }
]

When include_quadrants=true, the response format changes to include both results and quadrant analysis:

{
  "results": [
    {
      "feature": { ...Feature Object... },
      "complexity": 0.0,
      "value": 0.0,
      "ratio": 0.0,
      "rank": 1
    }
  ],
  "quadrants": {
    "quick_wins": [ ...Feature Objects... ],
    "major_projects": [ ...Feature Objects... ],
    "fill_ins": [ ...Feature Objects... ],
    "thankless_tasks": [ ...Feature Objects... ]
  }
}

This allows fetching both ranked results and quadrant categorization in a single API call.

Get Quadrant Analysis

GET /api/v1/projects/{project_id}/results/quadrants

Get features organized into quadrants based on complexity and value.

Parameters: * project_id (string, required): The UUID of the project.

Response (200 OK):

{
  "quick_wins": [ ...Feature Objects... ],
  "major_projects": [ ...Feature Objects... ],
  "fill_ins": [ ...Feature Objects... ],
  "thankless_tasks": [ ...Feature Objects... ]
}

Export Results

GET /api/v1/projects/{project_id}/results/export

Export project results in various formats.

Parameters: * project_id (string, required): The UUID of the project. * format (string, query, optional): One of "json", "csv". Default: "json". * sort_by (string, query, optional): One of "complexity", "value", "ratio".

Response (200 OK): * Content-Type: application/json or text/csv depending on format parameter.

Model Configuration

All model configuration endpoints are nested under projects: /api/v1/projects/{project_id}/model-config

Get Model Configuration

GET /api/v1/projects/{project_id}/model-config

Get the current model configuration for a project.

Parameters: * project_id (string, required): The UUID of the project.

Response (200 OK):

{
  "model_type": "string",
  "num_features": 0,
  "selection_strategy": "string",
  "dimensions": {
    "complexity": {
      "prior_mean": 0.0,
      "prior_variance": 1.0,
      "target_variance": 0.1
    },
    "value": {
      "prior_mean": 0.0,
      "prior_variance": 1.0,
      "target_variance": 0.1
    }
  }
}

Update Model Configuration

PUT /api/v1/projects/{project_id}/model-config

Update the model configuration for a project.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "model_type": "string",
  "selection_strategy": "string",
  "dimensions": {
    "complexity": {
      "prior_mean": 0.0,
      "prior_variance": 1.0,
      "target_variance": 0.1
    }
  }
}

Response (200 OK):

{
  "model_type": "string",
  "num_features": 0,
  "selection_strategy": "string",
  "updated_at": "datetime"
}

Response (400 Bad Request): * Invalid configuration parameters.

Preview Configuration Impact

POST /api/v1/projects/{project_id}/model-config/preview

Preview the impact of configuration changes without applying them.

Parameters: * project_id (string, required): The UUID of the project.

Request Body:

{
  "dimensions": {
    "complexity": {
      "target_variance": 0.05
    }
  }
}

Response (200 OK):

{
  "estimated_comparisons": 0,
  "current_variance": 0.0,
  "target_variance": 0.0
}

Reset Model Configuration

POST /api/v1/projects/{project_id}/model-config/reset

Reset model configuration to default values.

Parameters: * project_id (string, required): The UUID of the project.

Response (200 OK):

{
  "message": "Configuration reset to defaults",
  "config": { ...Config Object... }
}

Admin

All admin endpoints require superuser privileges and are prefixed with /api/v1/admin

Create Database Backup

POST /api/v1/admin/database/backup

Create a backup of the database.

Response (200 OK):

{
  "backup_id": "string",
  "filename": "string",
  "size_bytes": 0,
  "created_at": "datetime"
}

List Database Backups

GET /api/v1/admin/database/backups

List all available database backups.

Response (200 OK):

[
  {
    "backup_id": "string",
    "filename": "string",
    "size_bytes": 0,
    "created_at": "datetime"
  }
]

Download Backup

GET /api/v1/admin/database/backups/{backup_id}

Download a specific backup file.

Parameters: * backup_id (string, required): The ID of the backup.

Response (200 OK): * Content-Type: application/octet-stream * Binary backup file.

Restore Database

POST /api/v1/admin/database/restore

Restore database from a backup.

Request Body:

{
  "backup_id": "string"
}

Response (200 OK):

{
  "message": "Database restored",
  "restored_at": "datetime"
}

Response (503 Service Unavailable): * Service temporarily unavailable during restore.

Get Database Statistics

GET /api/v1/admin/database/stats

Get database statistics and health metrics.

Response (200 OK):

{
  "size_bytes": 0,
  "table_counts": {
    "users": 0,
    "projects": 0,
    "features": 0,
    "comparisons": 0
  },
  "last_vacuum": "datetime",
  "integrity_ok": true
}

Run Database Maintenance

POST /api/v1/admin/database/maintenance

Run database maintenance operations.

Request Body:

{
  "operation": "vacuum" | "integrity_check" | "optimize"
}

Response (200 OK):

{
  "operation": "string",
  "status": "completed",
  "duration_ms": 0
}

Export Database

GET /api/v1/admin/database/export

Bulk export data from the database.

Parameters: * project_id (string, query, optional): Export specific project only. * format (string, query, optional): One of "json", "sql". Default: "json".

Response (200 OK): * Content-Type: application/json or application/sql depending on format.

Import Database

POST /api/v1/admin/database/import

Bulk import data into the database.

Request Body (multipart/form-data): * file (file, required): The import file.

Response (200 OK):

{
  "status": "completed",
  "records_imported": 0,
  "errors": []
}


Data Objects

User Object

{
  "id": "uuid",
  "username": "string",
  "email": "string",
  "is_active": true,
  "is_superuser": false,
  "role": "user" | "root",
  "display_name": "string",
  "avatar_url": "string"
}

Project Object

{
  "id": "uuid",
  "name": "string",
  "description": "string",
  "comparison_mode": "binary" | "graded",
  "created_at": "datetime",
  "owner_id": "uuid"
}

Feature Object

{
  "id": "uuid",
  "name": "string",
  "description": "string",
  "tags": ["string"],
  "project_id": "uuid",
  "created_at": "datetime",
  "updated_at": "datetime"
}

Comparison Object

{
  "id": "uuid",
  "feature_a_id": "uuid",
  "feature_b_id": "uuid",
  "choice": "feature_a" | "feature_b" | "tie",
  "strength": "a_much_better" | "a_better" | "equal" | "b_better" | "b_much_better" | null,
  "dimension": "complexity" | "value",
  "project_id": "uuid",
  "user_id": "uuid",
  "created_at": "datetime"
}

Note: The strength field is only populated for graded comparisons. Binary comparisons have strength: null.

ComparisonWithStats Object

Returned by the Create Comparison endpoint to provide immediate feedback about inconsistencies:

{
  "id": "uuid",
  "project_id": "uuid",
  "feature_a": {
    "id": "uuid",
    "name": "string"
  },
  "feature_b": {
    "id": "uuid",
    "name": "string"
  },
  "choice": "feature_a" | "feature_b" | "tie",
  "strength": "much_better" | "better" | "equal" | "worse" | "much_worse" | null,
  "dimension": "complexity" | "value",
  "created_at": "datetime",
  "inconsistency_stats": {
    "cycle_count": 0,
    "total_comparisons": 0,
    "inconsistency_percentage": 0.0,
    "dimension": "complexity" | "value"
  }
}