Resum
- URL base:
https://change-mapping-engine.lovable.app - Endpoint:
POST /api/v1/theory-of-change - Format: JSON. Idioma de la resposta: català.
- Autenticació: Bearer token.
- Origen: només dominis autoritzats a la llista del servidor.
Autenticació i CORS
Totes les peticions han d'incloure aquestes capçaleres:
Authorization: Bearer <THEORY_API_TOKEN>
Content-Type: application/jsonSi la crida es fa des del navegador, el domini exacte (esquema, domini i port) ha d'estar donat d'alta a la llista d'orígens autoritzats, sense barra final. Per a producció es recomana cridar des del backend i no exposar el token al navegador.
Petició
POST /api/v1/theory-of-change
Content-Type: application/json
Authorization: Bearer <THEORY_API_TOKEN>
{
"text": "Descripció llarga del projecte...",
"topOutputs": 3,
"topOutcomes": 3
}| Camp | Tipus | Obligatori | Descripció |
|---|---|---|---|
| text | string | Sí | Descripció del projecte. Mínim 20 caràcters. |
| provider | string | No | Proveïdor del model de llenguatge. Si no s'envia, s'usa el del servidor. |
| model | string | No | Model de llenguatge. Si no s'envia, s'usa el del servidor. |
| embeddingProvider | string | No | Proveïdor d'embeddings. |
| embeddingModel | string | No | Model d'embeddings. |
| topOutputs | number | No | Nombre d'outputs a retornar. Entre 1 i 10. Per defecte 3. |
| topOutcomes | number | No | Nombre d'outcomes a retornar. Entre 1 i 10. Per defecte 3. |
Resposta 200
{
"problema": "Definició del problema que aborda el projecte...",
"activitats": [
{ "id": "act_001", "nom": "Tallers setmanals", "justificacio": "..." }
],
"outputs": [
{
"id": "out_001",
"nom": "Participants que completen activitats formatives",
"justificacio": "...",
"similitud": 0.82,
"indicadors": [
{
"id": "ind_001",
"nom": "Nombre de participants que finalitzen la formació",
"preguntes": [
{
"id": "pre_001",
"tipus_pregunta": "quantitativa",
"font_informacio": "registre del projecte",
"pregunta_recomanada": "Quantes persones han completat la formació?",
"opcions_resposta": null,
"observacions": null
}
]
}
]
}
],
"outcomes": [
{
"id": "outcome_001",
"nom": "Millora de l'autonomia de les persones participants",
"justificacio": "...",
"similitud": 0.79,
"indicadors": []
}
],
"impacte": "Impacte esperat a llarg termini...",
"meta": {
"provider": "mistral",
"model": "mistral-small-latest",
"embeddingProvider": "mistral",
"embeddingModel": "mistral-embed"
}
}| Camp | Descripció |
|---|---|
| problema | Definició del problema que aborda el projecte. |
| activitats | Activitats detectades o vinculades al catàleg. |
| outputs | Outputs seleccionats per similitud semàntica amb el catàleg. |
| outcomes | Outcomes seleccionats per similitud semàntica amb el catàleg. |
| outputs[].similitud | Puntuació de similitud (0–1). Com més alta, més afinitat. |
| indicadors | Indicadors associats a cada output o outcome. |
| preguntes | Preguntes recomanades per a cada indicador. |
| impacte | Impacte esperat a llarg termini. |
| meta | Proveïdor i models utilitzats per generar la resposta. |
Els outputs i outcomes es trien automàticament per similitud semàntica amb el catàleg d'indicadors; el model de llenguatge només redacta el problema, les activitats, l'impacte i les justificacions.
Errors
{
"error": {
"code": "invalid_request",
"message": "Petició invàlida.",
"request_id": "0f4c0b5b-4f9a-4f9e-8df7-8bb806afc8e7",
"details": [
{ "field": "text", "message": "El text del projecte és massa curt (mínim 20 caràcters)." }
]
}
}| HTTP | Codi | Causa habitual |
|---|---|---|
| 400 | invalid_request | JSON invàlid, text massa curt o paràmetres fora de rang. |
| 401 | unauthorized | Falta el Bearer token o no coincideix. |
| 403 | forbidden_origin | El domini d'origen no està a la llista autoritzada. |
| 413 | invalid_request | El text supera el màxim de caràcters permès. |
| 415 | invalid_request | Falta la capçalera Content-Type: application/json. |
| 429 | rate_limited | Massa peticions dins la finestra configurada. |
| 500 | internal_error | Error intern. Fes servir el request_id per buscar els logs. |
| 502 | ai_gateway_error | El proveïdor d'IA no ha retornat una resposta vàlida. |
| 503 | internal_error | Endpoint no configurat al servidor. |
Integració amb Laravel
Recomanació per a l'equip de desenvolupament: cridar l'API sempre des del backend de Laravel, mai des del navegador de l'usuari final. El token només ha de viure al servidor.
1. Configuració (.env)
THEORY_API_BASE_URL=https://change-mapping-engine.lovable.app
THEORY_API_TOKEN=token-llarg-secretA config/services.php:
'theory_of_change' => [
'base_url' => env('THEORY_API_BASE_URL', 'https://change-mapping-engine.lovable.app'),
'token' => env('THEORY_API_TOKEN'),
'timeout' => env('THEORY_API_TIMEOUT', 90), // la generació pot trigar desenes de segons
],2. Client amb el HTTP client de Laravel
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\RequestException;
function generateTheoryOfChange(string $text): array
{
$response = Http::withToken(config('services.theory_of_change.token'))
->baseUrl(config('services.theory_of_change.base_url'))
->acceptJson()
->timeout(config('services.theory_of_change.timeout'))
->retry(2, 1000, fn ($exception) => $exception instanceof \Illuminate\Http\Client\ConnectionException)
->post('/api/v1/theory-of-change', [
'text' => $text,
// topOutputs / topOutcomes opcionals (1-10, per defecte 3)
]);
if ($response->failed()) {
$error = $response->json('error') ?? [];
throw new \RuntimeException(
sprintf(
'Theory API %s: %s (request_id: %s)',
$error['code'] ?? $response->status(),
$error['message'] ?? 'Error desconegut',
$error['request_id'] ?? '-',
),
$response->status(),
);
}
return $response->json();
}3. Servei dedicat (alternativa neta)
namespace App\Services;
use Illuminate\Support\Facades\Http;
class TheoryOfChangeService
{
public function generate(string $text): array
{
return Http::withToken(config('services.theory_of_change.token'))
->baseUrl(config('services.theory_of_change.base_url'))
->acceptJson()
->timeout(90)
->throw()
->post('/api/v1/theory-of-change', ['text' => $text])
->json();
}
}4. Notes per a producció
- Posa la crida dins d'un Job en cua (Laravel Queues): la generació pot trigar desenes de segons i no ha de bloquejar la petició web.
- Augmenta el
timeout(60–120 s) i gestiona els codis 429 (rate limit) amb reintents i espera exponencial. - Guarda el
request_iddels errors als logs: és la referència per depurar incidències. - El token va al fitxer
.envdel servidor; mai al codi ni al frontend. - No cal donar d'alta cap domini a CORS si la crida és servidor a servidor; la llista d'orígens només aplica a crides des del navegador.
Exemples
cURL
curl -X POST "https://change-mapping-engine.lovable.app/api/v1/theory-of-change" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $THEORY_API_TOKEN" \
-d '{"text":"Projecte d\'acompanyament a joves en risc d\'exclusió social amb tallers i orientació laboral."}'JavaScript (backend)
const response = await fetch("https://change-mapping-engine.lovable.app/api/v1/theory-of-change", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.THEORY_API_TOKEN}`,
},
body: JSON.stringify({ text: projectDescription }),
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.error?.code}: ${result.error?.message}`);
}La generació pot trigar desenes de segons segons el proveïdor d'IA i la mida del text. L'endpoint antic /api/public/theory-of-change queda limitat al mateix domini de l'app i no s'ha de fer servir per a integracions noves.