Create a new manual position
What this endpoint does: Creates a new manual position (billable item) for a project. Manual positions represent extra costs that are not automatically tracked by the system, such as external services, third-party expenses, or one-time fees.
How it works:
- The position is added to the project’s current planning
- Sort order is automatically assigned (new positions are added at the end)
- Description can be provided as HTML or Markdown (automatically converted to internal format)
- The position can optionally be assigned to a category for better organization
- Once created, the position will appear in project quotes and invoices
Description format:
- Accepts HTML or Markdown format
- The system automatically detects the format and converts it to internal IDoc format
- Empty descriptions are allowed (will be stored as empty IDoc)
- In responses, descriptions are always returned as HTML
Validation rules:
- Name is required and must not be empty
- Price must be a number greater than or equal to 0
- Category ID must be a valid UUID if provided
- Project must exist and user must have access
Permission requirements:
- Requires Projects.manageManualPositions permission
- Requires api scope
- Requires read access to the project
Use cases:
- Add external costs like printing or translation services
- Record one-time fees that are not in the product catalog
- Include retroactive charges or corrections to existing offers
- Add services that happened outside the operational project structure
POST
/projects/{projectId}/manual-positionsAuthorization
AuthorizationOAuth2 access token · headerrequiredScopes:
api:writeor
AuthorizationBearer token (JWT) · headerrequiredPath parameters
projectIdstringrequiredQuery parameters
fieldsToReturnstringComma-separated list of top-level response fields to return. Overrides the endpoint compact default unless responseShape=full is used.
responseShapestringAdvanced override. Omit for the endpoint compact default. Use full only when you need the complete endpoint response, including nested fields that are not selectable with fieldsToReturn.
Allowed:
compactfullHeader parameters
LT-Response-ShapestringAdvanced override. Set to full only when you need the complete endpoint response, including nested fields that are not selectable with fieldsToReturn.
Allowed:
fullRequest body
requiredapplication/jsoncategoryIdstringOptional category ID to organize positions. Categories help group related positions together (e.g., "External costs", "Travel expenses"). Must be a valid UUID of an existing project manual position category.
descriptionstringDetailed description of the position. Can be provided as HTML or Markdown - the system will automatically detect and convert the format. This description appears in project quotes and invoices to provide additional context about the service or cost.
namestringrequiredName of the manual position. This will appear in project quotes and invoices. Examples: "Printing costs", "Translation services", "Workshop travel expenses"
pricenumberrequiredPrice of the position in the workspace currency. Must be a non-negative number. This is the net price before taxes. The position can later be discounted if needed.
min 0
Responses
200
categoryIdobject | nullCategory ID if the position is assigned to a category, or null if no category is assigned. Categories help organize related positions together.
createdAtstringrequiredISO 8601 timestamp indicating when the position was created
descriptionstringrequiredDescription of the position in HTML format. The description is converted from internal IDoc format to HTML for API responses. May be empty if no description was provided.
discountAmountnumberrequiredDiscount amount applied to this position. 0 means no discount.
discountTypestringrequiredType of discount: "Fixed" (fixed amount) or "Percentage" (percentage of price)
Allowed:
FixedPercentageidstringrequiredUnique identifier of the manual position (UUID)
namestringrequiredName of the manual position as it appears in quotes and invoices
pricenumberrequiredPrice of the position in the workspace currency (net price before taxes)
sortnumberrequiredSort order of the position. Lower numbers appear first. Used to control the display order in project planning views, quotes, and invoices.
401Unauthorized - Invalid or missing authentication token
403Forbidden - Insufficient API scopes or permissions
Request
curl -X POST "https://leadtime.app/api/public/projects/string/manual-positions" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"categoryId": "550e8400-e29b-41d4-a716-446655440000",
"description": "<p>Professional consulting services</p>",
"name": "Consulting Hours",
"price": 150
}'const response = await fetch("https://leadtime.app/api/public/projects/string/manual-positions", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({
"categoryId": "550e8400-e29b-41d4-a716-446655440000",
"description": "<p>Professional consulting services</p>",
"name": "Consulting Hours",
"price": 150
})
});import requests
response = requests.post(
"https://leadtime.app/api/public/projects/string/manual-positions",
headers={
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json"
},
json={
"categoryId": "550e8400-e29b-41d4-a716-446655440000",
"description": "<p>Professional consulting services</p>",
"name": "Consulting Hours",
"price": 150
},
)Response
{
"categoryId": "550e8400-e29b-41d4-a716-446655440000",
"createdAt": "2024-01-01T00:00:00Z",
"description": "<p>Professional consulting services</p>",
"discountAmount": 10,
"discountType": "Fixed",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Consulting Hours",
"price": 150,
"sort": 0
}Unauthorized - Invalid or missing authentication token
Forbidden - Insufficient API scopes or permissions