{"name":"upflow-backend","description":"Backend NestJS pour Upflow desktop. Il valide les tokens Clerk, gere l essai gratuit et la facturation Stripe, puis relaie les demandes de transcription audio vers Alibaba DashScope, xAI, Mistral ou OpenRouter, et de chat vers OpenRouter.","basePath":"/","authentication":{"provider":"Clerk for web login, Upflow desktop sessions for the macOS app","protectedHeader":"Authorization: Bearer <clerk_token_or_upflow_access_token>"},"routes":[{"method":"GET","path":"/health","auth":"public","description":"Verifie que le backend repond."},{"method":"GET","path":"/docs","auth":"public","description":"Retourne ce descriptif JSON des routes et du flux backend."},{"method":"GET","path":"/openapi.json","auth":"public","description":"Retourne la specification OpenAPI JSON du backend."},{"method":"GET","path":"/me","auth":"desktop ou clerk","description":"Retourne le profil minimal attendu par Upflow desktop: userId, email et sessionId."},{"method":"POST","path":"/auth/desktop/code","auth":"clerk","description":"Cree un code temporaire a usage unique pour connecter Upflow desktop."},{"method":"POST","path":"/auth/desktop/exchange","auth":"public code temporaire","description":"Echange un code desktop contre un access token court et un refresh token rotatif."},{"method":"POST","path":"/auth/desktop/refresh","auth":"refresh token desktop","description":"Renouvelle une session desktop et fait tourner le refresh token."},{"method":"POST","path":"/auth/desktop/logout","auth":"refresh token desktop","description":"Revoque la session desktop associee au refresh token."},{"method":"GET","path":"/auth/me","auth":"clerk","description":"Retourne le profil Clerk complet et les claims verifies."},{"method":"GET","path":"/billing/me","auth":"desktop ou clerk","description":"Retourne l etat billing courant sans demarrer l essai gratuit: statut d abonnement Stripe, dates d essai local et autorisation IA calculee."},{"method":"POST","path":"/billing/checkout","auth":"desktop ou clerk","contentType":"application/json","description":"Cree une session Stripe Checkout pour souscrire. Accepte successUrl et cancelUrl optionnels, valides par BILLING_ALLOWED_REDIRECT_URLS. Cet endpoint ne demarre pas l essai gratuit local."},{"method":"POST","path":"/billing/prices","auth":"desktop ou clerk","contentType":"application/json","description":"Liste les prix recurrents actifs du produit Stripe configure dans STRIPE_ALLOWED_PRODUCT_IDS et retourne une URL Checkout Stripe par prix. Accepte successUrl et cancelUrl optionnels."},{"method":"POST","path":"/billing/portal","auth":"desktop ou clerk","contentType":"application/json","description":"Cree une session Stripe Billing Portal pour gerer l abonnement. Accepte returnUrl optionnel, valide par BILLING_ALLOWED_REDIRECT_URLS. Cet endpoint ne demarre pas l essai gratuit local."},{"method":"POST","path":"/billing/webhook","auth":"public signature Stripe","description":"Synchronise les abonnements Stripe via webhook signe."},{"method":"POST","path":"/teams/bootstrap","auth":"desktop ou clerk","description":"Cree de facon idempotente une organisation personnelle Clerk si l utilisateur n appartient encore a aucune organisation."},{"method":"GET","path":"/teams/members","auth":"desktop ou clerk","description":"Liste les membres actifs de l organisation Clerk courante."},{"method":"GET","path":"/teams/invitations","auth":"clerk organisation admin","description":"Liste les invitations en attente de l organisation Clerk courante."},{"method":"POST","path":"/teams/invitations","auth":"clerk organisation admin","contentType":"application/json","description":"Invite un membre dans l organisation Clerk courante. Les invitations non acceptees ne sont pas facturees."},{"method":"DELETE","path":"/teams/invitations/{invitationId}","auth":"clerk organisation admin","description":"Revoque une invitation en attente de l organisation Clerk courante."},{"method":"POST","path":"/teams/webhook","auth":"public signature Clerk","description":"Traite user.created, organizationMembership.created et organizationMembership.deleted pour creer les organisations personnelles et synchroniser les seats Stripe."},{"method":"GET","path":"/transcribe/models","auth":"desktop ou clerk","description":"Liste les modeles de retranscription connus du backend a titre informatif. Le client ne choisit plus le modele; les champs model envoyes aux endpoints de transcription sont ignores."},{"method":"GET","path":"/transcribe/lexicon","auth":"desktop ou clerk","description":"Retourne le lexique de retranscription de l utilisateur courant. Les termes sont automatiquement transmis aux providers lors de POST /transcribe."},{"method":"PUT","path":"/transcribe/lexicon","auth":"desktop ou clerk","contentType":"application/json","description":"Remplace le lexique de retranscription de l utilisateur courant. Le body attendu est { \"terms\": [\"Upflow\", \"Ruben Djan\"] }."},{"method":"GET","path":"/transcribe/prompts","auth":"desktop ou clerk","description":"Liste les prompts de post-processing sauvegardes par l utilisateur courant."},{"method":"POST","path":"/transcribe/prompts","auth":"desktop ou clerk","contentType":"application/json","description":"Cree un prompt de post-processing sauvegarde. Le body attendu est { \"name\": \"Email pro\", \"prompt\": \"Utilise un ton professionnel.\" }."},{"method":"GET/PATCH/DELETE","path":"/transcribe/prompts/:promptId","auth":"desktop ou clerk","contentType":"application/json pour PATCH","description":"Retourne, met a jour ou supprime un prompt sauvegarde appartenant a l utilisateur courant."},{"method":"POST","path":"/transcribe/alibaba-token","auth":"desktop ou clerk + essai ou abonnement Stripe actif","contentType":"application/json","description":"Genere un API Key temporaire Alibaba Cloud DashScope pour Qwen-ASR Realtime. Le desktop utilise ensuite ce token ephemere pour ouvrir directement le WebSocket Alibaba."},{"method":"POST","path":"/transcribe","auth":"desktop ou clerk + essai ou abonnement Stripe actif","contentType":"multipart/form-data","description":"Transcrit un fichier audio ou une URL media avec le modele STT configure cote backend dans src/transcribe/transcribe.constants.ts (DEFAULT_TRANSCRIPTION_MODEL_ID). post_processing=true demande au backend de produire le texte final pret a coller avec DEFAULT_POST_PROCESSING_MODEL_ID, avec fallback silencieux vers la transcription brute en cas d echec LLM. post_processing_prompt_id utilise un prompt sauvegarde et active aussi le post-processing. post_processing_rephrase=true autorise une reformulation legere quand necessaire pour la clarte, sans changer le sens. translate_to active aussi implicitement le post-processing et demande de traduire le texte final dans la langue fournie. post_processing_context est optionnel et guide la mise en forme applicative. Une transcription reussie incremente le tracking mensuel utilisateur."},{"method":"POST","path":"/transcribe/post-process","auth":"desktop ou clerk + essai ou abonnement Stripe actif","contentType":"application/json","description":"Post-processe une retranscription deja produite, sans fichier audio. Utilise le meme post-processing de mise en forme que POST /transcribe, force post_processing=true, puis retourne le meme format TranscriptionResponse. Le modele est configure dans src/transcribe/transcribe.constants.ts (DEFAULT_POST_PROCESSING_MODEL_ID). post_processing_prompt_id utilise un prompt sauvegarde appartenant a l utilisateur courant. post_processing_rephrase=true autorise une reformulation legere dans ce meme appel si necessaire pour la clarte. translate_to demande de traduire le texte final dans la langue fournie. Les tokens LLM du post-processing sont journalises pour les metriques de cout, sans stocker de prix en base."},{"method":"POST","path":"/transcribe-and-rewrite","auth":"desktop ou clerk + essai ou abonnement Stripe actif","contentType":"multipart/form-data","description":"Transcrit un fichier audio, une URL media ou utilise transcription_text deja produit, puis applique optionnellement une reecriture de selection via OpenRouter selon mode. Les modeles STT et post-processing sont configures dans src/transcribe/transcribe.constants.ts; le chat et rewrite restent choisis cote backend. post_processing_prompt_id utilise un prompt sauvegarde et active le post-processing avant le mode choisi. mode vaut auto par defaut: rewrite de selected_text si fourni, sinon transcription post-processee. mode=rewrite exige selected_text. Le mode email est retire; la mise en forme applicative passe par post_processing_context. Si le traitement LLM echoue apres une transcription reussie, retourne 200 avec rewrite null, rewrite_error et pasted_text egal a la transcription. Les tokens LLM de la reecriture et du post-processing sont journalises pour les metriques de cout, sans stocker de prix en base."},{"method":"GET","path":"/usage/me?month=YYYY-MM","auth":"desktop ou clerk","description":"Retourne les statistiques mensuelles de transcription pour l utilisateur courant: nombre de transcriptions, mots retranscrits, secondes et heures audio transcrites. Le mois courant UTC est utilise par defaut."},{"method":"GET","path":"/metrics/transcriptions?limit=100&offset=0","auth":"desktop ou clerk + email autorise dans METRICS_ALLOWED_EMAILS","description":"Retourne la liste globale des demandes de transcription reussies: utilisateur, email, organisation, modele utilise, duree audio, duree de traitement serveur et date de creation. Reserve aux emails whitelistes."},{"method":"GET","path":"/metrics/transcription-responses?limit=100&offset=0&endpoint=auto&success=true","auth":"desktop ou clerk + email autorise dans METRICS_ALLOWED_EMAILS","description":"Retourne l historique pagine des reponses envoyees par POST /transcribe et POST /transcribe/auto, succes et erreurs inclus: statut HTTP, utilisateur, endpoint, modele, mode auto et payload JSON retourne. Reserve aux emails whitelistes."},{"method":"GET","path":"/metrics/usage?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD","auth":"desktop ou clerk + email autorise dans METRICS_ALLOWED_EMAILS","description":"Retourne l usage global agrege sur l ensemble des evenements de transcription: nombre de transcriptions, mots retranscrits, secondes et heures audio transcrites. Sans parametre, agrege tout l historique; avec startDate et/ou endDate, restreint la plage en UTC. startDate est inclusif, endDate est exclusif. Reserve aux emails whitelistes."},{"method":"GET","path":"/metrics/usage/active-users","auth":"desktop ou clerk + email autorise dans METRICS_ALLOWED_EMAILS","description":"Retourne le nombre d utilisateurs actifs: utilisateurs avec au moins un evenement d usage sur chacun des deux derniers jours ouvres UTC. Les samedis et dimanches sont ignores; le week-end, la metrique reste basee sur jeudi et vendredi."},{"method":"GET","path":"/metrics/usage/events?limit=100&offset=0","auth":"desktop ou clerk + email autorise dans METRICS_ALLOWED_EMAILS","description":"Retourne l historique pagine des evenements d usage (un par retranscription): utilisateur, email, organisation, mots retranscrits, secondes transcrites et date de creation. Tri par date decroissante. Reserve aux emails whitelistes."},{"method":"GET","path":"/metrics/llm/usage?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD","auth":"desktop ou clerk + email autorise dans METRICS_ALLOWED_EMAILS","description":"Retourne l usage LLM agrege: nombre de requetes, tokens prompt/completion/reasoning/cache, cout estime USD et breakdown par operation + modele. Les couts sont calcules a la lecture avec les prix courants OpenRouter (/api/v1/models); les prix et couts ne sont pas stockes en base."},{"method":"GET","path":"/metrics/llm/events?limit=100&offset=0&startDate=YYYY-MM-DD&endDate=YYYY-MM-DD","auth":"desktop ou clerk + email autorise dans METRICS_ALLOWED_EMAILS","description":"Retourne l historique pagine des requetes LLM journalisees: utilisateur, organisation, operation, modele, tokens et cout estime USD calcule a la lecture avec les prix courants OpenRouter. Reserve aux emails whitelistes."},{"method":"GET","path":"/metrics/users?month=YYYY-MM","auth":"desktop ou clerk + email autorise dans METRICS_ALLOWED_EMAILS","description":"Retourne les statistiques d inscriptions des utilisateurs (donnees Clerk): total des inscrits toute periode, total du mois demande et repartition par jour. Sans parametre, utilise le mois courant (UTC). Reserve aux emails whitelistes."},{"method":"POST","path":"/chat","auth":"desktop ou clerk + essai ou abonnement Stripe actif","contentType":"application/json","description":"Cree une reponse de chat via OpenRouter Chat Completions avec le modele choisi cote backend. Le champ model est accepte pour compatibilite mais ignore. Les tokens LLM sont journalises pour les metriques de cout, sans stocker de prix en base."}],"requiredEnvironment":["CLERK_SECRET_KEY","OPENROUTER_API_KEY","DATABASE_URL","UPFLOW_DESKTOP_JWT_SECRET","STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET","STRIPE_PRICE_ID","STRIPE_ALLOWED_PRODUCT_IDS"],"optionalEnvironment":["BILLING_ALLOWED_REDIRECT_URLS","BILLING_SUCCESS_URL","BILLING_CANCEL_URL","BILLING_PORTAL_RETURN_URL","BILLING_TRIAL_DAYS","CLERK_JWT_AUDIENCE","CLERK_AUTHORIZED_PARTIES","DATABASE_SSL_CA","DATABASE_SSL","DASHSCOPE_API_KEY","DASHSCOPE_COMPATIBLE_ENDPOINT","DASHSCOPE_REALTIME_API_KEY","DASHSCOPE_REALTIME_MODEL","DASHSCOPE_REALTIME_TOKEN_URL","DASHSCOPE_REALTIME_URL","DASHSCOPE_US_API_KEY","XAI_API_TOKEN","MISTRAL_API_KEY","OPENROUTER_HTTP_REFERER","OPENROUTER_APP_TITLE","OPENROUTER_APP_CATEGORIES","METRICS_ALLOWED_EMAILS","UPFLOW_DESKTOP_CODE_TTL_SECONDS","UPFLOW_DESKTOP_ACCESS_TOKEN_TTL_SECONDS","UPFLOW_DESKTOP_REFRESH_TOKEN_TTL_DAYS"]}