User Report Cards REST API

User Report Cards agregă metrici per-user (progres la cursuri, scoruri la survey-uri, activitate și altele) în view-uri de tip grid și detail, în panoul de administrare WordPress. Acest ghid este pentru dezvoltatorii externi care vor să citească aceleași date programatic — dintr-un serviciu de raportare, un sync către un data warehouse, un dashboard intern sau orice client HTTP din afara WordPress.

Tot ce urmează este servit de Enlivy Kit REST API, sub namespace-ul enlivy-kit/v1. Nu ai nevoie de cod de plugin de partea ta — doar de un client HTTP autentificat.


Base URL și versionare

Toate endpoint-urile se află sub o singură cale de bază, versionată. Înlocuiește host-ul cu domeniul site-ului tău:

https://yourdomain.com/wp-json/enlivy-kit/v1/user-report-card
  • Namespace: enlivy-kit/v1
  • Resource root: /user-report-card
  • Content type: corpurile de request și response sunt JSON (Content-Type: application/json).

Atenție — endpoint-urile de query folosesc POST. Chiar dacă doar citesc date, /query/grid, /query/detail și /query/drilldown primesc parametrii într-un corp JSON, deci sunt înregistrate ca POST. Este intenționat: selecțiile de card-uri și filtrele de useri sunt obiecte structurate care nu încap curat într-un query string. Tratează-le ca operațiuni de citire care se întâmplă să fie POST-uri.


Autentificare

Aceste rute sunt private — rezolvă date reale de useri, deci fiecare request trebuie autentificat ca un user WordPress care deține capability-ul potrivit. Există două moduri de autentificare:

  • Application Password prin HTTP Basic Auth — mecanismul recomandat pentru acces extern, server-to-server.
  • Cookie + header X-WP-Nonce — doar când apelezi din JavaScript, într-o sesiune wp-admin logată. Pentru orice din afara browser-ului, folosește un Application Password.

Crearea unui Application Password

  1. În wp-admin, deschide Users → Profile pentru contul cu care va rula integrarea.
  2. Derulează la Application Passwords, denumește credențialul (ex. reporting_bot) și generează-l.
  3. Copiază valoarea generată — nu o vei mai vedea din nou — și trimite-o ca parolă în HTTP Basic Auth.
curl https://yourdomain.com/wp-json/enlivy-kit/v1/user-report-card/config \
  -u "reporting_bot:abcd EFGH ijkl MNOP qrst UVWX"

Valoarea de după două puncte este Application Password-ul generat (spațiile fac parte din el și sunt ignorate de WordPress). Apelează întotdeauna prin HTTPS, ca să nu trimiți credențialul necriptat.

Capabilities necesare

Capability Permite
enlivy_kit_report_card_view Citește config, listează și citește view-uri, rulează query-uri grid / detail / drilldown și export.
enlivy_kit_report_card_management Tot ce e mai sus, plus crearea, actualizarea și ștergerea view-urilor salvate.

Un user fără niciunul dintre capability-uri primește 401/403. Pentru o integrare read-only, un cont dedicat cu doar enlivy_kit_report_card_view este cea mai sigură alegere.


Referință endpoint-uri

Method Path (după base URL) Descriere Capability
GET /config Listează toate tipurile de card-uri înregistrate, grupurile și opțiunile lor de configurare. view
GET /views Listează view-urile salvate. view
POST /views Creează un view salvat. manage
GET /views/{view_id} Citește un singur view salvat. view
PUT / PATCH /views/{view_id} Actualizează un view salvat. manage
DELETE /views/{view_id} Șterge un view salvat. manage
POST /query/grid Rulează un query grid pentru mai mulți useri (endpoint-ul principal de citire). view
POST /query/detail Rulează un query detail pentru un singur user. view
POST /query/drilldown Drill-down în date ierarhice, pentru un user și un card. view
GET /views/{view_id}/export Export plat, pe rânduri/coloane, al unui view salvat (paginat cu cursor). view

Descoperirea card-urilor: GET /config

Începe de aici. Endpoint-ul config îți spune ce tipuri de card-uri există pe acest site, cum sunt grupate și ce opțiuni de configurare acceptă fiecare. Folosești valorile type returnate pentru a construi request-urile de query.

GET /wp-json/enlivy-kit/v1/user-report-card/config

Response (prescurtat):

