Indice
1. API Piano 1b. Newsletter Piano ESP 1c. Feed newsletter (Quotidiano) 1d. Contenuti suggeriti (Cxense) 2. API OTP (interne) 3. User Collector 4. Mappa Frontend → Backend
1. API Piano
Integrazione Piano.io. Config in config/config.php → piano. Header Authorization = api_token (dove previsto). Sandbox: https://sandbox.piano.io.
| Chiave config | Uso |
|---|---|
aid | Application ID in query/body |
api_token | Token publisher |
base_url | Identity: /id/api/v1/publisher/identity |
user_update_url | /api/v3/publisher/user/update |
user_get_v3_url | /api/v3/publisher/user/get (profilo + custom fields) |
users_get_url | /id/api/v1/publisher/users/get (legacy identity) |
token_url | /id/api/v1/publisher/token |
user_search_url | /api/v3/publisher/user/search |
user_access_list_url | /api/v3/user/access/list (Bearer access token utente) |
identity_token_url | /id/api/v1/identity/token (OAuth refresh) |
1b. Newsletter — Piano ESP (iscrizione)
Iscrizione mailing list via Piano ESP.
Config: config.php → newsletter.
URL: GET https://api-esp.piano.io/tracker/securesub/email/{email}/ml/{mlids}?api_key=...
| Testata | Chiave config | api_key |
|---|---|---|
| Quotidiano Nazionale | qn | newsletter.api_keys.qn |
| il Resto del Carlino | rdc | newsletter.api_keys.rdc |
| Il Giorno | gio | newsletter.api_keys.gio |
| La Nazione | naz | newsletter.api_keys.naz |
Piano ESP GET /tracker/securesub/email/{email}/ml/{mlids}
Action FE: newsletter_subscribe. Preferito selections: [{feed, mlid}, …] da feed misti: il PHP raggruppa per testata e applica la api_key corretta. Retrocompat: testata + mlids.
GET .../email/user%40mail.it/ml/3832?api_key=*** GET .../email/user%40mail.it/ml/5906,3832?api_key=***
1c. Feed newsletter (Quotidiano — non Piano)
Wrapper verso i feed WordPress. Non passano da Piano.
Action FE: newsletter_feed. Con all: true (o senza feed) restituisce i 4 feed + items[] normalizzati.
| feed | Upstream |
|---|---|
qn | …/feed/qn |
ilresto | …/feed/ilresto |
ilgiorno | …/feed/ilgiorno |
lanazione | …/feed/lanazione |
1d. Contenuti suggeriti — Piano / Cxense widget
Wrapper verso POST https://api.cxense.com/public/widget/data.
Config: config.php → recommendations.
Action FE: recommendations.
| Chiave config | Uso |
|---|---|
recommendations.api_url | Endpoint Cxense widget data |
recommendations.widget_id_home | Senza URL articolo, toptrend=false |
recommendations.widget_id_article | Con URL articolo, toptrend=false |
recommendations.widget_id_home_toptrend | Senza URL, toptrend=true |
recommendations.widget_id_article_toptrend | Con URL, toptrend=true |
recommendations.widget_id | Alias legacy (fallback se widget_id_article vuoto) |
recommendations.user_id_prefix | Chiave in user.ids (default usi) |
recommendations.timeout | Timeout cURL (default 10s) |
recommendations.debug | Se false: body = solo { "response": { "items": […] } } (campi Cxense originali). Se true: wrapper API + articles mappati + recommendations_debug |
Cxense POST /public/widget/data
{
"widgetId": "<WIDGET_ID selezionato>",
"context": { "url": "https://www.example.com/articolo" }, // omesso se home
"user": { "ids": { "usi": "<UDI>" } } // opzionale
}
FE recommendations: url opzionale, toptrend opzionale (default false). Risposta normalizzata: articles[] con title (da og-title), url, thumbnail, click_url, description (troncata, da og-description/description). Opzionali (solo se presenti): author, publishtime, categoria, tassonomia, keyword, kw-classification, kw-taxonomy, kw-category, kw-sentiment, category0, category1, createtime.
Piano POST /id/api/v1/publisher/identity/login
Query: aid, email, password=%22%22 (passwordless) oppure password reale. Body JSON: aid, email, stay_logged_in ("true"/"false"), eventuale password.
POST .../identity/login?aid=...&email=user%40mail.it&password=%22%22
{
"aid": "...",
"email": "user@mail.it",
"stay_logged_in": "false"
}
Piano POST /id/api/v1/publisher/identity/register
Query include il parametro sandbox pasword=null, is_passwordless=true, consents, is_magic_link_sent, email, aid.
{
"password": "",
"is_passwordless": true,
"is_magic_link_sent": true,
"email": "user@mail.it",
"aid": "..."
}
Piano POST /id/api/v1/publisher/identity/doi/status
Query: aid, uid, email_confirmation_state=CONFIRMED. Body: email_confirmation_state: "confirmed".
{
"aid": "...",
"uid": "PNIxxxx",
"email_confirmation_state": "confirmed"
}
Piano POST /id/api/v1/publisher/users/get
Usato da get_user_email: lookup per email in query (aid, api_token, email). Poi arricchimento con v3 user/get per custom fields.
Piano POST /api/v3/publisher/user/get
Form-urlencoded. Usato da get_user (uid) per leggere profilo e custom fields (otp_phone, city, birth_date). Query/body: aid, api_token, uid.
Piano POST /api/v3/publisher/user/search
Form-urlencoded. Cerca per custom field otp_phone.
api_token=...
aid=...
source=CF
offset=0
limit=100
custom_fields=[{"field_name":"otp_phone","data_type":"NUMBER","condition":{"type":"EQUAL","equal":"3331234567"}}]
Piano POST /api/v3/publisher/user/update — identity
Se il frontend passa first_name / last_name / email. Form-urlencoded + query aid, api_token, uid.
Piano POST /api/v3/publisher/user/update — custom fields
Se il frontend passa otp_phone / city / birth_date. Query: aid, api_token, uid. Body JSON:
{
"city": "Roma",
"birth_date": "1973-07-21",
"otp_phone": 331222222
}
Content-Type: application/json. Le due update partono dalla stessa action frontend update_profile.
Piano POST /id/api/v1/publisher/token
Query: api_token, aid, uid. Body JSON opzionale. Restituisce access_token salvato in cookie dal backend.
Piano GET /api/v3/user/access/list
Elenco delle risorse a cui l’utente ha accesso. Auth con Bearer access token (non api_token publisher). Config: piano.user_access_list_url. Action FE: user_access_list.
GET https://sandbox.piano.io/api/v3/user/access/list?aid=<AID> Authorization: Bearer <ACCESS_TOKEN>
Piano POST /id/api/v1/identity/token (refresh)
Rinnova un access token scaduto con il refresh_token OAuth. Form-urlencoded. client_id = AID. Per il refresh non servono client_secret né redirect_uri. Config: piano.identity_token_url. Action FE: refresh_token.
POST https://sandbox.piano.io/id/api/v1/identity/token Content-Type: application/x-www-form-urlencoded client_id=<AID> grant_type=refresh_token refresh_token=<REFRESH_TOKEN>
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"refresh_token": "RFbqr...",
"expires_in": 3600,
"email_confirmation_required": false
}
Nota: il flusso attuale publisher/token (access_token / QN*) tipicamente non restituisce un refresh_token; questo endpoint serve quando il token arriva dal flusso OAuth identity.
2. API OTP (interne)
Non sono endpoint Piano: gestione OTP locale in lib/otp.php + invio in lib/notify.php e lib/sms.php. Config in otp, mail, sms.
| Chiave config | Default / uso |
|---|---|
otp.ttl_seconds | 300 — durata validità codice |
otp.length | 6 cifre |
otp.resend_cooldown_seconds | 60 — non rinviare se già inviato da meno di N secondi |
otp.debug | only debug — se true, le risposte API includono il codice OTP in chiaro (data.otp); in produzione lasciare false |
mail.* | SMTP (disabilitato se from_address vuoto) |
sms.* | Gateway QNMedia lib/sms.php → POST awssns_sms |
OTP Generate
Tabella otp_codes: uid, otp, scadenza, created_at.
- Se esiste OTP valido inviato da meno di
resend_cooldown_seconds→ non rinvia (already_sent+retry_after) - Altrimenti genera nuovo codice, salva in DB e invia notifiche se configurati
- Con
notify_email+phone: SMS e email (utente esistente conotp_phone) - Solo
phone/uidtelefono: invio solo SMS (nuovo numero) - Template:
template/otp_email.html,template/otp_sms.txt - Log debug:
logs/api.log(prefissoOTPLOGIN -, request/response API + SMTP/SMS) - Placeholder:
{{OTP_CODE}},{{OTP_EXPIRY_MINUTES}},{{APP_NAME}},{{YEAR}}
OTP Verify
Esiti: ok / invalid / expired. In caso di successo il record viene cancellato (one-shot).
OTP SMS Gateway (lib/sms.php)
POST verso sms.api_url con Bearer sms.api_key.
POST https://api.staging.quotidiano.net/sms-gateway/awssns_sms
Authorization: Bearer {api_key}
Content-Type: application/json
{
"receiver": "+393331234567",
"body": "QNMedia Access: il tuo codice OTP è 1234. Scade tra 5 minuti.",
"sender": "QNMedia"
}
receiver e body sono dinamici (telefono normalizzato E.164 + template otp_sms.txt).
OTP Notifiche
- Email: SMTP se
mail.from_addressvalorizzato - SMS:
SmsGatewaysesms.api_urlesms.api_keyvalorizzati - Se i parametri sono vuoti, il canale viene saltato
3. User Collector
Ingest login dopo emissione token verso Quotidiano staging (uid + accessToken).
Lib: lib/user_collector.php. Config: config.php → user_collector.
Integrato nelle API QN* all’emissione del token. Action debug: login-ingestion-user-collector.
| Chiave config | Uso |
|---|---|
base_url | https://api.staging.quotidiano.net/user-collector |
ingest_login_path | /ingest/login |
internal_token | Header x-internal-token |
timeout | Timeout cURL (default 20s) |
User Collector POST /ingest/login
Payload minimo: solo uid e accessToken (niente Piano access/list).
POST https://api.staging.quotidiano.net/user-collector/ingest/login
accept: */*
x-internal-token: {internal_token}
Content-Type: application/json
{
"uid": "google-oauth2|104497752255509497539",
"accessToken": "sddadsa"
}
Action debug login-ingestion-user-collector: richiede uid + accessToken (oppure access_token / cookie __utp / Bearer) e inoltra lo stesso body al Collector.
4. Mappa Frontend → Backend
| Action FE | Tipo | Servizi chiamati dal PHP |
|---|---|---|
otp_generate | OTP | DB OTP + email/SMS |
verify_otp | OTP (+ Piano) | DB OTP; se registrazione → Piano DOI |
otp_verify_local | OTP | Solo DB OTP (test) |
login | Piano | Piano login; se passwordless anche OTP |
register | Piano | Piano register + OTP |
get_user | Piano | Piano user/get v3 per uid → profilo ridotto |
get_user_email | Piano | identity users/get by email + user/get v3 → profilo ridotto |
search_user | Piano | Piano user/search |
update_profile | Piano | Piano user/update (1 o 2 volte) |
doi_confirm | Piano | Piano doi/status |
access_token | Piano | Piano publisher/token + cookie |
refresh_token | Piano | POST identity/token (grant_type=refresh_token) + cookie opzionale |
user_access_list | Piano | GET Piano user/access/list con Bearer access token |
QNVerifyOtp / QNRegister / QNUpdateProfile | QN* + User Collector | dopo access_token → ingest/login {uid, accessToken} |
login-ingestion-user-collector | User Collector (debug) | stesso ingest {uid, accessToken}, solo per test |
newsletter_subscribe | Piano ESP | GET api-esp.piano.io/tracker/securesub/… |
newsletter_feed | Feed QN | Wrapper newsletters.quotidiano.net/wp-json/feed/… |
recommendations | Cxense | POST api.cxense.com/public/widget/data |
test_mail | Debug | SMTP test con steps diagnostici (vedi pagina Test) |