Create a payment on a contract
Adds an instalment to a contract's billing plan and returns it.
Adds a payment (instalment to invoice) to a fixed-price, time-and-material account-end-balancing, prepaid-services or task-driven-revenue contract of the project, then saves the project's billing plan. Answers 200 with the new payment (PaymentRead). On task-driven-revenue contracts a payment can be linked to a task and flagged as the task's fixed-price instalment — which unlinks the task's other instalments (see side effects).
Use it to:
- Import a payment plan from an ERP or quote tool
Rules across fields:
- R1: ProjectSubContractID must be a contract of ProjectID (checked during body validation).
- R2: The contract must be FixedPriceBasic, TimeMaterialAccountEndBalancing, PrepaidServices or TaskDrivenRevenue (400).
- R3: TaskID and IsFixedPricePayment only on a TaskDrivenRevenue contract (400); a TaskID must be on the contract.
Behaviour:
- Not idempotent.
- Not safe to retry blindly. Every call adds a new instalment; nothing deduplicates. After a lost response, list the contract's payments before retrying.
- Side effect: Adds the instalment (InstallmentType 1 for fixed-price contracts, else 3; product number defaulted when the feature is on) and saves the whole billing plan through ProjectManager.Save.
- Side effect: IsFixedPricePayment = true: every OTHER instalment of the project with the same TaskID loses its task link (TaskID → null) and its fixed-price flag.
- Side effect: Per saved payment: payment budget periods (recurrence contracts), contract payment-plan amount sync (fixed-price / task-driven), invoicing-potential and registration-currency notifications, contract pending-booking status refresh.
- Changes are visible to the next read immediately.
Call first: GET /v1/project/get-all, POST /v1/payment/validate-new-payment
Next: GET /v1/payment/{paymentID}, GET /v1/payment
Not yet documented: behaviour → events emitted; behaviour → side effects on failure; examples; request → body.
curl -X POST "https://app3.timelog.com/your-account/api/v1/payment" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"Name": "John Doe",
"Comment": "example_string",
"Amount": 3.14,
"InvoiceDate": "2024-12-25T10:00:00Z",
"IsReadyForInvoicing": true,
"ProjectID": 123,
"TaskID": 123,
"ProductNumber": "example_string",
"DiscountPercentage": 3.14,
"ProjectSubContractID": 123,
"Quantity": 3.14,
"IsFixedPricePayment": true,
"UnitType": 42
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/payment"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"Name": "John Doe",
"Comment": "example_string",
"Amount": 3.14,
"InvoiceDate": "2024-12-25T10:00:00Z",
"IsReadyForInvoicing": true,
"ProjectID": 123,
"TaskID": 123,
"ProductNumber": "example_string",
"DiscountPercentage": 3.14,
"ProjectSubContractID": 123,
"Quantity": 3.14,
"IsFixedPricePayment": true,
"UnitType": 42
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/payment", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"Name": "John Doe",
"Comment": "example_string",
"Amount": 3.14,
"InvoiceDate": "2024-12-25T10:00:00Z",
"IsReadyForInvoicing": true,
"ProjectID": 123,
"TaskID": 123,
"ProductNumber": "example_string",
"DiscountPercentage": 3.14,
"ProjectSubContractID": 123,
"Quantity": 3.14,
"IsFixedPricePayment": true,
"UnitType": 42
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"Name": "John Doe",
"Comment": "example_string",
"Amount": 3.14,
"InvoiceDate": "2024-12-25T10:00:00Z",
"IsReadyForInvoicing": true,
"ProjectID": 123,
"TaskID": 123,
"ProductNumber": "example_string",
"DiscountPercentage": 3.14,
"ProjectSubContractID": 123,
"Quantity": 3.14,
"IsFixedPricePayment": true,
"UnitType": 42
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/payment", 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/payment')
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",
"Comment": "example_string",
"Amount": 3.14,
"InvoiceDate": "2024-12-25T10:00:00Z",
"IsReadyForInvoicing": true,
"ProjectID": 123,
"TaskID": 123,
"ProductNumber": "example_string",
"DiscountPercentage": 3.14,
"ProjectSubContractID": 123,
"Quantity": 3.14,
"IsFixedPricePayment": true,
"UnitType": 42
}'
response = http.request(request)
puts response.body
{
"Properties": {
"Name": "John Doe",
"PaymentID": 123,
"PaymentGuid": "123e4567-e89b-12d3-a456-426614174000",
"Comment": "example_string",
"Amount": 3.14,
"InvoiceDate": "2024-12-25T10:00:00Z",
"IsReadyForInvoicing": true,
"InstallmentType": 42,
"ProjectID": 123,
"TaskID": 123,
"ProductNumberID": 123,
"Discount": 3.14,
"ProjectSubContractID": 123,
"Quantity": 3.14,
"UnitType": 42,
"Rate": 3.14,
"IsFixedPricePayment": true,
"ExpensesCost": 3.14
},
"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
}
{
"Code": 102,
"Details": [
{
"Message": "No contract with ContractID: 0 exist for project with ProjectID: 0",
"ErrorCode": 37042
}
],
"Message": "Business rules exception.",
"Parameters": "createModel: {"Name":null, ...}",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/payment"
}
/v1/payment
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
Instalment name.
Free-text comment.
Instalment amount, in the project currency. Unit: project currency > Not yet documented: description.
Date the instalment is due for invoicing. Time zone: not yet documented. > Not yet documented: constraints → timezone.
Release the instalment for invoicing immediately.
The project. Must exist (37014) and be accessible to the caller (403).
Refers to ProjectRead.ProjectID. Look up valid values with GET /v1/project/get-all.
Valid values come from GET /v1/project/get-all.
Task to link the instalment to; only allowed (non-zero) on a task-driven-revenue contract, and the task must be on that contract (37949). 0 = none.
Conditional. Must be 0 unless the contract is TaskDrivenRevenue (400 "TaskID can only be added when importing on a Task Driven Revenue Contract.").
Refers to TaskRead.TaskID.
Product number TEXT (not an ID). Only used when the InvoiceSetting_ProductNoActive feature is on: a supplied value must exist in the project's legal entity (37939); empty = the default product number for payments (fixed-price contracts: the project's fixed-price product number).
Refers to ProductNumberRead.ProductNumber.
- Feature InvoiceSetting_ProductNoActive is off — Stored as empty.
- Empty and feature on — defaulted
Discount in percent. Unit: percent
The contract. Must belong to ProjectID (37042, raised inside model validation) and be FixedPriceBasic, TimeMaterialAccountEndBalancing, PrepaidServices or TaskDrivenRevenue (400 "The selected contract does not support import of payment.").
Refers to ContractRead.ProjectSubContractID.
Quantity in UnitType units. Unit: UnitType
Make this the fixed-price instalment of TaskID. Only allowed on a task-driven-revenue contract (400 otherwise). Conditional. Must be false unless the contract is TaskDrivenRevenue.
Unit of Quantity.
- Value not in the enum (e.g. 5) — 400.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
| 0 | Undefined | No unit. |
| 1 | Hours | Hours. |
| 2 | Minutes | Minutes. |
| 3 | Days | Days. |
| 4 | Km | Kilometres. |
| 6 | Pieces | Pieces. |
| 7 | Liters | Litres. |
| 8 | Meters | Metres. |
| 9 | Kilograms | Kilograms. |
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
Instalment name.
Free-text comment.
Instalment amount, in the project currency.
Unit: project currency
Not yet documented: description.
Date the instalment is due for invoicing.
Time zone: not yet documented.
Not yet documented: constraints → timezone.
Release the instalment for invoicing immediately.
The project. Must exist (37014) and be accessible to the caller (403).
Refers to ProjectRead.ProjectID. Look up valid values with GET /v1/project/get-all.
Valid values come from GET /v1/project/get-all.
Task to link the instalment to; only allowed (non-zero) on a task-driven-revenue contract, and the task must be on that contract (37949). 0 = none.
Conditional. Must be 0 unless the contract is TaskDrivenRevenue (400 "TaskID can only be added when importing on a Task Driven Revenue Contract.").
Refers to TaskRead.TaskID.
Product number TEXT (not an ID). Only used when the InvoiceSetting_ProductNoActive feature is on: a supplied value must exist in the project's legal entity (37939); empty = the default product number for payments (fixed-price contracts: the project's fixed-price product number).
Refers to ProductNumberRead.ProductNumber.
-
Feature InvoiceSetting_ProductNoActive is off — Stored as empty.
-
Empty and feature on — defaulted
Discount in percent.
Unit: percent
The contract. Must belong to ProjectID (37042, raised inside model validation) and be FixedPriceBasic, TimeMaterialAccountEndBalancing, PrepaidServices or TaskDrivenRevenue (400 "The selected contract does not support import of payment.").
Refers to ContractRead.ProjectSubContractID.
Quantity in UnitType units.
Unit: UnitType
Make this the fixed-price instalment of TaskID. Only allowed on a task-driven-revenue contract (400 otherwise).
Conditional. Must be false unless the contract is TaskDrivenRevenue.
Unit of Quantity.
- Value not in the enum (e.g. 5) — 400.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
0 | Undefined | No unit. |
1 | Hours | Hours. |
2 | Minutes | Minutes. |
3 | Days | Days. |
4 | Km | Kilometres. |
6 | Pieces | Pieces. |
7 | Liters | Litres. |
8 | Meters | Metres. |
9 | Kilograms | Kilograms. |
012346789Responses
One billing-plan instalment of a contract: what will be invoiced (name, quantity × rate less discount = amount), when (InvoiceDate), on which project / contract / task, and whether it is ready for invoicing. The TAF self link points to GET /v1/payment/{PaymentID}; each entity also lists the six payment TAF actions.