{
  "data": {
    "card_groups": {
      "learning": { "label": "Learning", "order": 10 }
    },
    "cards": [
      {
        "type": "course_progress",
        "label": "Course Progress",
        "group": "learning",
        "order": 10,
        "supports_drilldown": true,
        "config_options": [
          {
            "key": "date_range",
            "type": "select",
            "label": "Date Range",
            "options": { "all_time": "All Time", "last_30": "Last 30 Days" },
            "default": "all_time"
          }
        ],
        "display_formats": ["text", "percentage", "fraction"],
        "default_display": "percentage"
      }
    ]
  }
}
  • type — identificatorul pe care îl treci în cards[].type dintr-un query.
  • config_options — cheile pe care le poți seta sub cards[].config.
  • supports_drilldown — dacă /query/drilldown are sens pentru acest card.

Citirea grid-ului: POST /query/grid

Acesta este endpoint-ul pe care îl vei folosi cel mai des. Returnează un rând per user, cu o coloană per card cerut. Poți fie să îl indici către un view salvat (prin view_id), fie să furnizezi ad-hoc un set de cards și un user_filter inline.

Corpul request-ului

POST /wp-json/enlivy-kit/v1/user-report-card/query/grid
Content-Type: application/json

{
  "cards": [
    { "type": "course_progress", "label": "Onboarding", "config": { "date_range": "last_30" } }
  ],
  "user_filter": { "groups": [] },
  "order_by": [],
  "per_page": 50,
  "cursor": null
}
Parametru Tip Note
view_id string Opțional. Dacă e setat, cards / user_filter vin din view-ul salvat, iar valorile din corp sunt ignorate (cu excepția order_by).
cards array Obligatoriu când nu există view_id. Fiecare intrare: { type, label?, config? }. Cel puțin unul este necesar.
user_filter object Ce useri să fie incluși, ex. { "groups": [...] }. groups gol înseamnă toți userii.
order_by array Specificație de sortare opțională, corelată cu sortable_columns din response.
per_page int Implicit 50.
cursor object / null Trimite înapoi cursor-ul din response-ul anterior ca să iei pagina următoare.

Corpul response-ului

{
  "users": [
    {
      "user": { "id": 42, "email": "[email protected]", "display_name": "Jane Doe" },
      "columns": {
        "0": {
          "value": 80,
          "display_value": "80%",
          "action_items": [ { "label": "Lessons", "completed": 8, "total": 10 } ],
          "metadata": [ { "label": "Last Activity", "value": "2 days ago" } ]
        }
      }
    }
  ],
  "columns": {
    "0": { "type": "course_progress", "label": "Onboarding", "display": "percentage" }
  },
  "total": 137,
  "per_page": 50,
  "has_more": true,
  "cursor": { "after_user_id": 42 },
  "sortable_columns": { }
}
  • users[].columns este indexat după poziția card-ului din request-ul tău ("0", "1", …), corespunzând cu columns.
  • total este numărul complet de useri care se potrivesc; users conține doar pagina curentă.
  • Când has_more este true, trimite cursor înapoi identic la apelul următor.

Un user în detaliu: POST /query/detail

Acolo unde grid-ul îți dă un rând de sumar, /query/detail returnează defalcarea completă pentru un singur user, pe toate card-urile cerute — sumarul plus payload-ul detail al fiecărui card.

POST /wp-json/enlivy-kit/v1/user-report-card/query/detail
Content-Type: application/json

{
  "user_id": 42,
  "cards": [ { "type": "course_progress", "config": {} } ]
}
{
  "user_id": 42,
  "user": { "id": 42, "email": "[email protected]", "display_name": "Jane Doe" },
  "cards": [
    {
      "type": "course_progress",
      "label": "Course Progress",
      "config": {},
      "summary": { "value": 80, "display_value": "80%" },
      "detail": { "items": [ ], "summary": { "total": 10, "completed": 8 } }
    }
  ]
}

user_id este obligatoriu. Ca și la grid, poți trimite view_id în locul unui array cards inline.


Drill-down: POST /query/drilldown

Pentru card-urile care anunță supports_drilldown: true, acest endpoint intră într-o ierarhie — de exemplu, de la un curs în jos, până la un capitol anume. Trimite un obiect path care descrie unde să coboare.

POST /wp-json/enlivy-kit/v1/user-report-card/query/drilldown
Content-Type: application/json

{
  "user_id": 42,
  "card_type": "course_progress",
  "config": { "course_id": 5 },
  "path": { "chapter_id": 12 }
}

