curl --request POST \
--url https://app.orgo.space/api/v1/user_notes \
--header 'Api-Token: <api-key>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"user": "/api/v1/users/1",
"text": "Hello, you're invited to the Annual General Meeting.",
"action": "string"
}
EOFimport requests
url = "https://app.orgo.space/api/v1/user_notes"
payload = {
"user": "/api/v1/users/1",
"text": "Hello, you're invited to the Annual General Meeting.",
"action": "string"
}
headers = {
"Api-Token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Api-Token': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
user: '/api/v1/users/1',
text: 'Hello, you\'re invited to the Annual General Meeting.',
action: 'string'
})
};
fetch('https://app.orgo.space/api/v1/user_notes', 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://app.orgo.space/api/v1/user_notes",
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([
'user' => '/api/v1/users/1',
'text' => 'Hello, you\'re invited to the Annual General Meeting.',
'action' => 'string'
]),
CURLOPT_HTTPHEADER => [
"Api-Token: <api-key>",
"Content-Type: application/json"
],
]);
$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://app.orgo.space/api/v1/user_notes"
payload := strings.NewReader("{\n \"user\": \"/api/v1/users/1\",\n \"text\": \"Hello, you're invited to the Annual General Meeting.\",\n \"action\": \"string\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Api-Token", "<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://app.orgo.space/api/v1/user_notes")
.header("Api-Token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"user\": \"/api/v1/users/1\",\n \"text\": \"Hello, you're invited to the Annual General Meeting.\",\n \"action\": \"string\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.orgo.space/api/v1/user_notes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Api-Token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"user\": \"/api/v1/users/1\",\n \"text\": \"Hello, you're invited to the Annual General Meeting.\",\n \"action\": \"string\"\n}"
response = http.request(request)
puts response.read_body{
"uuid": "01938d8e-9c4f-7c2a-b8e1-3f7a9b8c4f12",
"text": "Hello, you're invited to the Annual General Meeting.",
"action": "string",
"dateCreated": "2026-01-15T10:30:00+00:00",
"dateUpdated": "2026-01-15T10:30:00+00:00",
"authorName": "Boston Chapter",
"authorId": 42,
"authorLogo": "https://cdn.orgo.space/tenants/acme/logo.png"
}Add a note to a user
Creates a note on a user. The author is set to the current user and the tenant is inherited from the subject user. Requires HR_LOCAL or FINANCIAL_LOCAL permission scoped to the subject user local center.
curl --request POST \
--url https://app.orgo.space/api/v1/user_notes \
--header 'Api-Token: <api-key>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"user": "/api/v1/users/1",
"text": "Hello, you're invited to the Annual General Meeting.",
"action": "string"
}
EOFimport requests
url = "https://app.orgo.space/api/v1/user_notes"
payload = {
"user": "/api/v1/users/1",
"text": "Hello, you're invited to the Annual General Meeting.",
"action": "string"
}
headers = {
"Api-Token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Api-Token': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
user: '/api/v1/users/1',
text: 'Hello, you\'re invited to the Annual General Meeting.',
action: 'string'
})
};
fetch('https://app.orgo.space/api/v1/user_notes', 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://app.orgo.space/api/v1/user_notes",
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([
'user' => '/api/v1/users/1',
'text' => 'Hello, you\'re invited to the Annual General Meeting.',
'action' => 'string'
]),
CURLOPT_HTTPHEADER => [
"Api-Token: <api-key>",
"Content-Type: application/json"
],
]);
$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://app.orgo.space/api/v1/user_notes"
payload := strings.NewReader("{\n \"user\": \"/api/v1/users/1\",\n \"text\": \"Hello, you're invited to the Annual General Meeting.\",\n \"action\": \"string\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Api-Token", "<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://app.orgo.space/api/v1/user_notes")
.header("Api-Token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"user\": \"/api/v1/users/1\",\n \"text\": \"Hello, you're invited to the Annual General Meeting.\",\n \"action\": \"string\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.orgo.space/api/v1/user_notes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Api-Token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"user\": \"/api/v1/users/1\",\n \"text\": \"Hello, you're invited to the Annual General Meeting.\",\n \"action\": \"string\"\n}"
response = http.request(request)
puts response.read_body{
"uuid": "01938d8e-9c4f-7c2a-b8e1-3f7a9b8c4f12",
"text": "Hello, you're invited to the Annual General Meeting.",
"action": "string",
"dateCreated": "2026-01-15T10:30:00+00:00",
"dateUpdated": "2026-01-15T10:30:00+00:00",
"authorName": "Boston Chapter",
"authorId": 42,
"authorLogo": "https://cdn.orgo.space/tenants/acme/logo.png"
}Authorizations
Server-to-server authentication. Generate a token in the admin UI at
Settings → Developers → API Access. Send the raw token in the
Api-Token header — there is no Bearer prefix.
Tokens can be marked read-only at creation time, in which case the API
rejects any non-GET request with 403 Forbidden.
Body
The new UserNote resource
A single, attributed note left on a User by HR/Financial staff. Mirrors ContactNote: an append-style thread where each entry records its author and timestamps.
Unlike ContactNote there is no legacy blob to migrate, but $author stays nullable so a deleted staff account (onDelete SET NULL) does not take the note text with it.
Notes are NOT serialized into user_read: that group is served on the plain
profile GET, the user list and self-profile-update, so embedding them would
expose staff notes to the subject and to peers. They are read only through
the UserNote collection, gated by UserNoteCollectionExtension.
Tenant is mapped explicitly (not delegated to the subject user) because
UserNote is an API Platform resource: the tenant-isolation Doctrine
extensions emit WHERE o.tenant = :tenant, which requires a real column.
Response
UserNote resource created
A single, attributed note left on a User by HR/Financial staff. Mirrors ContactNote: an append-style thread where each entry records its author and timestamps.
Unlike ContactNote there is no legacy blob to migrate, but $author stays nullable so a deleted staff account (onDelete SET NULL) does not take the note text with it.
Notes are NOT serialized into user_read: that group is served on the plain
profile GET, the user list and self-profile-update, so embedding them would
expose staff notes to the subject and to peers. They are read only through
the UserNote collection, gated by UserNoteCollectionExtension.
Tenant is mapped explicitly (not delegated to the subject user) because
UserNote is an API Platform resource: the tenant-isolation Doctrine
extensions emit WHERE o.tenant = :tenant, which requires a real column.

