MENU navbar-image

Introduction

This documentation aims to provide all the information you need to work with our API.

<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>

Authenticating requests

This API is not authenticated.

Authentication

Endpoints for JWT authentication and session lifecycle.

Login and get tokens

Authenticate with email and password to receive an access token and refresh token.

Example request:
curl --request POST \
    "http://localhost/api/auth/login" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"user@example.com\",
    \"password\": \"secret123\"
}"
const url = new URL(
    "http://localhost/api/auth/login"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "user@example.com",
    "password": "secret123"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "refresh_token": "abc123def456",
    "token_type": "bearer",
    "expires_in": 3600,
    "user": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "Alice Johnson",
        "email": "user@example.com",
        "role": "manager"
    }
}
 

Example response (401):


{
    "message": "Invalid credentials"
}
 

Example response (403):


{
    "message": "Account is inactive"
}
 

Example response (422):


{
    "errors": {
        "email": [
            "The email field is required."
        ],
        "password": [
            "The password field is required."
        ]
    }
}
 

Request      

POST api/auth/login

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

User email address. Example: user@example.com

password   string     

User password. Example: secret123

Refresh access token

requires authentication

Exchange a valid refresh token for a new access token and refresh token pair.

Example request:
curl --request POST \
    "http://localhost/api/auth/refresh" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"refresh_token\": \"abc123def456\"
}"
const url = new URL(
    "http://localhost/api/auth/refresh"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "refresh_token": "abc123def456"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "refresh_token": "newtoken123",
    "token_type": "bearer",
    "expires_in": 3600
}
 

Example response (401):


{
    "message": "Invalid or expired refresh token"
}
 

Request      

POST api/auth/refresh

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

refresh_token   string     

Refresh token returned by login. Example: abc123def456

Logout current session

requires authentication

Invalidate a refresh token and end the active authenticated session.

Example request:
curl --request POST \
    "http://localhost/api/auth/logout" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"refresh_token\": \"abc123def456\"
}"
const url = new URL(
    "http://localhost/api/auth/logout"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "refresh_token": "abc123def456"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "Logged out successfully"
}
 

Request      

POST api/auth/logout

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

refresh_token   string  optional    

Optional refresh token to invalidate immediately. Example: abc123def456

Projects

Endpoints for managing projects.

Get all project types

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/projects/types" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/projects/types"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "name": "Project"
    },
    {
        "id": 2,
        "name": "Support"
    },
    {
        "id": 3,
        "name": "Engagement"
    }
]
 

Request      

GET api/projects/types

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Get all project statuses

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/projects/statuses" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/projects/statuses"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "name": "Pre-sales",
        "order": 1
    },
    {
        "id": 2,
        "name": "SOW Approval",
        "order": 2
    },
    {
        "id": 3,
        "name": "Gathering Estimates",
        "order": 3
    }
]
 

Request      

GET api/projects/statuses

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

List all projects

requires authentication

Get a list of all projects with optional filtering by status and type.

Example request:
curl --request GET \
    --get "http://localhost/api/projects?status_id=1&type_id=2" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/projects"
);

const params = {
    "status_id": "1",
    "type_id": "2",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "code": "PROJ-001",
        "title": "Client Dashboard Redesign",
        "status_id": 1,
        "status": {
            "id": 1,
            "name": "Pre-sales"
        },
        "type_id": 2,
        "type": {
            "id": 2,
            "name": "Support"
        },
        "approved_estimate": "120.00",
        "forecasted_effort": {
            "2024-02": 40,
            "2024-03": 60,
            "2024-04": 20
        },
        "created_at": "2024-01-15T10:00:00.000000Z",
        "updated_at": "2024-01-15T10:00:00.000000Z"
    }
]
 

Request      

GET api/projects

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status_id   integer  optional    

Filter by status ID. Example: 1

type_id   integer  optional    

Filter by type ID. Example: 2

Create a new project

requires authentication

Create a new project with code, title, and type.

Example request:
curl --request POST \
    "http://localhost/api/projects" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"PROJ-001\",
    \"title\": \"Client Dashboard Redesign\",
    \"type_id\": 1
}"
const url = new URL(
    "http://localhost/api/projects"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "PROJ-001",
    "title": "Client Dashboard Redesign",
    "type_id": 1
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "code": "PROJ-001",
    "title": "Client Dashboard Redesign",
    "status_id": 1,
    "status": {
        "id": 1,
        "name": "Pre-sales"
    },
    "type_id": 1,
    "type": {
        "id": 1,
        "name": "Project"
    }
}
 

Example response (422):


{
    "message": "Validation failed",
    "errors": {
        "code": [
            "Project code must be unique"
        ],
        "title": [
            "The title field is required."
        ]
    }
}
 

Request      

