estimio

estimio API Documentation

Complete API reference for estimio - Free Online Planning Poker Tool.

Base URL

  • Development: http://localhost:3000
  • Production: https://www.estimio.ch

Authentication

Most endpoints require authentication via JWT token stored in an HTTP-only cookie. Some endpoints support optional authentication (guests can access).

Authentication Methods

  1. Cookie-based (recommended): Token is automatically sent via Cookie header
  2. Token in body: Some endpoints accept token in request body (for compatibility)

Rate Limiting

The following endpoints have rate limiting:

  • Login: 5 attempts per 15 minutes per IP
  • Register: 3 attempts per hour per IP
  • Password Reset: 3 attempts per hour per IP
  • AI Endpoints: 20 requests per minute per user
  • Session Join: 10 requests per minute per IP

Rate limit responses include Retry-After header indicating when to retry.

Response Format

All endpoints return JSON with the following structure:

{
  "success": true|false,
  "data": {...},
  "error": "Error message",
  "message": "Success message"
}

Error Codes

  • 400: Bad Request - Invalid input or validation error
  • 401: Unauthorized - Authentication required
  • 403: Forbidden - Insufficient permissions
  • 404: Not Found - Resource not found
  • 429: Too Many Requests - Rate limit exceeded
  • 500: Internal Server Error - Server error

Endpoints

Authentication

POST /api/auth/register

Register a new user account.

Rate Limit: 3 attempts per hour per IP

Request Body:

{
  "email": "user@example.com",
  "password": "securepassword123"
}

Response (200):

{
  "success": true,
  "message": "Registration successful. Please check your email to verify your account."
}

Errors:

  • 400: Email already registered, invalid input
  • 429: Rate limit exceeded

POST /api/auth/login

Log in to an existing account.

Rate Limit: 5 attempts per 15 minutes per IP

Request Body:

{
  "email": "user@example.com",
  "password": "securepassword123"
}

Response (200):

{
  "success": true,
  "user": {
    "id": "user-id",
    "email": "user@example.com",
    "emailVerified": true,
    "geminiEnabled": false
  },
  "token": "jwt-token-here"
}

Errors:

  • 401: Invalid email or password
  • 403: Email not verified
  • 429: Rate limit exceeded

GET /verify-email/:token

Verify email address via link (redirects to frontend).

Parameters:

  • token: Email verification token

Response: Redirects to frontend with ?success=true or ?error=invalid


POST /api/auth/verify-email/:token

Verify email address via API.

Parameters:

  • token: Email verification token

Response (200):

{
  "success": true,
  "message": "Email verified successfully"
}

Errors:

  • 400: Invalid or expired token

POST /api/auth/forgot-password`

Request password reset email.

Rate Limit: 3 attempts per hour per IP

Request Body:

{
  "email": "user@example.com"
}

Response (200):

{
  "success": true,
  "message": "If an account exists, a password reset email has been sent"
}

Note: Always returns success to prevent email enumeration.


POST /api/auth/reset-password/:token

Reset password using reset token.

Parameters:

  • token: Password reset token

Request Body:

{
  "password": "newsecurepassword123"
}

Response (200):

{
  "success": true,
  "message": "Password reset successfully"
}

Errors:

  • 400: Invalid or expired token, invalid password

GET /api/auth/me`

Get current authenticated user.

Authentication: Optional (returns null if not authenticated)

Response (200):

{
  "success": true,
  "user": {
    "id": "user-id",
    "email": "user@example.com",
    "emailVerified": true,
    "geminiEnabled": false
  }
}

Or if not authenticated:

{
  "success": true,
  "user": null
}

POST /api/auth/logout`

Log out current user (clears auth cookie).

Response (200):

{
  "success": true,
  "message": "Logged out successfully"
}

DELETE /api/auth/account`

Delete user account and all associated data (GDPR compliance).

Authentication: Required

Response (200):

{
  "success": true,
  "message": "Account deleted successfully"
}

Errors:

  • 401: Not authenticated
  • 500: Deletion failed

Settings

PUT /api/settings/gemini`

Enable or disable Gemini AI features.

Authentication: Required

Request Body:

{
  "enabled": true
}

Response (200):

{
  "success": true,
  "geminiEnabled": true
}

Errors:

  • 401: Not authenticated
  • 400: Invalid input (enabled must be boolean)

Jira Integration

GET /api/jira/connections`

Get user's Jira connection configuration.

Authentication: Required

Response (200):

{
  "success": true,
  "connection": {
    "id": "connection-id",
    "type": "cloud",
    "baseUrl": "https://company.atlassian.net",
    "organizationId": "company",
    "username": "user@example.com",
    "storyPointsFieldId": "Story Points"
  }
}

Or if no connection:

