{"info":{"_postman_id":"ae88f806-eb95-46f4-92d0-d4207e9f3fb8","name":"DevPulse API","description":"<html><head></head><body><h1 id=\"devpulse-api\">DevPulse API</h1>\n<p>A REST API for reporting and managing software issues. Built with Express, PostgreSQL and JWT authentication.</p>\n<h2 id=\"base-url\">Base URL</h2>\n<ul>\n<li>Production: <a href=\"https://devpulse-two-pi.vercel.app/\">https://devpulse-two-pi.vercel.app</a></li>\n</ul>\n<h2 id=\"authentication\">Authentication</h2>\n<p>Protected endpoints expect the <strong>raw JWT access token</strong> in the <code>Authorization</code> header. Do <strong>not</strong> prefix it with <code>Bearer</code>.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>Authorization: &lt;accessToken&gt;\n\n</code></pre><p>Run <strong>Auth &gt; Login</strong> first. Its test script automatically stores the returned access token in the <code></code> collection variable.</p>\n<h2 id=\"roles\">Roles</h2>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Role</th>\n<th>Capabilities</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>contributor</code></td>\n<td>Create issues, update their own issues while the status is <code>open</code></td>\n</tr>\n<tr>\n<td><code>maintainer</code></td>\n<td>Create issues, update any issue, delete issues</td>\n</tr>\n</tbody>\n</table>\n</div><h2 id=\"response-envelope\">Response envelope</h2>\n<p>Every endpoint returns the same shape:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"message\": \"Human readable message\",\n  \"data\": {}\n}\n\n</code></pre>\n<p>Errors return <code>success: false</code> and may include an <code>errors</code> stack in development.</p>\n<h2 id=\"enumerations\">Enumerations</h2>\n<ul>\n<li><p><code>role</code>: <code>contributor</code> | <code>maintainer</code></p>\n</li>\n<li><p><code>type</code>: <code>bug</code> | <code>feature_request</code></p>\n</li>\n<li><p><code>status</code>: <code>open</code> | <code>in_progress</code> | <code>resolved</code></p>\n</li>\n</ul>\n<h2 id=\"constraints\">Constraints</h2>\n<ul>\n<li><p><code>title</code> max 150 characters</p>\n</li>\n<li><p><code>description</code> minimum 20 characters</p>\n</li>\n<li><p><code>email</code> must be unique</p>\n</li>\n<li><p><code>sort</code>: <code>newest</code> (default, descending) or <code>oldest</code> (ascending)</p>\n</li>\n</ul>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[{"content":"DevPulse API","slug":"devpulse-api"}],"owner":"34724156","collectionId":"ae88f806-eb95-46f4-92d0-d4207e9f3fb8","publishedId":"2sBYHXPxiY","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"},"publishDate":"2026-11-01T13:13:18.000Z"},"item":[{"name":"Health","item":[{"name":"Root","id":"bb47ec7b-fc01-4807-939f-1f9640b6a75c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://devpulse-two-pi.vercel.app/","description":"<p>Returns the plain-text string <code>Root route</code>. Useful for a quick connectivity check.</p>\n","urlObject":{"path":[""],"host":["https://devpulse-two-pi.vercel.app"],"query":[],"variable":[]}},"response":[{"id":"749a74de-a78e-4fa0-a4f4-a4a331f1aabf","name":"OK","originalRequest":{"method":"GET","header":[],"url":"https://devpulse-two-pi.vercel.app/"},"status":"OK","code":200,"_postman_previewlanguage":"text","header":[{"key":"Content-Type","value":"text/html; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"Root route"}],"_postman_id":"bb47ec7b-fc01-4807-939f-1f9640b6a75c"}],"id":"53cfcbed-0eb6-4487-91ed-b1c916891a77","description":"<p>Service availability endpoints.</p>\n","_postman_id":"53cfcbed-0eb6-4487-91ed-b1c916891a77"},{"name":"Auth","item":[{"name":"Signup","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status code is 201', () => pm.response.to.have.status(201));","pm.test('User is returned without password', () => {","  const body = pm.response.json();","  pm.expect(body.success).to.eql(true);","  pm.expect(body.data).to.have.property('id');","  pm.expect(body.data).to.not.have.property('password');","});"],"id":"94c75b9b-6c11-4c07-abca-fe8cd72e91a1"}}],"id":"f92b6b9b-f349-473c-9a4e-ca3f7597174d","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"name\": \"Jane Contributor\",\n  \"email\": \"contributor@example.com\",\n  \"password\": \"password123\",\n  \"role\": \"contributor\"\n}","options":{"raw":{"language":"json"}}},"url":"https://devpulse-two-pi.vercel.app/api/auth/signup","description":"<p>Registers a new user.</p>\n<p><strong>Body</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>name</code></td>\n<td>string</td>\n<td>yes</td>\n<td>Max 100 characters</td>\n</tr>\n<tr>\n<td><code>email</code></td>\n<td>string</td>\n<td>yes</td>\n<td>Unique, max 255 characters</td>\n</tr>\n<tr>\n<td><code>password</code></td>\n<td>string</td>\n<td>yes</td>\n<td>Stored as a bcrypt hash</td>\n</tr>\n<tr>\n<td><code>role</code></td>\n<td>string</td>\n<td>no</td>\n<td><code>contributor</code> (default) or <code>maintainer</code></td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Response 201</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"message\": \"User registered successfully\",\n  \"data\": {\n    \"id\": 1,\n    \"name\": \"Jane Contributor\",\n    \"email\": \"jane@example.com\",\n    \"role\": \"contributor\",\n    \"created_at\": \"2026-01-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n  }\n}\n</code></pre>\n<p><strong>Errors</strong></p>\n<ul>\n<li><code>500</code> - Duplicate email or validation failure raised by PostgreSQL.</li>\n</ul>\n","urlObject":{"path":["api","auth","signup"],"host":["https://devpulse-two-pi.vercel.app"],"query":[],"variable":[]}},"response":[{"id":"6b5514c8-ac5d-4bf0-b395-ab38ebe9ccc0","name":"Created","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"name\": \"Jane Contributor\",\n  \"email\": \"jane@example.com\",\n  \"password\": \"password123\",\n  \"role\": \"contributor\"\n}"},"url":"https://devpulse-two-pi.vercel.app/api/auth/signup"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"message\": \"User registered successfully\",\n  \"data\": {\n    \"id\": 1,\n    \"name\": \"Jane Contributor\",\n    \"email\": \"jane@example.com\",\n    \"role\": \"contributor\",\n    \"created_at\": \"2026-01-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n  }\n}"}],"_postman_id":"f92b6b9b-f349-473c-9a4e-ca3f7597174d"},{"name":"Login","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status code is 200', () => pm.response.to.have.status(200));","const body = pm.response.json();","pm.test('Access token returned', () => pm.expect(body.data.token).to.be.a('string'));","if (body.data && body.data.token) {","  pm.collectionVariables.set('accessToken', body.data.token);","}","if (body.data && body.data.user) {","  pm.collectionVariables.set('userId', body.data.user.id);","  pm.collectionVariables.set('userRole', body.data.user.role);","}"],"id":"ee629a34-1ce9-4e7c-b6b1-3c379bb5c6e2"}}],"id":"6ef02551-f6ff-4c9d-9077-e87c9251c360","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"email\": \"contributor@example.com\",\n  \"password\": \"password123\"\n}","options":{"raw":{"language":"json"}}},"url":"https://devpulse-two-pi.vercel.app/api/auth/login","description":"<p>Authenticates a user and returns a JWT access token.</p>\n<p><strong>Body</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>email</code></td>\n<td>string</td>\n<td>yes</td>\n</tr>\n<tr>\n<td><code>password</code></td>\n<td>string</td>\n<td>yes</td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Response 200</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"message\": \"Login successful\",\n  \"data\": {\n    \"token\": \"&lt;access token&gt;\",\n    \"user\": {\n      \"id\": 1,\n      \"name\": \"Jane Contributor\",\n      \"email\": \"jane@example.com\",\n      \"role\": \"contributor\",\n      \"created_at\": \"2026-01-01T00:00:00.000Z\",\n      \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n    }\n  }\n}\n</code></pre>\n<p>A <code>refreshToken</code> cookie (httpOnly, 7 days) is also set.</p>\n<p><strong>Errors</strong></p>\n<ul>\n<li><code>500</code> - <code>Invalid Credentials!</code> when the email does not exist or the password is wrong.</li>\n</ul>\n<p><strong>Postman</strong></p>\n<p>The test script stores <code>data.token</code> in <code></code> automatically.</p>\n","urlObject":{"path":["api","auth","login"],"host":["https://devpulse-two-pi.vercel.app"],"query":[],"variable":[]}},"response":[{"id":"ee2c26c6-ae1d-4767-b098-454887b53b58","name":"OK","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"email\": \"jane@example.com\",\n  \"password\": \"password123\"\n}"},"url":"https://devpulse-two-pi.vercel.app/api/auth/login"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"message\": \"Login successful\",\n  \"data\": {\n    \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"user\": {\n      \"id\": 1,\n      \"name\": \"Jane Contributor\",\n      \"email\": \"jane@example.com\",\n      \"role\": \"contributor\",\n      \"created_at\": \"2026-01-01T00:00:00.000Z\",\n      \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n    }\n  }\n}"}],"_postman_id":"6ef02551-f6ff-4c9d-9077-e87c9251c360"}],"id":"6939ced8-587a-4f5a-93f4-839a4532f54d","description":"<p>User registration and login. Login issues a 1-day access token and a 7-day refresh token (set as an httpOnly cookie).</p>\n","_postman_id":"6939ced8-587a-4f5a-93f4-839a4532f54d"},{"name":"Issues","item":[{"name":"Create Issue","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status code is 201', () => pm.response.to.have.status(201));","const body = pm.response.json();","pm.test('Issue id returned', () => pm.expect(body.data).to.have.property('id'));","if (body.data && body.data.id) {","  pm.collectionVariables.set('issueId', body.data.id);","}"],"id":"e7ff2ca7-3243-4b94-b5a9-b66860243cc5"}}],"id":"5feaa820-4b9d-4fd6-bbe8-18cebf218aff","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"","description":"<p>Raw JWT access token (no Bearer prefix).</p>\n"}],"body":{"mode":"raw","raw":"{\n  \"title\": \"Login button unresponsive on mobile\",\n  \"description\": \"Tapping the login button on iOS Safari does nothing. Reproducible on iPhone 13.\",\n  \"type\": \"bug\",\n  \"status\": \"open\"\n}","options":{"raw":{"language":"json"}}},"url":"https://devpulse-two-pi.vercel.app/api/issues","description":"<p>Creates a new issue. Requires a <code>contributor</code> or <code>maintainer</code> access token.</p>\n<p><strong>Headers</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Header</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>Authorization</code></td>\n<td><code>&lt;accessToken&gt;</code> (raw, no <code>Bearer</code>)</td>\n</tr>\n<tr>\n<td><code>Content-Type</code></td>\n<td><code>application/json</code></td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Body</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>title</code></td>\n<td>string</td>\n<td>yes</td>\n<td>Max 150 characters</td>\n</tr>\n<tr>\n<td><code>description</code></td>\n<td>string</td>\n<td>yes</td>\n<td>Minimum 20 characters</td>\n</tr>\n<tr>\n<td><code>type</code></td>\n<td>string</td>\n<td>yes</td>\n<td><code>bug</code> or <code>feature_request</code></td>\n</tr>\n<tr>\n<td><code>status</code></td>\n<td>string</td>\n<td>no</td>\n<td><code>open</code> (default), <code>in_progress</code>, <code>resolved</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>The reporter is taken from the authenticated token, not from the body.</p>\n<p><strong>Response 201</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"message\": \"Issue created successfully\",\n  \"data\": {\n    \"id\": 1,\n    \"title\": \"Login button unresponsive on mobile\",\n    \"description\": \"Tapping the login button on iOS Safari does nothing. Reproducible on iPhone 13.\",\n    \"type\": \"bug\",\n    \"status\": \"open\",\n    \"reporter_id\": 1,\n    \"created_at\": \"2026-01-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n  }\n}\n</code></pre>\n<p><strong>Errors</strong></p>\n<ul>\n<li><code>401</code> - Missing token (<code>Unauthorized Access!</code>)</li>\n<li><code>403</code> - Role is not contributor or maintainer (<code>Forbidden Access!</code>)</li>\n<li><code>404</code> - Token user no longer exists</li>\n<li><code>500</code> - Validation failure (short description, invalid type/status)</li>\n</ul>\n","urlObject":{"path":["api","issues"],"host":["https://devpulse-two-pi.vercel.app"],"query":[],"variable":[]}},"response":[{"id":"0f6c82f5-54c7-43a7-80d2-400c85fabf8e","name":"Created","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"<accessToken>"}],"body":{"mode":"raw","raw":"{\n  \"title\": \"Login button unresponsive on mobile\",\n  \"description\": \"Tapping the login button on iOS Safari does nothing. Reproducible on iPhone 13.\",\n  \"type\": \"bug\",\n  \"status\": \"open\"\n}"},"url":"https://devpulse-two-pi.vercel.app/api/issues"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"message\": \"Issue created successfully\",\n  \"data\": {\n    \"id\": 1,\n    \"title\": \"Login button unresponsive on mobile\",\n    \"description\": \"Tapping the login button on iOS Safari does nothing. Reproducible on iPhone 13.\",\n    \"type\": \"bug\",\n    \"status\": \"open\",\n    \"reporter_id\": 1,\n    \"created_at\": \"2026-01-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n  }\n}"}],"_postman_id":"5feaa820-4b9d-4fd6-bbe8-18cebf218aff"},{"name":"Get All Issues","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status code is 200', () => pm.response.to.have.status(200));","pm.test('Returns an array', () => pm.expect(pm.response.json().data).to.be.an('array'));"],"id":"9dec5836-8010-4345-ad0f-0367e87a82b0"}}],"id":"af05e778-7e3d-48d6-beea-16a0fe7808b2","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://devpulse-two-pi.vercel.app/api/issues?sort=newest&type=bug&status=open","description":"<p>Returns all issues, newest first by default. All filters are optional and can be combined.</p>\n<p><strong>Query parameters</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Param</th>\n<th>Type</th>\n<th>Required</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>sort</code></td>\n<td>string</td>\n<td>no</td>\n<td><code>newest</code> (default) or <code>oldest</code></td>\n</tr>\n<tr>\n<td><code>type</code></td>\n<td>string</td>\n<td>no</td>\n<td><code>bug</code> or <code>feature_request</code></td>\n</tr>\n<tr>\n<td><code>status</code></td>\n<td>string</td>\n<td>no</td>\n<td><code>open</code>, <code>in_progress</code> or <code>resolved</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>This endpoint is public; no token is required.</p>\n<p><strong>Response 200</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"message\": \"Issues retrieved successfully.\",\n  \"data\": [\n    {\n      \"id\": 1,\n      \"title\": \"Login button unresponsive on mobile\",\n      \"description\": \"Tapping the login button on iOS Safari does nothing. Reproducible on iPhone 13.\",\n      \"type\": \"bug\",\n      \"status\": \"open\",\n      \"reporter\": { \"id\": 1, \"name\": \"Jane Contributor\", \"role\": \"contributor\" },\n      \"created_at\": \"2026-01-01T00:00:00.000Z\",\n      \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n    }\n  ]\n}\n</code></pre>\n","urlObject":{"path":["api","issues"],"host":["https://devpulse-two-pi.vercel.app"],"query":[{"description":{"content":"<p><code>newest</code> (default) or <code>oldest</code></p>\n","type":"text/plain"},"key":"sort","value":"newest"},{"description":{"content":"<p><code>bug</code> or <code>feature_request</code></p>\n","type":"text/plain"},"key":"type","value":"bug"},{"description":{"content":"<p><code>open</code>, <code>in_progress</code> or <code>resolved</code></p>\n","type":"text/plain"},"key":"status","value":"open"}],"variable":[]}},"response":[{"id":"586aaff4-feca-48f0-b782-84a55cdb6672","name":"OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://devpulse-two-pi.vercel.app/api/issues?sort=newest&type=bug&status=open","host":["https://devpulse-two-pi.vercel.app"],"path":["api","issues"],"query":[{"key":"sort","value":"newest"},{"key":"type","value":"bug"},{"key":"status","value":"open"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"message\": \"Issues retrieved successfully.\",\n  \"data\": [\n    {\n      \"id\": 1,\n      \"title\": \"Login button unresponsive on mobile\",\n      \"description\": \"Tapping the login button on iOS Safari does nothing. Reproducible on iPhone 13.\",\n      \"type\": \"bug\",\n      \"status\": \"open\",\n      \"reporter\": {\n        \"id\": 1,\n        \"name\": \"Jane Contributor\",\n        \"role\": \"contributor\"\n      },\n      \"created_at\": \"2026-01-01T00:00:00.000Z\",\n      \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n    }\n  ]\n}"}],"_postman_id":"af05e778-7e3d-48d6-beea-16a0fe7808b2"},{"name":"Get Single Issue","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status code is 200', () => pm.response.to.have.status(200));"],"id":"6db302ea-646c-4402-bb14-4a715b10d465"}}],"id":"fd9e3190-63b4-4205-9bb1-3e7174b6d3e5","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://devpulse-two-pi.vercel.app/api/issues/1","description":"<p>Returns a single issue by id, including its reporter.</p>\n<p><strong>Path parameters</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Param</th>\n<th>Type</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>id</code></td>\n<td>number</td>\n<td>yes</td>\n</tr>\n</tbody>\n</table>\n</div><p>This endpoint is public; no token is required.</p>\n<p><strong>Response 200</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"message\": \"Issue retrieved successfully.\",\n  \"data\": {\n    \"id\": 1,\n    \"title\": \"Login button unresponsive on mobile\",\n    \"description\": \"Tapping the login button on iOS Safari does nothing. Reproducible on iPhone 13.\",\n    \"type\": \"bug\",\n    \"status\": \"open\",\n    \"reporter\": { \"id\": 1, \"name\": \"Jane Contributor\", \"role\": \"contributor\" },\n    \"created_at\": \"2026-01-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n  }\n}\n</code></pre>\n<p><strong>Errors</strong></p>\n<ul>\n<li><code>500</code> - <code>Issue not found!</code> when no issue matches the id.</li>\n</ul>\n","urlObject":{"path":["api","issues","1"],"host":["https://devpulse-two-pi.vercel.app"],"query":[],"variable":[]}},"response":[{"id":"9e891e7b-58cf-4071-a86b-8bece7ab76a9","name":"OK","originalRequest":{"method":"GET","header":[],"url":"https://devpulse-two-pi.vercel.app/api/issues/1"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"message\": \"Issue retrieved successfully.\",\n  \"data\": {\n    \"id\": 1,\n    \"title\": \"Login button unresponsive on mobile\",\n    \"description\": \"Tapping the login button on iOS Safari does nothing. Reproducible on iPhone 13.\",\n    \"type\": \"bug\",\n    \"status\": \"open\",\n    \"reporter\": {\n      \"id\": 1,\n      \"name\": \"Jane Contributor\",\n      \"role\": \"contributor\"\n    },\n    \"created_at\": \"2026-01-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-01-01T00:00:00.000Z\"\n  }\n}"}],"_postman_id":"fd9e3190-63b4-4205-9bb1-3e7174b6d3e5"},{"name":"Update Issue","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status code is 200', () => pm.response.to.have.status(200));"],"id":"8fc58c58-2a57-4847-bfad-80f982aa1075"}}],"id":"848c4a61-42bd-4640-ab2f-857720da25f1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"","description":"<p>Raw JWT access token (no Bearer prefix).</p>\n"}],"body":{"mode":"raw","raw":"{\n  \"title\": \"Login button unresponsive on mobile (iOS 17)\",\n  \"description\": \"Tapping the login button on iOS Safari does nothing. Confirmed on iPhone 13 running iOS 17.\",\n  \"type\": \"bug\"\n}","options":{"raw":{"language":"json"}}},"url":"https://devpulse-two-pi.vercel.app/api/issues/1","description":"<p>Partially updates an issue. Only <code>title</code>, <code>description</code> and <code>type</code> can be changed; <code>status</code> is not updatable through this endpoint. Omitted fields keep their current value.</p>\n<p><strong>Headers</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Header</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>Authorization</code></td>\n<td><code>&lt;accessToken&gt;</code> (raw, no <code>Bearer</code>)</td>\n</tr>\n<tr>\n<td><code>Content-Type</code></td>\n<td><code>application/json</code></td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Path parameters</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Param</th>\n<th>Type</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>id</code></td>\n<td>number</td>\n<td>yes</td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Body</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>title</code></td>\n<td>string</td>\n<td>no</td>\n<td>Max 150 characters</td>\n</tr>\n<tr>\n<td><code>description</code></td>\n<td>string</td>\n<td>no</td>\n<td>Minimum 20 characters</td>\n</tr>\n<tr>\n<td><code>type</code></td>\n<td>string</td>\n<td>no</td>\n<td><code>bug</code> or <code>feature_request</code></td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Authorization rules</strong></p>\n<ul>\n<li><code>maintainer</code> can update any issue.</li>\n<li><code>contributor</code> can update only issues they reported, and only while the status is <code>open</code>.</li>\n</ul>\n<p><strong>Response 200</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"message\": \"Issue updated successfully\",\n  \"data\": {\n    \"id\": 1,\n    \"title\": \"Login button unresponsive on mobile (iOS 17)\",\n    \"description\": \"Tapping the login button on iOS Safari does nothing. Confirmed on iPhone 13 running iOS 17.\",\n    \"type\": \"bug\",\n    \"status\": \"open\",\n    \"reporter_id\": 1,\n    \"created_at\": \"2026-01-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-01-02T00:00:00.000Z\"\n  }\n}\n</code></pre>\n<p><strong>Errors</strong></p>\n<ul>\n<li><code>500</code> - <code>Unauthorized access!</code> for a missing/invalid token</li>\n<li><code>500</code> - <code>Forbidden access!</code> when a contributor edits someone else's issue or a non-open issue</li>\n<li><code>500</code> - <code>Issue not found!</code> when the id does not exist</li>\n</ul>\n","urlObject":{"path":["api","issues","1"],"host":["https://devpulse-two-pi.vercel.app"],"query":[],"variable":[]}},"response":[{"id":"84bd996f-4350-4fb9-b991-a7c3efc5dda0","name":"OK","originalRequest":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json"},{"key":"Authorization","value":"<accessToken>"}],"body":{"mode":"raw","raw":"{\n  \"title\": \"Login button unresponsive on mobile (iOS 17)\",\n  \"description\": \"Tapping the login button on iOS Safari does nothing. Confirmed on iPhone 13 running iOS 17.\",\n  \"type\": \"bug\"\n}"},"url":"https://devpulse-two-pi.vercel.app/api/issues/1"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"message\": \"Issue updated successfully\",\n  \"data\": {\n    \"id\": 1,\n    \"title\": \"Login button unresponsive on mobile (iOS 17)\",\n    \"description\": \"Tapping the login button on iOS Safari does nothing. Confirmed on iPhone 13 running iOS 17.\",\n    \"type\": \"bug\",\n    \"status\": \"open\",\n    \"reporter_id\": 1,\n    \"created_at\": \"2026-01-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-01-02T00:00:00.000Z\"\n  }\n}"}],"_postman_id":"848c4a61-42bd-4640-ab2f-857720da25f1"},{"name":"Delete Issue","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status code is 200', () => pm.response.to.have.status(200));"],"id":"6cfaba67-4e61-4f1d-8b21-b85f597a0845"}}],"id":"1b72e8d9-dc43-47a9-886a-64afb9b619cd","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[{"key":"Authorization","value":"","description":"<p>Raw maintainer JWT access token (no Bearer prefix).</p>\n"}],"url":"https://devpulse-two-pi.vercel.app/api/issues/1","description":"<p>Deletes an issue permanently. Requires a <code>maintainer</code> access token.</p>\n<p><strong>Headers</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Header</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>Authorization</code></td>\n<td><code>&lt;accessToken&gt;</code> (raw, no <code>Bearer</code>)</td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Path parameters</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Param</th>\n<th>Type</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>id</code></td>\n<td>number</td>\n<td>yes</td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Response 200</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"message\": \"Issue deleted successfully\"\n}\n</code></pre>\n<p><strong>Errors</strong></p>\n<ul>\n<li><code>500</code> - <code>Unauthorized access</code> for a missing/invalid token or a non-maintainer role</li>\n<li><code>500</code> - <code>Issue not found!</code> when the id does not exist</li>\n</ul>\n","urlObject":{"path":["api","issues","1"],"host":["https://devpulse-two-pi.vercel.app"],"query":[],"variable":[]}},"response":[{"id":"9ddd0b77-244a-4ebe-b1f2-12332977f0ca","name":"OK","originalRequest":{"method":"DELETE","header":[{"key":"Authorization","value":"<accessToken>"}],"url":"https://devpulse-two-pi.vercel.app/api/issues/1"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"message\": \"Issue deleted successfully\"\n}"}],"_postman_id":"1b72e8d9-dc43-47a9-886a-64afb9b619cd"}],"id":"3272893f-4fa8-406f-9821-39ef2e7d521d","description":"<p>Create, browse, update and delete issues. Issues are joined with their reporter (id, name, role) in list and single responses.</p>\n","_postman_id":"3272893f-4fa8-406f-9821-39ef2e7d521d"}],"event":[{"listen":"prerequest","script":{"id":"8130a9f2-90b2-49d7-b24d-80cb78f2c461","type":"text/javascript","packages":{},"requests":{},"exec":[""]}},{"listen":"test","script":{"id":"1ca30741-71b7-49bd-8ce9-d82042e797f9","type":"text/javascript","packages":{},"requests":{},"exec":[""]}}],"variable":[{"key":"baseUrl","value":"https://devpulse-two-pi.vercel.app"},{"key":"accessToken","value":""},{"key":"issueId","value":"1"},{"key":"userId","value":""},{"key":"userRole","value":""},{"key":"email","value":"contributor@example.com"},{"key":"password","value":"password123"}]}