This document describes all available API endpoints for the Grainlify backend. Use this as a reference when integrating the frontend.
To protect the services from denial-of-service (DoS) attacks and memory exhaustion, the API enforces strict limits on request body sizes:
- Global Limit: Configurable via
MAX_BODY_BYTES(defaulting to 1 MB). Any standard endpoint will reject request bodies exceeding this limit. - GitHub Webhook Limit: Allowed up to 10 MB to accommodate larger GitHub webhook payloads.
When a request body exceeds the allowed size limit, the API returns a 413 Request Entity Too Large status with the standard error shape:
{
"error": "request_entity_too_large",
"message": "Request Entity Too Large",
"request_id": "req_..."
}Most endpoints require authentication using a JWT (JSON Web Token). Include the token in the Authorization header:
Authorization: Bearer <your_jwt_token>
- Sign in with GitHub (see GitHub OAuth section)
- The JWT token is returned in the response
- Store the token and include it in subsequent requests
- Health & Status
- Authentication
- User Profile
- GitHub OAuth
- KYC Verification
- Projects
- Public Projects
- Ecosystems
- Admin
Check if the API server is running.
Authentication: None required
Response:
{
"ok": true,
"service": "grainlify-api"
}Check if the API is ready (database connectivity check).
Authentication: None required
Response:
{
"ok": true,
"db": "connected"
}Error Response (503):
{
"ok": false,
"db": "disconnected"
}Generate a nonce for wallet signature login.
Authentication: None required
Request Body:
{
"wallet_type": "evm",
"address": "0x123..."
}Response:
{
"nonce": "base64-encoded-nonce",
"message": "Grainlify login. Nonce: base64-encoded-nonce",
"expires_at": "2026-01-01T00:00:00Z"
}Verify a wallet signature and obtain a JWT.
Authentication: None required
Request Body:
{
"wallet_type": "evm",
"address": "0x123...",
"nonce": "base64-encoded-nonce",
"signature": "0xabc...",
"public_key": "optional-public-key"
}Response:
{
"token": "jwt-token-string",
"user": {
"id": "user-uuid",
"role": "contributor"
},
"wallet": {
"wallet_type": "evm",
"address": "0x123..."
}
}Get current authenticated user information.
Authentication: Required (JWT)
Response:
{
"id": "8420cb43-eb78-4aa8-b8fb-9d3ab0e2d7c8",
"role": "contributor"
}Error Responses:
401 Unauthorized- Invalid or missing JWT token503 Service Unavailable- Database not configured
Get user profile statistics including contribution count, most active languages, and ecosystems.
Authentication: Required (JWT)
Response:
{
"contributions_count": 165,
"languages": [
{
"language": "TypeScript",
"contribution_count": 45
},
{
"language": "Go",
"contribution_count": 30
}
],
"ecosystems": [
{
"ecosystem_name": "Starknet",
"contribution_count": 120
},
{
"ecosystem_name": "Ethereum",
"contribution_count": 45
}
]
}Notes:
- Only counts contributions to verified projects in our system
- Returns empty arrays if user has no GitHub account linked
- Languages and ecosystems are limited to top 10
Get daily contribution counts for the last 365 days (for contribution heatmap/calendar visualization).
Authentication: Required (JWT)
Response:
{
"calendar": [
{
"date": "2024-01-15",
"count": 5,
"level": 3
},
{
"date": "2024-01-16",
"count": 0,
"level": 0
},
{
"date": "2024-01-17",
"count": 12,
"level": 4
}
// ... 365 days total
],
"total": 470
}Field Descriptions:
date: ISO date string (YYYY-MM-DD)count: Number of contributions on that day (issues + PRs)level: Color level (0-4) for visualization0= No contributions (lightest color)1= Low activity2= Medium activity3= High activity4= Very high activity (darkest color)
Notes:
- Only includes contributions to verified projects
- Returns empty calendar if user has no GitHub account
- Level calculation uses quartiles of max count (similar to GitHub)
Get paginated list of individual contributions (issues and PRs) for the authenticated user.
Authentication: Required (JWT)
Query Parameters:
limit(optional, default: 50, max: 100) - Number of results per pageoffset(optional, default: 0) - Pagination offset
Example Request:
GET /profile/activity?limit=50&offset=0
Response:
{
"activities": [
{
"type": "pull_request",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"number": 2341,
"title": "Implement caching layer",
"url": "https://github.com/owner/repo/pull/2341",
"date": "2025-11-15",
"month_year": "November 2025",
"project_name": "owner/repo",
"project_id": "79caaf9a-f1e6-4da0-be79-52c5bee169e1"
},
{
"type": "issue",
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"number": 8923,
"title": "Add unit tests for API endpoints",
"url": "https://github.com/owner/repo/issues/8923",
"date": "2025-11-22",
"month_year": "November 2025",
"project_name": "owner/repo",
"project_id": "79caaf9a-f1e6-4da0-be79-52c5bee169e1"
}
],
"total": 165,
"limit": 50,
"offset": 0
}Field Descriptions:
type: Either"issue"or"pull_request"id: Contribution UUIDnumber: GitHub issue/PR numbertitle: Contribution titleurl: GitHub URLdate: ISO date string (YYYY-MM-DD)month_year: Formatted month/year (e.g., "November 2025")project_name: Repository full name (owner/repo)project_id: Project UUID
Notes:
- Ordered by date descending (most recent first)
- Only includes contributions to verified projects
- Returns empty array if user has no GitHub account
Get projects the user has contributed to (via issues or PRs).
Authentication: Required (JWT)
Query Parameters:
user_id(optional) - View another user's contributed projects by UUIDlogin(optional) - View another user's contributed projects by GitHub loginlimit(optional, default: 10, max: 100) - Number of results per pageoffset(optional, default: 0) - Pagination offset
Example Request:
GET /profile/projects?limit=10&offset=0
Response:
[
{
"id": "79caaf9a-f1e6-4da0-be79-52c5bee169e1",
"github_full_name": "owner/repo",
"status": "verified",
"ecosystem_name": "Starknet",
"language": "Cairo",
"owner_avatar_url": "https://avatars.githubusercontent.com/u/123?s=128"
}
]Notes:
- Ordered by repository full name (ascending)
- Response is a bare JSON array for backward compatibility;
limit/offsetpage the underlying query so contributions beyond the first slice are reachable limitis clamped to the max and a negativeoffsetreturns400- Returns an empty array if the user has no GitHub account
Start GitHub OAuth login/signup flow. Redirects user to GitHub for authorization.
Authentication: None required
Response: HTTP 302 redirect to GitHub OAuth page
Flow:
- User is redirected to GitHub
- User authorizes the application
- GitHub redirects back to
/auth/github/callback - Backend processes OAuth and redirects to frontend with JWT token
GitHub OAuth callback endpoint (handled automatically by backend).
Authentication: None required
Note: This endpoint is called by GitHub after user authorization. The backend will redirect to the frontend with the JWT token.
Link GitHub account to existing authenticated user (legacy endpoint, still available).
Authentication: Required (JWT)
Response: HTTP 302 redirect to GitHub OAuth page
Check GitHub account linking status for the authenticated user.
Authentication: Required (JWT)
Response:
{
"linked": true,
"github": {
"id": 92681651,
"login": "Jagadeeshftw"
}
}If not linked:
{
"linked": false
}Start a new KYC verification session using Didit.
Authentication: Required (JWT)
Request Body: None
Response:
{
"session_id": "871e9803-178d-4290-a36b-bd2f09901e57",
"url": "https://verify.didit.me/session/OcTUSqkMkW7Q"
}Error Responses:
409 Conflict- User already has an active KYC session{ "error": "kyc_session_exists", "message": "You already have an active KYC verification session (status: pending). Please complete it or contact admin to delete it.", "session_id": "871e9803-178d-4290-a36b-bd2f09901e57", "status": "pending", "url": "https://verify.didit.me/session/OcTUSqkMkW7Q" }503 Service Unavailable- KYC not configured (missing DIDIT_API_KEY or DIDIT_WORKFLOW_ID)
Notes:
- Only one active session per user is allowed
- If a previous session exists, the endpoint returns 409 with the existing session URL
- User should visit the
urlto complete verification
Get current KYC verification status for the authenticated user.
Authentication: Required (JWT)
Response:
{
"status": "verified",
"session_id": "871e9803-178d-4290-a36b-bd2f09901e57",
"verified_at": "2025-12-31T03:07:27.273068+05:30",
"data": {
"decision": {},
"data": {
"id_verification": {
"full_name": "Jagadeesh",
"date_of_birth": "2002-06-26",
"age": 23,
"address": "1/136, School Street...",
"document_type": "Identity Card",
"document_number": "382195807643",
"status": "Approved"
},
"face_match": {
"score": 26.89,
"status": "In Review"
}
},
"extracted": {
"full_name": "Jagadeesh",
"date_of_birth": "2002-06-26",
"age": 23,
"address": "1/136, School Street...",
"document_type": "Identity Card",
"document_number": "382195807643",
"id_verification_status": "Approved",
"face_match_score": 26.89,
"face_match_status": "In Review"
},
"session_url": "https://verify.didit.me/session/OcTUSqkMkW7Q"
},
"extracted": {
"full_name": "Jagadeesh",
"date_of_birth": "2002-06-26",
"age": 23,
"address": "1/136, School Street...",
"document_type": "Identity Card",
"document_number": "382195807643",
"id_verification_status": "Approved",
"face_match_score": 26.89,
"face_match_status": "In Review"
}
}Status Values:
null- No KYC session started"not_started"- Session created but user hasn't clicked verification link"pending"- User has started verification (clicked link, submitted documents)"in_review"- Didit is actively reviewing the verification"verified"- KYC approved"rejected"- KYC declined"expired"- Session expired or deleted
Rejected Status Response:
{
"status": "rejected",
"session_id": "871e9803-178d-4290-a36b-bd2f09901e57",
"verified_at": null,
"rejection_reason": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.",
"extracted": {
"rejection_reasons": [
"The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch."
]
}
}Notes:
- Status is automatically synced with Didit API
- If session is deleted in Didit dashboard, status updates to
"expired" extractedfield contains structured KYC information for easy display
Register a new GitHub repository as a project.
Authentication: Required (JWT)
Request Body:
{
"github_full_name": "owner/repo",
"ecosystem_name": "Starknet",
"language": "TypeScript",
"tags": ["good first issue", "help wanted"],
"category": "Frontend"
}Field Descriptions:
github_full_name(required): Repository full name (owner/repo)ecosystem_name(required): Name of an active ecosystem (must exist in database)language(optional): Programming languagetags(optional): Array of tag stringscategory(optional): Project category
Response:
{
"id": "79caaf9a-f1e6-4da0-be79-52c5bee169e1",
"github_full_name": "owner/repo",
"status": "pending_verification",
"ecosystem_name": "Starknet",
"language": "TypeScript",
"tags": ["good first issue", "help wanted"],
"category": "Frontend",
"created_at": "2025-12-30T21:25:50.85241+05:30",
"updated_at": "2025-12-30T21:25:50.85241+05:30"
}Error Responses:
400 Bad Request- Invalid request (missing required fields, ecosystem not found)401 Unauthorized- Invalid or missing JWT token
Get all projects owned by the authenticated user.
Authentication: Required (JWT)
Response:
[
{
"id": "79caaf9a-f1e6-4da0-be79-52c5bee169e1",
"github_full_name": "owner/repo",
"github_repo_id": 1038118146,
"status": "verified",
"ecosystem_name": "Starknet",
"language": "TypeScript",
"tags": ["good first issue", "help wanted"],
"category": "Frontend",
"verification_error": null,
"verified_at": "2025-12-30T22:52:00.3484+05:30",
"webhook_created_at": "2025-12-30T21:30:18.524427+05:30",
"webhook_id": 588988804,
"webhook_url": "https://slfs8kjg75.loclx.io/webhooks/github",
"created_at": "2025-12-30T21:25:50.85241+05:30",
"updated_at": "2025-12-30T22:52:00.3484+05:30"
}
]Status Values:
"pending_verification"- Project created but not yet verified"verified"- Project verified and webhook enabled"rejected"- Project verification failed
Verify project ownership and enable GitHub webhook.
Authentication: Required (JWT)
URL Parameters:
id- Project UUID
Request Body: None
Response:
{
"id": "79caaf9a-f1e6-4da0-be79-52c5bee169e1",
"status": "verified",
"verified_at": "2025-12-30T22:52:00.3484+05:30",
"webhook_id": 588988804,
"webhook_url": "https://slfs8kjg75.loclx.io/webhooks/github"
}Error Responses:
400 Bad Request- Verification failed (seeverification_errorin response)404 Not Found- Project not found503 Service Unavailable- Webhook not configured (missing PUBLIC_BASE_URL or GITHUB_WEBHOOK_SECRET)
Notes:
- Requires PUBLIC_BASE_URL and GITHUB_WEBHOOK_SECRET to be configured
- Verifies user has admin access to the repository
- Creates GitHub webhook for the repository
Enqueue a full sync job for a project (syncs issues and PRs from GitHub).
Authentication: Required (JWT)
URL Parameters:
id- Project UUID
Request Body: None
Response:
{
"ok": true,
"message": "Sync job enqueued"
}Notes:
- Sync runs asynchronously
- Use
/projects/:id/sync/jobsto check sync status
Get sync jobs for a project.
Authentication: Required (JWT)
URL Parameters:
id- Project UUID
Query Parameters:
limit(optional, default: 50, max: 200) - Number of results per pageoffset(optional, default: 0) - Pagination offset
Example Request:
GET /projects/:id/sync/jobs?limit=50&offset=0
Response:
{
"jobs": [
{
"id": "job-uuid",
"job_type": "sync_issues",
"status": "completed",
"run_at": "2025-12-30T22:56:03.058032+05:30",
"attempts": 1,
"last_error": null,
"created_at": "2025-12-30T22:56:03.058032+05:30",
"updated_at": "2025-12-30T22:56:03.058032+05:30"
}
],
"total": 12,
"limit": 50,
"offset": 0
}Notes:
- Ordered by creation date (newest first)
totalis the job count for the project; use it withlimit/offsetto pagelimitis clamped to the max and a negativeoffsetreturns400
Status Values:
"pending"- Job queued, not started"running"- Job in progress"completed"- Job finished successfully"failed"- Job failed (checklast_error)
Get issues for a project.
Authentication: Required (JWT)
URL Parameters:
id- Project UUID
Response:
[
{
"id": "issue-uuid",
"github_issue_id": 3770820248,
"number": 1,
"state": "open",
"title": "Button not responding on click",
"description": "The submit button does not respond when clicked...",
"author_login": "1nonlypiece",
"url": "https://github.com/owner/repo/issues/1",
"assignees": [
{
"login": "1nonlypiece"
}
],
"labels": [
{
"name": "bug",
"color": "d73a4a"
},
{
"name": "documentation",
"color": "0075ca"
}
],
"comments_count": 1,
"comments": [
{
"id": 1234567890,
"user": {
"login": "commenter"
},
"body": "I can reproduce this issue...",
"created_at": "2025-12-30T23:02:40.298969+05:30"
}
],
"created_at_github": "2025-12-30T22:56:03.058032+05:30",
"updated_at_github": "2025-12-30T22:56:03.058032+05:30",
"closed_at_github": null
}
]Notes:
- Returns latest 50 issues
- Includes assignees, labels, and comments
- Only includes issues from verified projects
Get pull requests for a project.
Authentication: Required (JWT)
URL Parameters:
id- Project UUID
Response:
[
{
"id": "pr-uuid",
"github_pr_id": 1234567890,
"number": 42,
"state": "open",
"title": "Add new feature",
"body": "This PR adds...",
"author_login": "contributor",
"url": "https://github.com/owner/repo/pull/42",
"merged": false,
"created_at_github": "2025-12-30T22:56:03.058032+05:30",
"updated_at_github": "2025-12-30T22:56:03.058032+05:30",
"merged_at_github": null,
"closed_at_github": null
}
]Notes:
- Returns latest 50 PRs
- Only includes PRs from verified projects
Get webhook events for a project.
Authentication: Required (JWT)
URL Parameters:
id- Project UUID
Response:
[
{
"delivery_id": "abc123",
"event": "issues",
"action": "opened",
"received_at": "2025-12-30T22:56:03.058032+05:30"
}
]Get a filtered list of verified projects (public endpoint).
Authentication: None required
Query Parameters:
ecosystem(optional) - Filter by ecosystem name (case-insensitive)language(optional) - Filter by programming languagecategory(optional) - Filter by categorytags(optional) - Comma-separated list of tags (project must have ALL tags)limit(optional, default: 50, max: 200) - Number of results per pageoffset(optional, default: 0) - Pagination offset
Example Request:
GET /projects?ecosystem=Starknet&language=TypeScript&tags=good first issue,help wanted&limit=50&offset=0
Response:
{
"projects": [
{
"id": "79caaf9a-f1e6-4da0-be79-52c5bee169e1",
"github_full_name": "owner/repo",
"language": "TypeScript",
"tags": ["good first issue", "help wanted"],
"category": "Frontend",
"ecosystem_name": "Starknet",
"ecosystem_slug": "starknet",
"created_at": "2025-12-30T21:25:50.85241+05:30",
"updated_at": "2025-12-30T22:52:00.3484+05:30"
}
],
"total": 150,
"limit": 50,
"offset": 0
}Notes:
- Only returns verified projects
- Multiple filters are combined with AND logic
- Tags filter requires project to have ALL specified tags
Get available filter options (languages, categories, tags) from verified projects.
Authentication: None required
Response:
{
"languages": ["Go", "TypeScript", "JavaScript", "Python"],
"categories": ["Frontend", "Backend", "Full Stack"],
"tags": ["good first issue", "help wanted", "documentation", "bug"]
}Notes:
- Returns distinct values from verified projects only
- Useful for populating filter dropdowns
Get list of active ecosystems with project and user counts (public endpoint).
Authentication: None required
Response:
{
"ecosystems": [
{
"id": "ecosystem-uuid",
"slug": "starknet",
"name": "Starknet",
"description": "A permissionless Validity-Rollup Layer 2 network",
"website_url": "https://www.starknet.io",
"status": "active",
"project_count": 45,
"user_count": 23,
"created_at": "2025-12-30T21:25:50.85241+05:30",
"updated_at": "2025-12-30T22:52:00.3484+05:30"
}
]
}Notes:
- Only returns active ecosystems
project_countanduser_countare computed dynamically- Useful for populating ecosystem dropdowns
All admin endpoints require:
- Valid JWT token
- User role must be
"admin"
Bootstrap the first admin user (or promote current user to admin if already admin).
Authentication: Required (JWT) + X-Admin-Bootstrap-Token header
Headers:
Authorization: Bearer <jwt_token>X-Admin-Bootstrap-Token: <ADMIN_BOOTSTRAP_TOKEN>
Request Body: None
Response:
{
"ok": true,
"token": "new_jwt_token_with_admin_role",
"role": "admin"
}Error Responses:
401 Unauthorized- Invalid bootstrap token403 Forbidden- Admin already exists and user is not admin503 Service Unavailable- Bootstrap not configured
Notes:
- Only works if there are 0 admins in the database, OR the user is already an admin
- Returns a new JWT token with updated role (use this token for subsequent requests)
Get list of users (admin only).
Authentication: Required (JWT, admin role)
Query Parameters:
limit(optional, default: 50, max: 200) - Number of results per pageoffset(optional, default: 0) - Pagination offset
Example Request:
GET /admin/users?limit=50&offset=0
Response:
{
"users": [
{
"id": "8420cb43-eb78-4aa8-b8fb-9d3ab0e2d7c8",
"role": "contributor",
"github_user_id": 92681651,
"created_at": "2025-12-30T21:25:50.85241+05:30",
"updated_at": "2025-12-30T22:52:00.3484+05:30"
}
],
"total": 165,
"limit": 50,
"offset": 0
}Notes:
- Ordered by creation date (newest first)
totalis the unfiltered user count; use it withlimit/offsetto pagelimitis clamped to the max and a negativeoffsetreturns400
Update user role (admin only).
Authentication: Required (JWT, admin role)
URL Parameters:
id- User UUID
Request Body:
{
"role": "maintainer"
}Valid Roles:
"contributor"- Default role"maintainer"- Can manage projects"admin"- Full access
Response:
{
"ok": true
}Error Responses:
400 Bad Request- Invalid role404 Not Found- User not found
Get all ecosystems (admin only, includes inactive).
Authentication: Required (JWT, admin role)
Response:
{
"ecosystems": [
{
"id": "ecosystem-uuid",
"slug": "starknet",
"name": "Starknet",
"description": "A permissionless Validity-Rollup Layer 2 network",
"website_url": "https://www.starknet.io",
"status": "active",
"project_count": 45,
"user_count": 23,
"created_at": "2025-12-30T21:25:50.85241+05:30",
"updated_at": "2025-12-30T22:52:00.3484+05:30"
}
]
}Notes:
- Includes both active and inactive ecosystems
project_countanduser_countare computed dynamically
Create a new ecosystem (admin only).
Authentication: Required (JWT, admin role)
Request Body:
{
"name": "Ethereum",
"description": "A decentralized platform for smart contracts",
"website_url": "https://ethereum.org",
"status": "active"
}Field Descriptions:
name(required) - Ecosystem name (slug is auto-generated)description(optional) - Ecosystem descriptionwebsite_url(optional) - Ecosystem website URLstatus(required) - Either"active"or"inactive"
Response:
{
"id": "ecosystem-uuid",
"slug": "ethereum",
"name": "Ethereum",
"description": "A decentralized platform for smart contracts",
"website_url": "https://ethereum.org",
"status": "active",
"project_count": 0,
"user_count": 0,
"created_at": "2025-12-30T21:25:50.85241+05:30",
"updated_at": "2025-12-30T21:25:50.85241+05:30"
}Error Responses:
400 Bad Request- Missing required fields or invalid status
Update an ecosystem (admin only).
Authentication: Required (JWT, admin role)
URL Parameters:
id- Ecosystem UUID
Request Body:
{
"name": "Ethereum",
"description": "Updated description",
"website_url": "https://ethereum.org",
"status": "active"
}Response:
{
"id": "ecosystem-uuid",
"slug": "ethereum",
"name": "Ethereum",
"description": "Updated description",
"website_url": "https://ethereum.org",
"status": "active",
"project_count": 45,
"user_count": 23,
"created_at": "2025-12-30T21:25:50.85241+05:30",
"updated_at": "2025-12-30T22:52:00.3484+05:30"
}Error Responses:
400 Bad Request- Invalid request404 Not Found- Ecosystem not found
GitHub webhook receiver (for GitHub to send events).
Authentication: None required (uses webhook secret for verification)
Note: This endpoint is called by GitHub, not by the frontend.
Didit KYC webhook receiver (for Didit to send status updates).
Authentication: None required
Note: This endpoint is called by Didit, not by the frontend.
All endpoints return errors in a consistent JSON envelope. Every error response includes a request_id (from the X-Request-Id response header) for support and debugging.
{
"error": "error_code",
"message": "Human-readable error message (optional)",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}Handler-returned errors and recovered panics are mapped through the global Fiber ErrorHandler:
- 4xx — status code is preserved;
erroris a stable machine-readable code. - 5xx — clients receive
error: "internal_server_error"and an opaque message; internal details and stack traces are logged server-side only (never in the response body).
Unmatched routes return 404 with the same envelope plus the requested path:
{
"error": "not_found",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"path": "/unknown-route"
}{
"error": "error_code",
"message": "Human-readable error message (optional)",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}{
"error": "invalid_user",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}or
{
"error": "invalid_token",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}{
"error": "insufficient_permissions",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}{
"error": "resource_not_found",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}{
"error": "resource_exists",
"message": "Detailed message",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}Unexpected errors and panics return an opaque response (no stack traces or internal error text):
{
"error": "internal_server_error",
"message": "An unexpected error occurred",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}{
"error": "service_not_configured",
"message": "Service configuration missing",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}- User clicks "Sign in with GitHub"
- Frontend redirects to
/auth/github/login/start - User authorizes on GitHub
- GitHub redirects to
/auth/github/callback - Backend processes OAuth and redirects to frontend with JWT token
- Frontend stores JWT token
- Frontend includes token in
Authorization: Bearer <token>header for all authenticated requests
For paginated endpoints, use limit and offset:
- First page:
?limit=50&offset=0 - Second page:
?limit=50&offset=50 - Third page:
?limit=50&offset=100
Use the total field in the response to calculate total pages.
All dates are returned in ISO 8601 format:
- Date only:
"2025-12-30" - Date and time:
"2025-12-30T22:52:00.3484+05:30"(with timezone)
All IDs are UUIDs (version 4) returned as strings:
- Example:
"8420cb43-eb78-4aa8-b8fb-9d3ab0e2d7c8"
- Set base URL (e.g.,
http://localhost:8080for development) - Implement GitHub OAuth flow
- Store JWT token securely (localStorage or secure cookie)
- Include JWT token in
Authorizationheader for authenticated requests - Handle token expiration (401 responses) and prompt re-login
- Implement error handling for all endpoints
- Use pagination for list endpoints
- Handle empty states (no data)
- Format dates for display
- Implement contribution calendar visualization using
levelfield (0-4) - Group activity feed by
month_yearfield
const token = localStorage.getItem('grainlify_jwt');
const response = await fetch('http://localhost:8080/profile', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();if (!response.ok) {
if (response.status === 401) {
// Token expired, redirect to login
localStorage.removeItem('grainlify_jwt');
window.location.href = '/login';
} else {
const error = await response.json();
console.error('API Error:', error.error, error.message);
}
}async function fetchActivities(page: number = 0) {
const limit = 50;
const offset = page * limit;
const response = await fetch(
`http://localhost:8080/profile/activity?limit=${limit}&offset=${offset}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
const data = await response.json();
// data.activities - array of activities
// data.total - total count
// data.limit, data.offset - pagination info
}For questions or issues, refer to the backend codebase or contact the backend team.
Last Updated: 2025-12-31