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, comment sub-resources, 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...",
"username": "user",
"expiresIn": 3600
}Response 401 Unauthorized
{
"timestamp": "2026-02-02T16:00:00Z",
"status": 401,
"error": "Unauthorized",
"message": "Invalid username or password"
}POST /auth/refresh
Get a new token using the current (non-expired) token.
Request
curl -X POST "https://api.testauto.app/api/v2/auth/refresh" \
-H "Authorization: Bearer YOUR_CURRENT_TOKEN"Response 200 OK
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600
}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"A missing or invalid token returns 401 Unauthorized.
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}/commentsβ create commentPUT /tasks/{id}/comments/{commentId}β update commentDELETE /tasks/{id}/comments/{commentId}β delete commentGET /tasks/summaryβ statistics
The request and response shapes follow V1: priorities include URGENT, search covers labels, and comments use the text field.
Authenticated list example
# Step 1: login
TOKEN=$(curl -s -X POST "https://api.testauto.app/api/v2/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"user","password":"user123"}' | jq -r .token)
# Step 2: use token
curl "https://api.testauto.app/api/v2/tasks?status=TODO" \
-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.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);
});Missing or Expired Token
test('request without token returns 401', 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);
});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
- 404 Not Found β Resource does not exist