> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.taktoria.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.taktoria.com/_mcp/server.

# Update Key Result

PATCH https://api.taktoria.com/api/v1/key-results/{keyResultId}
Content-Type: application/json

Setting `score` here records a check-in with no note, so the history
never misses a score; to say why it moved, post a check-in instead,
which does the same with a note. A **Final** grade set on the
scorecard still outranks either: while there is one, it stays the
score.

A key may change any key result. A signed-in session changes what the
app lets its person: everything, if they may edit the objective (see
`updateObjective`); `score` alone, if the key result is theirs or one
of their teams'. Anything else is `403 forbidden`.

Reference: https://docs.taktoria.com/api-reference/okr/update-key-result

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Servers

- `https://api.taktoria.com/api/v1` (Production, default)
- `http://localhost:3000/api/v1` (Local)

## Request

### Path parameters

- `keyResultId` (string, required)

### Body (application/json)

This endpoint expects an UpdateKeyResultRequest.

- `title` (string, optional)
- `score` (double, optional) — A new score, 0.0 to 1.0, is recorded as a check-in with no note, by the caller (a key's maker, for a key), and the activity feed says the key result was scored, as the dashboard's score grid does. The same score again records nothing. `null` clears it; any check-in or scorecard grade it has then shows again.
- `weight` (double, optional) — 0.01 to 100.
- `priority` (enum, optional) — A key result's priority, and the dimension scores are averaged by, so a workspace can see how it did on what mattered most rather than only in aggregate. Objectives have none: a priority's average takes each objective on its key results of that priority alone, with the objective's own weight.
  - Allowed values: `P0`, `P1`, `P2`, `P3`
- `draft` (boolean, optional) — `false` publishes it.
- `sortOrder` (integer, optional)
- `ownerUserIds` (list of string, optional)
- `ownerTeamIds` (list of string, optional)

## Response

### 200

- `id` (string, required)
- `objectiveId` (string, required)
- `title` (string, required)
- `weight` (double, required) — Relative weight within its own objective, 0.01 to 100.
- `priority` (enum, required) — A key result's priority, and the dimension scores are averaged by, so a workspace can see how it did on what mattered most rather than only in aggregate. Objectives have none: a priority's average takes each objective on its key results of that priority alone, with the objective's own weight.
  - Allowed values: `P0`, `P1`, `P2`, `P3`
- `draft` (boolean, required) — Still being worked out, not yet agreed. A label only: a draft is seen, scored, rolled up and checked in on like any other.
- `sortOrder` (integer, required) — Its place among its objective's key results, lowest first.
- `ownerUserIds` (list of string, required) — Members of the workspace who own it, directly.
- `ownerTeamIds` (list of string, required) — Teams of the workspace that own it; their members count as owners.
- `createdAt` (datetime, required)
- `updatedAt` (datetime, required)
- `score` (double, optional) — Google-style grade from 0.0 to 1.0, or absent until it has been scored. 0.7 and up is delivered for an ambitious goal, 0.4 to 0.7 is progress, and under 0.4 is missed. A **Final** grade set on the scorecard is the score. Until there is one, the score is the newest of the key result's other scorecard grades and its latest check-in. A score reset on the scorecard (`noScore`) empties that cell only and leaves this alone. It is what the dashboard's lists show, and what the objective's `score` rolls up.
- `lastCheckInAt` (datetime, optional) — When it was last checked in on: its newest check-in's `recordedAt`, so a check-in recorded late counts at its date. Absent if it has never been checked in on. How long it has gone quiet.
- `metric` (KeyResultMetric, optional) — The connected metric that drives its score, absent if none. Each number the metric hears becomes a check-in, and a person's own check-in stands only until the metric next moves. Sources are set up on the key result's page in the app, not through the API.

## Errors

### 403 Forbidden Error

Refused, and `error.code` says why. On an app's write endpoints, a signed-in session is refused `viewer_only` when the person is a Viewer of the app (they read it, and change nothing in it), and `seat_required` without a seat on it; a key is never either. Beyond those: `admin_required` when the change is an admin's (an app admin's for an app's admin powers, a workspace admin's for the workspace's settings), `forbidden` when the person may not change this one thing, `read_only_session` for a change only a key makes, `person_required` for what only a signed-in person does through their own account elsewhere (exporting a LaunchCal artifact to their docs tool), which a key cannot, `insufficient_scope` for a key without the scope, and `google_sign_in_required` for a session the workspace's Google rule turns away. Nothing was done.

- `error` (ErrorDetail, required)

### 404 Not Found Error

- `error` (ErrorDetail, required)

### 422 Validation Error

- `error` (ErrorDetail, required)

### 429 Too Many Requests Error

Rate limited (`rate_limited`): the key, the signed-in person, the workspace or an expensive endpoint's own budget is used up for now. `Retry-After` says how many seconds to wait, and `details` names the `policy`, its `limit`, `windowSeconds` and `retryAfter`. Every answer to an authenticated request carries the IETF `RateLimit-Policy` and `RateLimit` headers, so a client can slow down before it gets this. Nothing was done; wait and send it again. See "Rate limits".

- `error` (ErrorDetail, required)

## Types

### KeyResultMetric

The metric a key result follows, as anybody who may see the key result sees it. Never its secret, token or address.

- `name` (string, required) — What the number is, as the history names it, such as "Weekly active teams".
- `kind` (enum, required) — How a metric hears its number.
  - Allowed values: `WEBHOOK`, `PULL`, `TICKETING`
- `paused` (boolean, required) — Paused, it records nothing until somebody resumes it.

### ErrorDetail

- `code` (string, required) — A stable machine-readable code such as `not_found`.
- `message` (string, required)
- `details` (any, optional)

## Examples

**Request**

```json
{}
```

**Response**

```json
{
  "id": "id",
  "objectiveId": "objectiveId",
  "title": "title",
  "weight": 1.1,
  "priority": "P0",
  "draft": true,
  "sortOrder": 1,
  "ownerUserIds": [
    "ownerUserIds",
    "ownerUserIds"
  ],
  "ownerTeamIds": [
    "ownerTeamIds",
    "ownerTeamIds"
  ],
  "createdAt": "2024-01-15T09:30:00Z",
  "updatedAt": "2024-01-15T09:30:00Z",
  "score": 1.1,
  "lastCheckInAt": "2024-01-15T09:30:00Z",
  "metric": {
    "name": "name",
    "kind": "WEBHOOK",
    "paused": true
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.taktoria.com/api/v1/key-results/keyResultId"

payload = {}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.taktoria.com/api/v1/key-results/keyResultId';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.taktoria.com/api/v1/key-results/keyResultId"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.taktoria.com/api/v1/key-results/keyResultId")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.taktoria.com/api/v1/key-results/keyResultId")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.taktoria.com/api/v1/key-results/keyResultId', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.taktoria.com/api/v1/key-results/keyResultId");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.taktoria.com/api/v1/key-results/keyResultId")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```