API V2 Reference β JWT Authentication
V2 has the same task endpoints as V1 but requires a valid JWT Bearer token on every request. That includes labels, due dates, checklist items, comment sub-resources, reorder operations, and updatedAt behaviour. Use this API to practice testing authenticated REST services.
Base URL
https://api.testauto.app/api/v2Authentication
Test Users
- admin /
admin123β role: ADMIN - user /
user123β role: USER - testuser /
test123β role: USER
POST /auth/login
Exchange credentials for a JWT token.
Request
curl -X POST "https://api.testauto.app/api/v2/auth/login" \
-H "Content-Type: application/json" \
-d '{
"username": "user",
"password": "user123"
}'Response 200 OK
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "refresh-token-value",
"type": "Bearer",
"username": "user",
"role": "USER",
"expiresIn": 3600
}Response 401 Unauthorized
{
"timestamp": "2026-02-02T16:00:00Z",
"status": 401,
"error": "Unauthorized",
"message": "Invalid username or password"
}POST /auth/refresh
Exchange a refresh token for a new access token pair.
Request
curl -X POST "https://api.testauto.app/api/v2/auth/refresh" -H "Content-Type: application/json" -d '{
"refreshToken": "YOUR_REFRESH_TOKEN"
}'Response 200 OK
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "new-refresh-token-value",
"type": "Bearer",
"expiresIn": 3600
}POST /auth/logout
Log out the current user. The client should discard both access and refresh tokens after this call.
Example
curl -X POST "https://api.testauto.app/api/v2/auth/logout" -H "Authorization: Bearer YOUR_TOKEN_HERE"Response 200 OK
{
"message": "Logged out successfully"
}GET /auth/me
Inspect the current authenticated user context.
Example
curl "https://api.testauto.app/api/v2/auth/me" -H "Authorization: Bearer YOUR_TOKEN_HERE"Response 200 OK
{
"username": "user",
"role": "ROLE_USER"
}Using the Token
Include the token in the Authorization header on all task endpoints:
curl "https://api.testauto.app/api/v2/tasks" \
-H "Authorization: Bearer YOUR_TOKEN_HERE"In the current implementation, a missing or invalid token is rejected from protected task endpoints with 403 Forbidden.
Task Endpoints
V2 task endpoints are identical to V1 in request/response shape, but every call must include the Authorization header.
GET /tasksβ list tasks (pagination, filtering, sorting)GET /tasks/{id}β get single taskPOST /tasksβ create taskPUT /tasks/{id}β replace taskDELETE /tasks/{id}β delete taskPOST /tasks/{id}/reorderβ move a task within or across board columnsPOST /tasks/{id}/commentsβ create commentPUT /tasks/{id}/comments/{commentId}β update commentDELETE /tasks/{id}/comments/{commentId}β delete commentPOST /tasks/{id}/attachmentsβ upload one or more attachments viamultipart/form-dataDELETE /tasks/{id}/attachments/{attachmentId}β delete a stored attachmentGET /tasks/summaryβ statisticsDELETE /tasksβ delete all tasks (ADMIN only)
The request and response shapes follow V1: direct API requests use a 1β100 title limit and a 500-character description limit, search covers labels, comments use the text field, and task payloads include dueDate, labels, checklistItems, comments, attachments, and sortOrder. Attachment uploads are capped at 3 files per task and 1 MB per file.
Authenticated list example
# Step 1: login
TOKENS=$(curl -s -X POST "https://api.testauto.app/api/v2/auth/login" -H "Content-Type: application/json" \
-d '{"username":"user","password":"user123"}')
TOKEN=$(echo "$TOKENS" | jq -r .token)
# Step 2: use token
curl "https://api.testauto.app/api/v2/tasks?status=TODO&search=api&sort=updatedAt&direction=desc" -H "Authorization: Bearer $TOKEN"Admin-only reset example
# Login as admin to use DELETE /tasks
ADMIN_TOKEN=$(curl -s -X POST "https://api.testauto.app/api/v2/auth/login" -H "Content-Type: application/json" -d '{"username":"admin","password":"admin123"}' | jq -r .token)
curl -X DELETE "https://api.testauto.app/api/v2/tasks" -H "Authorization: Bearer $ADMIN_TOKEN"Authenticated attachment upload example
curl -X POST "https://api.testauto.app/api/v2/tasks/43/attachments" -H "Authorization: Bearer $TOKEN" -F "files=@./release-notes.txt" -F "files=@./diagram.png"Authenticated attachment delete example
curl -X DELETE "https://api.testauto.app/api/v2/tasks/43/attachments/401" -H "Authorization: Bearer $TOKEN"Testing Authentication in Code
Successful Login
test('login with valid credentials returns token', async () => {
const response = await fetch('https://api.testauto.app/api/v2/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'user123' }),
});
expect(response.status).toBe(200);
const data = await response.json();
expect(data.token).toBeDefined();
expect(data.refreshToken).toBeDefined();
expect(data.username).toBe('user');
expect(data.expiresIn).toBe(3600);
});Invalid Credentials
test('login fails with wrong password', async () => {
const response = await fetch('https://api.testauto.app/api/v2/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'wrong' }),
});
expect(response.status).toBe(401);
});Accessing Protected Endpoint
test('authenticated request returns tasks', async () => {
const loginResp = await fetch('https://api.testauto.app/api/v2/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'user123' }),
});
const { token } = await loginResp.json();
const tasksResp = await fetch('https://api.testauto.app/api/v2/tasks', {
headers: { Authorization: `Bearer ${token}` },
});
expect(tasksResp.status).toBe(200);
const data = await tasksResp.json();
expect(Array.isArray(data.content)).toBe(true);
});Refreshing a Token Pair
test('refresh token returns a new access token pair', async () => {
const loginResp = await fetch('https://api.testauto.app/api/v2/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'user123' }),
});
const { refreshToken } = await loginResp.json();
const refreshResp = await fetch('https://api.testauto.app/api/v2/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
expect(refreshResp.status).toBe(200);
const refreshed = await refreshResp.json();
expect(refreshed.token).toBeDefined();
expect(refreshed.refreshToken).toBeDefined();
});Missing or Expired Token
test('request without token returns 403', async () => {
const response = await fetch('https://api.testauto.app/api/v2/tasks');
expect(response.status).toBe(403);
});
test('tampered token is rejected', async () => {
const loginResp = await fetch('https://api.testauto.app/api/v2/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'user123' }),
});
const { token } = await loginResp.json();
// Modify last character to tamper with the signature
const tampered = token.slice(0, -1) + 'X';
const response = await fetch('https://api.testauto.app/api/v2/tasks', {
headers: { Authorization: `Bearer ${tampered}` },
});
expect(response.status).toBe(403);
});Admin-only Reset
test('non-admin user cannot delete all tasks', async () => {
const loginResp = await fetch('https://api.testauto.app/api/v2/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'user123' }),
});
const { token } = await loginResp.json();
const response = await fetch('https://api.testauto.app/api/v2/tasks', {
method: 'DELETE',
headers: { Authorization: 'Bearer ' + token },
});
expect(response.status).toBe(403);
});Authenticated Comment Lifecycle
test('create and update a comment with JWT authentication', async () => {
const loginResp = await fetch('https://api.testauto.app/api/v2/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'user123' }),
});
const { token } = await loginResp.json();
const createComment = await fetch('https://api.testauto.app/api/v2/tasks/1/comments', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ text: 'JWT-protected comment' }),
});
expect(createComment.status).toBe(201);
});Token Structure
// JWT payload (base64-decoded)
{
"sub": "user",
"iat": 1706877600, // issued at (Unix timestamp)
"exp": 1706881200, // expires at (iat + 3600s)
"role": "USER"
}Tokens expire after 3600 seconds (1 hour). Use POST /auth/refresh to extend.
HTTP Status Codes
- 200 OK β Request succeeded
- 201 Created β Resource created
- 204 No Content β Succeeded, no body
- 400 Bad Request β Validation error
- 401 Unauthorized β Invalid login credentials
- 403 Forbidden β Missing, invalid, or rejected token for protected endpoints, or insufficient role for admin-only operations
- 404 Not Found β Resource does not exist