user_id și card_type sunt obligatorii; un card_type necunoscut returnează 400. Rezultatul este returnat sub cheia data.


View-uri salvate

Un view este o combinație salvată de card-uri, un filtru de useri și o ordonare, identificată printr-un view_id precum view_9f1c…. View-urile îți permit să configurezi un raport o singură dată în admin și apoi să îl citești după ID. Listarea și citirea view-urilor necesită doar capability-ul view; crearea, actualizarea sau ștergerea lor necesită capability-ul management.

GET /wp-json/enlivy-kit/v1/user-report-card/views
{
  "data": [
    {
      "id": "view_9f1c8a2e",
      "name": "Cohort A — Onboarding",
      "cards_count": 3,
      "created_at": "2026-07-01 09:14:00",
      "updated_at": "2026-07-05 11:02:00",
      "created_by": 1
    }
  ]
}

Ia definiția completă a unui singur view (card-uri, filtru, ordonare) cu GET /views/{view_id}, apoi trimite acel view_id direct în /query/grid sau /query/detail.


Export în masă: GET /views/{view_id}/export

Endpoint-ul de export aplatizează un view salvat în headers și rows — ideal pentru a împinge datele într-un spreadsheet, un CSV sau un data warehouse. Este paginat cu cursor, după user ID, pentru export-uri stabile: chiar dacă activitatea se schimbă în timpul export-ului, nu primești rânduri duplicate sau omise.

GET /wp-json/enlivy-kit/v1/user-report-card/views/view_9f1c8a2e/export?per_page=200
{
  "data": {
    "view_name": "Cohort A — Onboarding",
    "headers": ["User ID", "Email", "Display Name", "First Name", "Last Name", "Onboarding"],
    "rows": [
      [42, "[email protected]", "Jane Doe", "Jane", "Doe", "80%"]
    ],
    "pagination": { "total": 137, "per_page": 200, "has_more": true, "last_user_id": 42 }
  }
}
  • per_page este limitat între 50 și 500 (implicit 200).
  • headers este returnat doar pe prima pagină (când after_user_id lipsește).
  • Ca să iei pagina următoare, trimite last_user_id din response-ul anterior ca after_user_id: ?per_page=200&after_user_id=42. Continuă cât timp has_more este true.

Paginare, pe scurt

Atât grid-ul, cât și export-ul folosesc paginare cu cursor, nu numere de pagină: citește cursor-ul (cursor pentru grid, last_user_id pentru export) din fiecare response și trimite-l înapoi la request-ul următor, până când has_more este false. Cursorii sunt stabili la scrieri concurente, ceea ce paginarea cu offset nu este.


Erori

Erorile urmează forma standard WordPress REST: un obiect JSON cu code, message și un data.status care corespunde status-ului HTTP.

Status Code Când
400 no_cards Un query a fost trimis fără card-uri (și fără un view care să le furnizeze).
400 missing_user_id /query/detail sau /query/drilldown apelat fără user_id.
400 missing_card_type / invalid_card_type Drilldown apelat fără card_type, sau cu unul care nu este înregistrat.
401 / 403 rest_forbidden Credențiale lipsă/invalide, sau userul nu are capability-ul necesar.
404 view_not_found view_id-ul referit nu există.
{
  "code": "no_cards",
  "message": "At least one card is required",
  "data": { "status": 400 }
}

Exemplu complet (end-to-end)

Un flux minim de citire: descoperă card-urile, apoi trage prima pagină a unui grid pentru toți userii.

# 1. Ce card-uri există?
curl -s https://yourdomain.com/wp-json/enlivy-kit/v1/user-report-card/config \
  -u "reporting_bot:abcd EFGH ijkl MNOP qrst UVWX"

# 2. Trage grid-ul pentru un card, toți userii, prima pagină
curl -s -X POST https://yourdomain.com/wp-json/enlivy-kit/v1/user-report-card/query/grid \
  -u "reporting_bot:abcd EFGH ijkl MNOP qrst UVWX" \
  -H "Content-Type: application/json" \
  -d '{
        "cards": [ { "type": "course_progress", "config": {} } ],
        "user_filter": { "groups": [] },
        "per_page": 50
      }'

De acolo, iterează pe cursor-ul returnat până când has_more este false, sau treci la un view salvat și endpoint-ul /export pentru extrageri mari, dintr-o singură dată.