POST api/projects

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

code   string     

Project code (must be unique). Example: PROJ-001

title   string     

Project title. Example: Client Dashboard Redesign

type_id   integer     

Project type ID. Example: 1

Get a single project

requires authentication

Get details of a specific project by ID.

Example request:
curl --request GET \
    --get "http://localhost/api/projects/550e8400-e29b-41d4-a716-446655440000" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/projects/550e8400-e29b-41d4-a716-446655440000"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "code": "PROJ-001",
    "title": "Client Dashboard Redesign",
    "status": {
        "id": 1,
        "name": "Pre-sales"
    },
    "type": {
        "id": 1,
        "name": "Project"
    },
    "approved_estimate": "120.00",
    "forecasted_effort": {
        "2024-02": 40,
        "2024-03": 60
    }
}
 

Example response (404):


{
    "message": "Project not found"
}
 

Request      

GET api/projects/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Update a project

requires authentication

Update details of an existing project.

Example request:
curl --request PUT \
    "http://localhost/api/projects/550e8400-e29b-41d4-a716-446655440000" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"PROJ-002\",
    \"title\": \"Updated Title\",
    \"type_id\": 2
}"
const url = new URL(
    "http://localhost/api/projects/550e8400-e29b-41d4-a716-446655440000"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "PROJ-002",
    "title": "Updated Title",
    "type_id": 2
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "code": "PROJ-002",
    "title": "Updated Title",
    "type_id": 2
}
 

Example response (404):


{
    "message": "Project not found"
}
 

Example response (422):


{
    "message": "Validation failed",
    "errors": {
        "type_id": [
            "The selected type id is invalid."
        ]
    }
}
 

Request      

PUT api/projects/{id}

PATCH api/projects/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

code   string  optional    

Project code (must be unique). Example: PROJ-002

title   string  optional    

Project title. Example: Updated Title

type_id   integer  optional    

Project type ID. Example: 2

Delete a project

requires authentication

Delete a project. Cannot delete if project has allocations or actuals.

Example request:
curl --request DELETE \
    "http://localhost/api/projects/550e8400-e29b-41d4-a716-446655440000" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/projects/550e8400-e29b-41d4-a716-446655440000"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Project deleted successfully"
}
 

Example response (404):


{
    "message": "Project not found"
}
 

Example response (422):


{
    "message": "Cannot delete project with allocations"
}
 

Request      

DELETE api/projects/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Transition project status

requires authentication

Transition project to a new status following the state machine rules.

Example request:
curl --request PUT \
    "http://localhost/api/projects/architecto/status" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status_id\": 2
}"
const url = new URL(
    "http://localhost/api/projects/architecto/status"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status_id": 2
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": {
        "id": 2,
        "name": "SOW Approval"
    }
}
 

Example response (404):


{
    "message": "Project not found"
}
 

Example response (422):


{
    "message": "Cannot transition from Pre-sales to Done"
}
 

Request      

PUT api/projects/{project}/status

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

project   string     

The project. Example: architecto

id   string     

Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

status_id   integer     

Target status ID. Example: 2

Set approved estimate

requires authentication

Set the approved billable hours estimate for a project.

Example request:
curl --request PUT \
    "http://localhost/api/projects/architecto/estimate" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"approved_estimate\": 120
}"
const url = new URL(
    "http://localhost/api/projects/architecto/estimate"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "approved_estimate": 120
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "approved_estimate": "120.00"
}
 

Example response (404):


{
    "message": "Project not found"
}
 

Example response (422):


{
    "message": "Approved estimate must be greater than 0"
}
 

Request      

PUT api/projects/{project}/estimate

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

project   string     

The project. Example: architecto

id   string     

Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

approved_estimate   number     

Approved estimate hours (must be > 0). Example: 120

Set forecasted effort

requires authentication

Set the month-by-month forecasted effort breakdown.

Example request:
curl --request PUT \
    "http://localhost/api/projects/architecto/forecast" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"forecasted_effort\": {
        \"2024-02\": 40,
        \"2024-03\": 60
    }
}"
const url = new URL(
    "http://localhost/api/projects/architecto/forecast"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "forecasted_effort": {
        "2024-02": 40,
        "2024-03": 60
    }
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "forecasted_effort": {
        "2024-02": 40,
        "2024-03": 60
    }
}
 

Example response (404):


{
    "message": "Project not found"
}
 

Example response (422):


{
    "message": "Forecasted effort exceeds approved estimate by more than 5%"
}
 

Request      

PUT api/projects/{project}/forecast

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

project   string     

The project. Example: architecto

id   string     

Project UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

forecasted_effort   object     

Monthly effort breakdown.

Team Members

Endpoints for managing team members.

List all team members

requires authentication

Get a list of all team members with optional filtering by active status.

