Documentació

API de teoria del canvi

Genera l'estructura de teoria del canvi d'un projecte en català a partir d'una descripció textual: problema, activitats, outputs, outcomes i impacte.

← Tornar al generador

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/json

Si 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
}
CampTipusObligatoriDescripció
textstringDescripció del projecte. Mínim 20 caràcters.
providerstringNoProveïdor del model de llenguatge. Si no s'envia, s'usa el del servidor.
modelstringNoModel de llenguatge. Si no s'envia, s'usa el del servidor.
embeddingProviderstringNoProveïdor d'embeddings.
embeddingModelstringNoModel d'embeddings.
topOutputsnumberNoNombre d'outputs a retornar. Entre 1 i 10. Per defecte 3.
topOutcomesnumberNoNombre 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"
  }
}
CampDescripció
problemaDefinició del problema que aborda el projecte.
activitatsActivitats detectades o vinculades al catàleg.
outputsOutputs seleccionats per similitud semàntica amb el catàleg.
outcomesOutcomes seleccionats per similitud semàntica amb el catàleg.
outputs[].similitudPuntuació de similitud (0–1). Com més alta, més afinitat.
indicadorsIndicadors associats a cada output o outcome.
preguntesPreguntes recomanades per a cada indicador.
impacteImpacte esperat a llarg termini.
metaProveï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)." }
    ]
  }
}
HTTPCodiCausa habitual
400invalid_requestJSON invàlid, text massa curt o paràmetres fora de rang.
401unauthorizedFalta el Bearer token o no coincideix.
403forbidden_originEl domini d'origen no està a la llista autoritzada.
413invalid_requestEl text supera el màxim de caràcters permès.
415invalid_requestFalta la capçalera Content-Type: application/json.
429rate_limitedMassa peticions dins la finestra configurada.
500internal_errorError intern. Fes servir el request_id per buscar els logs.
502ai_gateway_errorEl proveïdor d'IA no ha retornat una resposta vàlida.
503internal_errorEndpoint 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-secret

A 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_id dels errors als logs: és la referència per depurar incidències.
  • El token va al fitxer .env del 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.