{
  "success": true,
  "connection": null
}

POST /api/jira/connections`

Create or update Jira connection.

Authentication: Required

Request Body (Jira Cloud):

{
  "type": "cloud",
  "organizationId": "company",
  "username": "user@example.com",
  "token": "api-token",
  "storyPointsFieldId": "Story Points"
}

Request Body (Jira Server):

{
  "type": "server",
  "baseUrl": "https://jira.company.com",
  "username": "username",
  "password": "password",
  "storyPointsFieldId": "Story Points"
}

Response (200):

{
  "success": true,
  "message": "Jira connection created"
}

Errors:

  • 400: Invalid input, missing required fields
  • 401: Not authenticated

DELETE /api/jira/connections`

Delete Jira connection.

Authentication: Required

Response (200):

{
  "success": true,
  "message": "Jira connection deleted"
}

POST /api/jira/test`

Test Jira connection.

Authentication: Required

Request Body: Same as POST /api/jira/connections`

Response (200):

{
  "success": true,
  "message": "Connection successful"
}

Errors:

  • 400: Connection failed, invalid credentials
  • 401: Not authenticated

GET /api/jira/projects`

Get list of Jira projects.

Authentication: Required

Query Parameters:

  • connectionId (optional): Connection ID to use

Response (200):

{
  "success": true,
  "projects": [
    {
      "id": "10000",
      "key": "PROJ",
      "name": "Project Name"
    }
  ]
}

GET /api/jira/sprints`

Get list of sprints for a project.

Authentication: Required

Query Parameters:

  • projectKey: Jira project key (e.g., "PROJ")
  • connectionId (optional): Connection ID to use

Response (200):

{
  "success": true,
  "sprints": [
    {
      "id": 123,
      "name": "Sprint 1",
      "state": "active"
    }
  ]
}

GET /api/jira/issues`

Get Jira issues for a sprint or project.

Authentication: Required

Query Parameters:

  • projectKey: Jira project key
  • sprintId (optional): Sprint ID
  • connectionId (optional): Connection ID to use

Response (200):

{
  "success": true,
  "issues": [
    {
      "key": "PROJ-123",
      "summary": "Issue title",
      "description": "Issue description",
      "storyPoints": 5
    }
  ]
}

AI Features

POST /api/ai/summarize`

Generate AI summary for a story (legacy endpoint).

Authentication: Required
Rate Limit: 20 requests per minute per user

Request Body:

{
  "issueKey": "PROJ-123",
  "summary": "Story summary",
  "description": "Story description"
}

Response (200):

{
  "success": true,
  "summary": "AI-generated summary text"
}

Errors:

  • 400: Invalid input, AI API error
  • 401: Not authenticated
  • 429: Rate limit exceeded

POST /api/ai/summary`

Generate structured AI summary for a Jira story.

Authentication: Required
Rate Limit: 20 requests per minute per user

Request Body:

{
  "key": "PROJ-123",
  "summary": "Story summary",
  "description": "Story description",
  "acceptanceCriteria": "Acceptance criteria",
  "comments": []
}

Response (200):

{
  "success": true,
  "summary": {
    "oneSentenceSummary": "Summary text",
    "keyConstraints": ["Constraint 1", "Constraint 2"],
    "acceptanceCriteria": "Condensed criteria",
    "risksDependencies": ["Risk 1"],
    "technicalDifficulty": 5
  }
}

POST /api/ai/complexity`

Predict story complexity and estimate story points.

Authentication: Required
Rate Limit: 20 requests per minute per user

Request Body:

{
  "storyTitle": "Story title",
  "description": "Story description",
  "acceptanceCriteria": "Acceptance criteria"
}

Response (200):

{
  "success": true,
  "prediction": {
    "suggestedPoints": "8",
    "confidence": "high",
    "reasoning": "Reasoning text",
    "similarStoriesCount": 3,
    "teamAverage": "7.5"
  }
}

Sessions

POST /api/sessions`

Create a new planning session.

Authentication: Optional (logged-in users can save sessions)

Request Body:

{
  "moderatorName": "John Doe",
  "title": "Sprint Planning"
}

Response (200):

{
  "success": true,
  "session": {
    "code": "ABC123",
    "moderatorName": "John Doe",
    "participants": [],
    "storyTitle": "",
    "votesRevealed": false,
    "createdAt": 1234567890
  }
}

POST /api/sessions/:code/join`

Join an existing session.

Rate Limit: 10 requests per minute per IP

Parameters:

  • code: Session code

Request Body:

{
  "name": "Participant Name"
}

Response (200):

{
  "success": true,
  "participantId": "participant-id",
  "session": {
    "code": "ABC123",
    "moderatorName": "John Doe",
    "participants": [...],
    "storyTitle": "Story title",
    "votesRevealed": false
  }
}