Example request:
curl --request GET \
    --get "http://localhost/api/team-members?active=1" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/team-members"
);

const params = {
    "active": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "John Doe",
        "role_id": 1,
        "role": {
            "id": 1,
            "name": "Backend Developer"
        },
        "hourly_rate": "150.00",
        "active": true,
        "created_at": "2024-01-15T10:00:00.000000Z",
        "updated_at": "2024-01-15T10:00:00.000000Z"
    }
]
 

Request      

GET api/team-members

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

active   boolean  optional    

Filter by active status. Example: true

Create a new team member

requires authentication

Create a new team member with name, role, and hourly rate.

Example request:
curl --request POST \
    "http://localhost/api/team-members" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"John Doe\",
    \"role_id\": 1,
    \"hourly_rate\": \"150.00\",
    \"active\": true
}"
const url = new URL(
    "http://localhost/api/team-members"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "John Doe",
    "role_id": 1,
    "hourly_rate": "150.00",
    "active": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "John Doe",
    "role_id": 1,
    "role": {
        "id": 1,
        "name": "Backend Developer"
    },
    "hourly_rate": "150.00",
    "active": true,
    "created_at": "2024-01-15T10:00:00.000000Z",
    "updated_at": "2024-01-15T10:00:00.000000Z"
}
 

Example response (422):


{
    "message": "Validation failed",
    "errors": {
        "name": [
            "The name field is required."
        ],
        "hourly_rate": [
            "Hourly rate must be greater than 0"
        ]
    }
}
 

Request      

POST api/team-members

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Team member name. Example: John Doe

role_id   integer     

Role ID. Example: 1

hourly_rate   numeric     

Hourly rate (must be > 0). Example: 150.00

active   boolean  optional    

Active status (defaults to true). Example: true

Get a single team member

requires authentication

Get details of a specific team member by ID.

Example request:
curl --request GET \
    --get "http://localhost/api/team-members/550e8400-e29b-41d4-a716-446655440000" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/team-members/550e8400-e29b-41d4-a716-446655440000"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "John Doe",
    "role_id": 1,
    "role": {
        "id": 1,
        "name": "Backend Developer"
    },
    "hourly_rate": "150.00",
    "active": true,
    "created_at": "2024-01-15T10:00:00.000000Z",
    "updated_at": "2024-01-15T10:00:00.000000Z"
}
 

Example response (404):


{
    "message": "Team member not found"
}
 

Request      

GET api/team-members/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Team member UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Update a team member

requires authentication

Update details of an existing team member.

Example request:
curl --request PUT \
    "http://localhost/api/team-members/550e8400-e29b-41d4-a716-446655440000" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"John Doe\",
    \"role_id\": 1,
    \"hourly_rate\": \"175.00\",
    \"active\": false
}"
const url = new URL(
    "http://localhost/api/team-members/550e8400-e29b-41d4-a716-446655440000"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "John Doe",
    "role_id": 1,
    "hourly_rate": "175.00",
    "active": false
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "John Doe",
    "role_id": 1,
    "role": {
        "id": 1,
        "name": "Backend Developer"
    },
    "hourly_rate": "175.00",
    "active": false,
    "created_at": "2024-01-15T10:00:00.000000Z",
    "updated_at": "2024-01-15T11:00:00.000000Z"
}
 

Example response (404):


{
    "message": "Team member not found"
}
 

Example response (422):


{
    "message": "Validation failed",
    "errors": {
        "hourly_rate": [
            "Hourly rate must be greater than 0"
        ]
    }
}
 

Request      

PUT api/team-members/{id}

PATCH api/team-members/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Team member UUID. Example: 550e8400-e29b-41d4-a716-446655440000

Body Parameters

name   string  optional    

Team member name. Example: John Doe

role_id   integer  optional    

Role ID. Example: 1

hourly_rate   numeric  optional    

Hourly rate (must be > 0). Example: 175.00

active   boolean  optional    

Active status. Example: false

Delete a team member

requires authentication

Delete a team member. Cannot delete if member has allocations or actuals.

Example request:
curl --request DELETE \
    "http://localhost/api/team-members/550e8400-e29b-41d4-a716-446655440000" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/team-members/550e8400-e29b-41d4-a716-446655440000"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Team member deleted successfully"
}
 

Example response (404):


{
    "message": "Team member not found"
}
 

Example response (422):


{
    "message": "Cannot delete team member with active allocations",
    "suggestion": "Consider deactivating the team member instead"
}
 

Example response (422):


{
    "message": "Cannot delete team member with historical data",
    "suggestion": "Consider deactivating the team member instead"
}
 

Request      

DELETE api/team-members/{id}

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Team member UUID. Example: 550e8400-e29b-41d4-a716-446655440000