Create a project from a template
Creates a new project by copying a project template, and returns it.
Copies the template's task plan, milestones, resource group, contracts and payment plan into a new project and overrides name, customer, people, dates, type, category, currency and legal entity from the body. The project manager (and the milestone assignee, when the template has milestones) is added as a project resource. The status and time-registration flag come from the template. Answers 200 with the new project (ProjectRead). Only available when the account's ProjectManagerToolbox business process is on.
Use it to:
- CRM / quote-to-project integration: create the delivery project when a deal is won
Not for:
- Creating a project without a template — no such REST v1 operation
Rules across fields:
- R1: The legal entity (LegalEntityID, or the default one) must have a standard price group in CurrencyID.
Behaviour:
- Not idempotent.
- Not safe to retry blindly. Every call creates a new project; nothing deduplicates (not even ProjectNo was established as unique). After a lost response or a 403, look the project up before retrying.
- Duplicates: Not deduplicated.
- Side effect: Creates the project row, then the resource group and the project-manager (and milestone-assignee) resources, the invoice settings, the template status, and batch-creates tasks, milestones, contracts, allocations and payment plan from the template.
- On failure: Not transactional: a failure after the project row is inserted leaves a partially created project.
- Changes are visible to the next read immediately.
Call first: GET /v1/customer, GET /v1/user, GET /v1/currency/{status}, POST /v1/project/validate-create-from-template
Next: GET /v1/project/{projectID}, PUT /v1/project/{projectID}/status
Not yet documented: errors; examples.
curl -X POST "https://app3.timelog.com/your-account/api/v1/project/create-from-template" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"Description": "example_string",
"ProjectTemplateID": 123,
"ProjectManagerID": 123,
"ProjectStartDate": "2024-12-25T10:00:00Z",
"ProjectEndDate": "2024-12-25T10:00:00Z",
"ProjectTypeID": 123,
"ProjectCategoryID": 123,
"CurrencyID": 123,
"LegalEntityID": 123,
"DepartmentID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ContactID": 123,
"InvoicingCustomerReferenceID": "null",
"LanguageID": "null"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/project/create-from-template"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"Description": "example_string",
"ProjectTemplateID": 123,
"ProjectManagerID": 123,
"ProjectStartDate": "2024-12-25T10:00:00Z",
"ProjectEndDate": "2024-12-25T10:00:00Z",
"ProjectTypeID": 123,
"ProjectCategoryID": 123,
"CurrencyID": 123,
"LegalEntityID": 123,
"DepartmentID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ContactID": 123,
"InvoicingCustomerReferenceID": "null",
"LanguageID": "null"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/project/create-from-template", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"Description": "example_string",
"ProjectTemplateID": 123,
"ProjectManagerID": 123,
"ProjectStartDate": "2024-12-25T10:00:00Z",
"ProjectEndDate": "2024-12-25T10:00:00Z",
"ProjectTypeID": 123,
"ProjectCategoryID": 123,
"CurrencyID": 123,
"LegalEntityID": 123,
"DepartmentID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ContactID": 123,
"InvoicingCustomerReferenceID": "null",
"LanguageID": "null"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"Description": "example_string",
"ProjectTemplateID": 123,
"ProjectManagerID": 123,
"ProjectStartDate": "2024-12-25T10:00:00Z",
"ProjectEndDate": "2024-12-25T10:00:00Z",
"ProjectTypeID": 123,
"ProjectCategoryID": 123,
"CurrencyID": 123,
"LegalEntityID": 123,
"DepartmentID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ContactID": 123,
"InvoicingCustomerReferenceID": "null",
"LanguageID": "null"
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/project/create-from-template", bytes.NewBuffer(data))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
require 'net/http'
require 'json'
uri = URI('https://app3.timelog.com/your-account/api/v1/project/create-from-template')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request.body = '{
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"Description": "example_string",
"ProjectTemplateID": 123,
"ProjectManagerID": 123,
"ProjectStartDate": "2024-12-25T10:00:00Z",
"ProjectEndDate": "2024-12-25T10:00:00Z",
"ProjectTypeID": 123,
"ProjectCategoryID": 123,
"CurrencyID": 123,
"LegalEntityID": 123,
"DepartmentID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ContactID": 123,
"InvoicingCustomerReferenceID": "null",
"LanguageID": "null"
}'
response = http.request(request)
puts response.body
{
"Properties": {
"ProjectID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Name": "John Doe",
"No": "example_string",
"ExpenseIsBillable": true,
"CustomerID": 123,
"Customer": {
"CustomerID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Name": "John Doe",
"No": "example_string",
"DefaultMileageDistance": 25,
"CustomerStatusID": 123,
"VATNo": "example_string",
"OrganizationNo": "example_string"
},
"ContactID": 123,
"Description": "example_string",
"DepartmentID": "null",
"ProjectManagerID": "null",
"AccountManagerID": "null",
"PartnerID": "null",
"ProjectTypeID": "null",
"ProjectCategoryID": "null",
"BudgetWorkHours": 3.14,
"BudgetWorkAmount": 3.14,
"StartDate": "null",
"EndDate": "null"
},
"Links": [
{
"Href": "example_string",
"Rel": "example_string"
}
],
"Actions": [
{}
]
}
{
"error": "Bad Request",
"message": "The request contains invalid parameters or malformed data",
"code": 400,
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
{
"error": "Forbidden",
"message": "You don't have permission to access this resource",
"code": 403
}
{
"error": "Internal Server Error",
"message": "An unexpected error occurred on the server",
"code": 500,
"requestId": "req_1234567890"
}
/v1/project/create-from-template
Target server for requests. Edit to use your own host.
OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp).
The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user.
> Not yet documented: how to obtain a token.
> Not yet documented: token lifetime.
Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp).
The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user.
> Not yet documented: how to obtain a token.
> Not yet documented: token lifetime.The media type of the request body
Project name. > Not yet documented: constraints.
Project number. Empty or omitted = the template / auto-number value is kept. Default: the number the template mapping produces > Not yet documented: default.
The customer the project is for.
Refers to CustomerRead.CustomerID. Look up valid values with GET /v1/customer.
Valid values come from GET /v1/customer.
Free-text project description.
The project template to copy.
Refers to ProjectTemplateRead.ProjectTemplateID.
The project manager, as a USER ID. Must be an existing, active user; stored as that user's employee.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
Valid values come from GET /v1/user.
Project start date. Time zone: not yet documented. > Not yet documented: constraints → timezone.
Project end date (stored as the contract end date, returned as ProjectRead.EndDate). Time zone: not yet documented. > Not yet documented: constraints → timezone.
Project type.
Refers to ProjectTypeRead.ProjectTypeID.
Project category; 0 = none.
Refers to ProjectCategoryRead.ProjectCategoryID.
Project currency. The legal entity must have a standard price group in this currency (else 30097).
Refers to CurrencyRead.CurrencyID. Look up valid values with GET /v1/currency/{status}.
Valid values come from GET /v1/currency/{status}.
Legal entity of the project; 0 = the account's default legal entity.
Refers to LegalEntityRead.LegalEntityID. Look up valid values with GET /v1/legal-entity.
Default: 0 → default legal entity
- 0 or omitted — GetDefaultLegalEntity()
Valid values come from GET /v1/legal-entity.
Handling department; 0 = the default department.
Refers to DepartmentRead.DepartmentID. Look up valid values with GET /v1/department.
Default: 0 → default department
Valid values come from GET /v1/department.
Account manager, as a USER ID (existing, active). Only used when the AccountManager feature is on.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Feature AccountManager is off — Set to none, not validated.
Valid values come from GET /v1/user.
Partner, as a USER ID (existing, active); stored as that user's EMPLOYEE. Only used when the Partner feature is on.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Feature Partner is off — Set to none, not validated.
Valid values come from GET /v1/user.
Customer contact of the project; 0 = none. A contact the lookup does not return is ignored.
Refers to ContactRead.ContactID.
> Not yet documented: description.
Customer reference on invoices: -1 = the project's contact; > 0 = this contact ID; null/0 = none.
Refers to ContactRead.ContactID.
Applied by create-from-template only; validate-create-from-template does not map it.
> Not yet documented: description.
Invoice language. Only used when the InvoiceMultilingual feature is on; null = the customer's default invoice language.
- Any other value — Validation (400), even when the feature is off.
- Feature InvoiceMultilingual is off — ignored
- null and feature on — Customer's default invoice language.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
| 1 | Danish | Invoices in Danish. |
| 2 | English | Invoices in English. |
| 4 | Swedish | Invoices in Swedish. |
| 15 | Norwegian | Invoices in Norwegian. |
| 16 | German | Invoices in German. |
| 17 | Dutch | Invoices in Dutch. |
| 18 | French | Invoices in French. |
Request Preview
Response
Response will appear here after sending the request
Authentication
Bearer token. OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp).
The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user.
Not yet documented: how to obtain a token.
Not yet documented: token lifetime.
Body
Project name.
Not yet documented: constraints.
Project number. Empty or omitted = the template / auto-number value is kept.
Default: the number the template mapping produces
Not yet documented: default.
The customer the project is for.
Refers to CustomerRead.CustomerID. Look up valid values with GET /v1/customer.
Valid values come from GET /v1/customer.
Free-text project description.
The project template to copy.
Refers to ProjectTemplateRead.ProjectTemplateID.
The project manager, as a USER ID. Must be an existing, active user; stored as that user's employee.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
Valid values come from GET /v1/user.
Project start date.
Time zone: not yet documented.
Not yet documented: constraints → timezone.
Project end date (stored as the contract end date, returned as ProjectRead.EndDate).
Time zone: not yet documented.
Not yet documented: constraints → timezone.
Project type.
Refers to ProjectTypeRead.ProjectTypeID.
Project category; 0 = none.
Refers to ProjectCategoryRead.ProjectCategoryID.
Project currency. The legal entity must have a standard price group in this currency (else 30097).
Refers to CurrencyRead.CurrencyID. Look up valid values with GET /v1/currency/{status}.
Valid values come from GET /v1/currency/{status}.
Legal entity of the project; 0 = the account's default legal entity.
Refers to LegalEntityRead.LegalEntityID. Look up valid values with GET /v1/legal-entity.
Default: 0 → default legal entity
- 0 or omitted — GetDefaultLegalEntity()
Valid values come from GET /v1/legal-entity.
Handling department; 0 = the default department.
Refers to DepartmentRead.DepartmentID. Look up valid values with GET /v1/department.
Default: 0 → default department
Valid values come from GET /v1/department.
Account manager, as a USER ID (existing, active). Only used when the AccountManager feature is on.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Feature AccountManager is off — Set to none, not validated.
Valid values come from GET /v1/user.
Partner, as a USER ID (existing, active); stored as that user's EMPLOYEE. Only used when the Partner feature is on.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Feature Partner is off — Set to none, not validated.
Valid values come from GET /v1/user.
Customer contact of the project; 0 = none. A contact the lookup does not return is ignored.
Refers to ContactRead.ContactID.
Not yet documented: description.
Customer reference on invoices: -1 = the project's contact; > 0 = this contact ID; null/0 = none.
Refers to ContactRead.ContactID.
Applied by create-from-template only; validate-create-from-template does not map it.
Not yet documented: description.
Invoice language. Only used when the InvoiceMultilingual feature is on; null = the customer's default invoice language.
-
Any other value — Validation (400), even when the feature is off.
-
Feature InvoiceMultilingual is off — ignored
-
null and feature on — Customer's default invoice language.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
1 | Danish | Invoices in Danish. |
2 | English | Invoices in English. |
4 | Swedish | Invoices in Swedish. |
15 | Norwegian | Invoices in Norwegian. |
16 | German | Invoices in German. |
17 | Dutch | Invoices in Dutch. |
18 | French | Invoices in French. |
12415161718Responses
The REST v1 project: identifiers, name and number, customer and contact, the responsible people (project manager, account manager, partner), department, type and category, the work budget and the start/end dates. It does not carry status, contracts, tasks or financials. The TAF self link points to GET /v1/project/{ProjectID}; each entity also lists the four TAF actions create-from-template, validate-create-from-template, update-project and update-project-status.