curl --request POST \
--url https://api.zivio.net/api/v4/projects/{id}/complete \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'zivio-tenant-id: <api-key>' \
--data '
{
"ended_at": "2026-07-28T17:00:00Z",
"reason_code_id": 7,
"custom_reason": "All milestones delivered and signed off."
}
'import requests
url = "https://api.zivio.net/api/v4/projects/{id}/complete"
payload = {
"ended_at": "2026-07-28T17:00:00Z",
"reason_code_id": 7,
"custom_reason": "All milestones delivered and signed off."
}
headers = {
"Authorization": "Bearer <token>",
"zivio-tenant-id": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
Authorization: 'Bearer <token>',
'zivio-tenant-id': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
ended_at: '2026-07-28T17:00:00Z',
reason_code_id: 7,
custom_reason: 'All milestones delivered and signed off.'
})
};
fetch('https://api.zivio.net/api/v4/projects/{id}/complete', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zivio.net/api/v4/projects/{id}/complete",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'ended_at' => '2026-07-28T17:00:00Z',
'reason_code_id' => 7,
'custom_reason' => 'All milestones delivered and signed off.'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"zivio-tenant-id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zivio.net/api/v4/projects/{id}/complete"
payload := strings.NewReader("{\n \"ended_at\": \"2026-07-28T17:00:00Z\",\n \"reason_code_id\": 7,\n \"custom_reason\": \"All milestones delivered and signed off.\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("zivio-tenant-id", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.zivio.net/api/v4/projects/{id}/complete")
.header("Authorization", "Bearer <token>")
.header("zivio-tenant-id", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"ended_at\": \"2026-07-28T17:00:00Z\",\n \"reason_code_id\": 7,\n \"custom_reason\": \"All milestones delivered and signed off.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zivio.net/api/v4/projects/{id}/complete")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["zivio-tenant-id"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"ended_at\": \"2026-07-28T17:00:00Z\",\n \"reason_code_id\": 7,\n \"custom_reason\": \"All milestones delivered and signed off.\"\n}"
response = http.request(request)
puts response.read_body{
"project_id": 123,
"project_title": "Website Redesign",
"state": "work_completed",
"changed": true,
"message": "Project 123 completed"
}{
"error": "invalid_token",
"error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons"
}{
"error": "insufficient_scope",
"error_description": "The request requires higher privileges than provided by the access token",
"required_scope": "projects:write",
"provided_scopes": [
"welcome:read"
]
}{
"error": "invalid_state",
"message": "Project 123 cannot be completed from its current state: new",
"state": "new"
}Mark a delivered project's work as completed
Completes a project in delivery (state in_progress -> work_completed) via the same interactor as the web UI: stamps the completion, notifies, and records the closure reason log when the tenant uses reason codes. The project closes automatically after its auto-close window. Accepts an optional ended_at (defaults to now) and, for tenants with closure reason codes, an optional reason_code_id/custom_reason. Idempotent: completing an already-completed project reports changed: false.
curl --request POST \
--url https://api.zivio.net/api/v4/projects/{id}/complete \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'zivio-tenant-id: <api-key>' \
--data '
{
"ended_at": "2026-07-28T17:00:00Z",
"reason_code_id": 7,
"custom_reason": "All milestones delivered and signed off."
}
'import requests
url = "https://api.zivio.net/api/v4/projects/{id}/complete"
payload = {
"ended_at": "2026-07-28T17:00:00Z",
"reason_code_id": 7,
"custom_reason": "All milestones delivered and signed off."
}
headers = {
"Authorization": "Bearer <token>",
"zivio-tenant-id": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
Authorization: 'Bearer <token>',
'zivio-tenant-id': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
ended_at: '2026-07-28T17:00:00Z',
reason_code_id: 7,
custom_reason: 'All milestones delivered and signed off.'
})
};
fetch('https://api.zivio.net/api/v4/projects/{id}/complete', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zivio.net/api/v4/projects/{id}/complete",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'ended_at' => '2026-07-28T17:00:00Z',
'reason_code_id' => 7,
'custom_reason' => 'All milestones delivered and signed off.'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"zivio-tenant-id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zivio.net/api/v4/projects/{id}/complete"
payload := strings.NewReader("{\n \"ended_at\": \"2026-07-28T17:00:00Z\",\n \"reason_code_id\": 7,\n \"custom_reason\": \"All milestones delivered and signed off.\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("zivio-tenant-id", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.zivio.net/api/v4/projects/{id}/complete")
.header("Authorization", "Bearer <token>")
.header("zivio-tenant-id", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"ended_at\": \"2026-07-28T17:00:00Z\",\n \"reason_code_id\": 7,\n \"custom_reason\": \"All milestones delivered and signed off.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zivio.net/api/v4/projects/{id}/complete")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["zivio-tenant-id"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"ended_at\": \"2026-07-28T17:00:00Z\",\n \"reason_code_id\": 7,\n \"custom_reason\": \"All milestones delivered and signed off.\"\n}"
response = http.request(request)
puts response.read_body{
"project_id": 123,
"project_title": "Website Redesign",
"state": "work_completed",
"changed": true,
"message": "Project 123 completed"
}{
"error": "invalid_token",
"error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons"
}{
"error": "insufficient_scope",
"error_description": "The request requires higher privileges than provided by the access token",
"required_scope": "projects:write",
"provided_scopes": [
"welcome:read"
]
}{
"error": "invalid_state",
"message": "Project 123 cannot be completed from its current state: new",
"state": "new"
}Authorizations
OAuth 2.0 client credentials. The access token from POST /oauth/token is sent as a bearer token. Request only the scopes you need.
Your Zivio organisation identifier. The regional API endpoints serve every Zivio organisation, so each request must identify yours.
Path Parameters
Project ID
Body
When the work actually ended (ISO 8601). Defaults to the completion time.
"2026-07-28T17:00:00Z"
A closure reason code id, for tenants with closure reason codes configured.
7
Free-text detail accompanying the closure reason code.
"All milestones delivered and signed off."
Response
The project state after the call
The response is of type object.

