curl --request POST \
--url https://api.peridot.id/v1/experience/events \
--header 'Content-Type: application/json' \
--data '
{
"client_id": "pidapp_abc123",
"client_secret": "pidsk_...",
"event_id": "evt_123",
"user_id": "ifal@pid",
"event_type": "onboarding_completed"
}
'const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
client_id: 'pidapp_abc123',
client_secret: 'pidsk_...',
event_id: 'evt_123',
user_id: 'ifal@pid',
event_type: 'onboarding_completed'
})
};
fetch('https://api.peridot.id/v1/experience/events', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.peridot.id/v1/experience/events"
payload = {
"client_id": "pidapp_abc123",
"client_secret": "pidsk_...",
"event_id": "evt_123",
"user_id": "ifal@pid",
"event_type": "onboarding_completed"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.peridot.id/v1/experience/events"
payload := strings.NewReader("{\n \"client_id\": \"pidapp_abc123\",\n \"client_secret\": \"pidsk_...\",\n \"event_id\": \"evt_123\",\n \"user_id\": \"ifal@pid\",\n \"event_type\": \"onboarding_completed\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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))
}{
"status": "awarded",
"awarded_xp": 50,
"level": 2,
"leveledUp": true
}Report an application activity event (server-to-server)
Submit a supported activity event for a user so PeridotID can verify
it and award XP (ANT-89 M3). Server-to-server ONLY: authenticate with
the app’s client_id + client_secret in the body. Cookie/JWT
sessions are NEVER accepted here (401 without valid app credentials).
Restricted to Verified apps with explicit XP permission and an
admin-approved event type (403 otherwise, nothing written). The task
is resolved server-side (event_type → active task, app-scoped wins);
XP comes from the task’s configured reward — the request MUST NOT
carry any xp/amount field (stripped at the boundary, never read).
Idempotent per app: retries with the same (client_id, event_id)
return duplicate without a second award. Unknown subjects and
failed verifications share the generic not_eligible rejection (no
PID oracle). Awards are release-gated: 503 while
PID_XP_INTAKE_ENABLED is not exactly "true".
Rate limits: global default plus per-app (60/min) and per-app+subject (30/min) tiers.
curl --request POST \
--url https://api.peridot.id/v1/experience/events \
--header 'Content-Type: application/json' \
--data '
{
"client_id": "pidapp_abc123",
"client_secret": "pidsk_...",
"event_id": "evt_123",
"user_id": "ifal@pid",
"event_type": "onboarding_completed"
}
'const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
client_id: 'pidapp_abc123',
client_secret: 'pidsk_...',
event_id: 'evt_123',
user_id: 'ifal@pid',
event_type: 'onboarding_completed'
})
};
fetch('https://api.peridot.id/v1/experience/events', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.peridot.id/v1/experience/events"
payload = {
"client_id": "pidapp_abc123",
"client_secret": "pidsk_...",
"event_id": "evt_123",
"user_id": "ifal@pid",
"event_type": "onboarding_completed"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.peridot.id/v1/experience/events"
payload := strings.NewReader("{\n \"client_id\": \"pidapp_abc123\",\n \"client_secret\": \"pidsk_...\",\n \"event_id\": \"evt_123\",\n \"user_id\": \"ifal@pid\",\n \"event_type\": \"onboarding_completed\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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))
}{
"status": "awarded",
"awarded_xp": 50,
"level": 2,
"leveledUp": true
}Body
Public app identifier (pidapp_*)
Confidential-client secret (pidsk_*; never logged, never echoed)
App-scoped idempotency key (dedup is per app + event_id)
Subject user PID
^[a-z0-9_]{3,20}@pid$Approved event type (e.g. onboarding_completed)
Checker input (e.g. achievement/resource reference)
Response
Processing result (awarded, duplicate, or rejected with a reason)
Event-intake processing result. awarded_xp is always the server-side task reward (clients can never submit XP); reason is a machine-readable cause that never oracles PID existence.
awarded, duplicate, rejected x >= 0Authoritative level after processing (awarded/duplicate)
x >= 1Machine-readable cause (duplicate_event, not_eligible, task_limit, cooldown, quota_exceeded, suspended, no_active_task, task_not_available, app_not_permitted)