Errors:

  • 404: Session not found
  • 400: Invalid input
  • 429: Rate limit exceeded

GET /api/sessions/:code

Get session details.

Parameters:

  • code: Session code

Response (200):

{
  "success": true,
  "session": {
    "code": "ABC123",
    "moderatorName": "John Doe",
    "participants": [...],
    "storyTitle": "Story title",
    "votesRevealed": false,
    "issues": [],
    "currentIssueIndex": 0
  }
}

Errors:

  • 404: Session not found

POST /api/sessions/:code/vote`

Submit a vote for the current story.

Parameters:

  • code: Session code

Request Body:

{
  "participantId": "participant-id",
  "vote": "8"
}

Response (200):

{
  "success": true,
  "session": {
    "code": "ABC123",
    "participants": [...],
    "votesRevealed": false
  }
}

Errors:

  • 404: Session not found
  • 400: Invalid vote, participant not found

POST /api/sessions/:code/reveal`

Reveal all votes for the current story.

Parameters:

  • code: Session code

Request Body:

{
  "participantId": "participant-id"
}

Response (200):

{
  "success": true,
  "session": {
    "code": "ABC123",
    "votesRevealed": true,
    "participants": [...]
  }
}

Errors:

  • 404: Session not found
  • 403: Not moderator

POST /api/sessions/:code/reset`

Reset votes for the current story.

Parameters:

  • code: Session code

Request Body:

{
  "participantId": "participant-id"
}

Response (200):

{
  "success": true,
  "session": {
    "code": "ABC123",
    "votesRevealed": false,
    "participants": [...]
  }
}

Errors:

  • 404: Session not found
  • 403: Not moderator

POST /api/sessions/:code/next-story`

Move to the next story in the session.

Parameters:

  • code: Session code

Request Body:

{
  "participantId": "participant-id"
}

Response (200):

{
  "success": true,
  "session": {
    "code": "ABC123",
    "currentIssueIndex": 1,
    "storyTitle": "Next story title",
    "votesRevealed": false,
    "participants": [...]
  }
}

Errors:

  • 404: Session not found
  • 403: Not moderator

POST /api/sessions/:code/ai-vote`

Trigger AI to vote on the current story.

Parameters:

  • code: Session code

Request Body:

{
  "participantId": "participant-id"
}

Response (200):

{
  "success": true,
  "session": {
    "code": "ABC123",
    "participants": [...]
  }
}

Errors:

  • 404: Session not found
  • 403: Not moderator or AI not enabled
  • 400: AI voting failed

PUT /api/sessions/:code/story`

Update the current story title.

Parameters:

  • code: Session code

Request Body:

{
  "participantId": "participant-id",
  "storyTitle": "New story title"
}

Response (200):

{
  "success": true,
  "session": {
    "code": "ABC123",
    "storyTitle": "New story title"
  }
}

Errors:

  • 404: Session not found
  • 400: Invalid input

POST /api/sessions/:code/leave`

Leave a session.

Parameters:

  • code: Session code

Request Body:

{
  "participantId": "participant-id"
}

Response (200):

{
  "success": true,
  "message": "Left session successfully"
}

Health Check

GET /health

Health check endpoint for Cloud Run.

Response (200):

{
  "status": "ok",
  "timestamp": "2024-01-01T00:00:00.000Z"
}

Real-time Updates

estimio uses HTTP polling for real-time updates (firewall-friendly). The client polls GET /api/sessions/:code every 2 seconds to receive session state updates.

Webhooks

Jira Webhook (Optional)

estimio supports Jira webhooks for automated AI summary generation when story points are updated in Jira.

Webhook URL: https://www.estimio.ch/jira/webhook

Events: Issue Updated (when story points field changes)

Payload: Standard Jira webhook payload


Rate Limiting

Rate limits are enforced per IP or per user (for authenticated endpoints). When rate limit is exceeded:

  • Status Code: 429 Too Many Requests
  • Headers: Retry-After (seconds until retry allowed)
  • Response: {"success": false, "error": "Too many requests, please try again later."}

CORS

CORS is configured to allow requests from:

  • Production domain: https://www.estimio.ch
  • Development: http://localhost:3000

Security

  • All API endpoints use HTTPS in production
  • JWT tokens expire after 1 hour
  • Passwords are hashed using bcrypt
  • Rate limiting prevents abuse
  • Input validation using Zod schemas
  • SQL injection prevention via parameterized queries

Last updated: January 2025

API Version: 1.0

Base URL: https://www.estimio.ch

Cookie Preferences

We use cookies to enhance your experience. Essential cookies are required for the service to function. Analytics cookies help us improve the service by understanding how you use it. IP addresses are anonymized and we respect Do Not Track settings. Learn more