Skip to content

Core · Service · Active

platform-connectors-service

Shared datasource boundary used by product backends and MCP tools to reach Salesforce through source-based, service-to-service connector contracts.

  • TypeScript
  • NestJS 11
  • PostgreSQL
  • jsforce
  • HTTP internal APIs

Spec sheet

Boundary

Core / Datasource

Runtime

NestJS 11 HTTP service

Default port

3200

Security

Internal-only surface guarded by x-platform-internal-token

Primary connector

Salesforce via jsforce

Persistence

PostgreSQL via CONNECTORS_DATABASE_URL

Responsibilities

  • Keep vendor SDK logic out of product BFFs.
  • Expose connector health, source configuration and capability discovery.
  • Support Salesforce object describe, structured query, raw read-only SOQL, SOSL and cursor pagination.
  • Expose single-record CRUD, batch record operations and controlled Apex REST invocation for product BFFs.

Interfaces and contract surface

  • GET /health
  • GET /internal/connectors/health
  • GET /internal/connectors/sources
  • POST /internal/connectors/sources/:sourceId/test
  • POST /internal/connectors/sources/:sourceId/configure
  • GET /internal/connectors/sources/:sourceId/status
  • GET /internal/connectors/sources/:sourceId/capabilities
  • GET /internal/connectors/sources/:sourceId/describe/objects
  • GET /internal/connectors/sources/:sourceId/describe/objects/:objectName
  • GET /internal/connectors/sources/:sourceId/describe/objects/:objectName/fields
  • POST /internal/connectors/sources/:sourceId/query/run
  • POST /internal/connectors/sources/:sourceId/query/page
  • POST /internal/connectors/sources/:sourceId/query/more
  • POST /internal/connectors/sources/:sourceId/query/raw
  • POST /internal/connectors/sources/:sourceId/query/raw/page
  • POST /internal/connectors/sources/:sourceId/query/raw/more
  • POST /internal/connectors/sources/:sourceId/query/sosl
  • POST /internal/connectors/sources/:sourceId/records/:objectName
  • POST /internal/connectors/sources/:sourceId/records/:objectName/create-many
  • POST /internal/connectors/sources/:sourceId/records/:objectName/update-many
  • POST /internal/connectors/sources/:sourceId/records/:objectName/upsert-many
  • POST /internal/connectors/sources/:sourceId/records/:objectName/delete-many
  • PUT /internal/connectors/sources/:sourceId/records/:objectName/:recordId
  • DELETE /internal/connectors/sources/:sourceId/records/:objectName/:recordId
  • POST /internal/connectors/sources/:sourceId/apex/invoke

Consumers

Dependencies and external touchpoints

Notes

  • Frontend applications must never call this service directly.
  • The current public proxy does not expose a dedicated host for this service in local stack.
  • The legacy /internal/connectors/salesforce/* controller has been removed; consumers should use source-based endpoints under /internal/connectors/sources/:sourceId.

Source references

  • platform-connectors-service/README.md
  • docs/core-services-integration.md
  • platform-connectors-service/package.json

Come integrarsi davvero

platform-connectors-service e un boundary esclusivamente service-to-service. I frontend non devono chiamarlo mai direttamente: un backend/BFF di prodotto traduce il contratto tecnico in un contratto pubblico piu adatto al dominio applicativo.

Variabili utili

bash
export CONNECTORS_URL=http://127.0.0.1:3200
export INTERNAL_TOKEN=platform-local-stack-internal-token
export SOURCE_ID=salesforce-default

salesforce-default e il sourceId locale standard usato dai BFF quando non devono gestire piu sorgenti.

1. Discovery rapida di sorgenti e stato

bash
curl -sS "$CONNECTORS_URL/internal/connectors/sources" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"
bash
curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/status" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"

2. Test e configurazione di una sorgente Salesforce

Prima puoi validare le credenziali senza persisterle:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/test" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "providerKey": "salesforce",
    "config": {
      "mode": "username-password",
      "loginUrl": "https://login.salesforce.com",
      "username": "integration@example.com",
      "password": "super-secret-password",
      "securityToken": "security-token"
    }
  }'

Quando il probe e corretto, usa lo stesso payload su /configure per salvarlo:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/configure" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "providerKey": "salesforce",
    "config": {
      "mode": "username-password",
      "loginUrl": "https://login.salesforce.com",
      "username": "integration@example.com",
      "password": "super-secret-password",
      "securityToken": "security-token"
    }
  }'

3. Describe e query strutturata

Campi di un oggetto:

bash
curl -sS \
  "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/describe/objects/Contact/fields" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"

Per i BFF che hanno gia template SOQL/SOSL propri, il contratto source-based espone anche:

  • POST /internal/connectors/sources/$SOURCE_ID/query/raw
  • POST /internal/connectors/sources/$SOURCE_ID/query/raw/page
  • POST /internal/connectors/sources/$SOURCE_ID/query/raw/more
  • POST /internal/connectors/sources/$SOURCE_ID/query/sosl

Query strutturata paginata:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/page" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "pageSize": 100,
    "query": {
      "kind": "structured",
      "query": {
        "objectName": "Contact",
        "select": ["Id", "Name", "Email"],
        "sort": [
          {
            "field": "Name",
            "direction": "ASC"
          }
        ],
        "limit": 100
      }
    }
  }'

Se la risposta contiene nextCursor, la pagina successiva si legge cosi:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/more" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "cursor": "<NEXT_CURSOR>",
    "pageSize": 100
  }'

4. Operazioni record e Apex controllato

Creazione singola:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "record": {
      "LastName": "Rossi",
      "Email": "mario.rossi@example.com"
    }
  }'

Operazioni batch:

  • POST /internal/connectors/sources/$SOURCE_ID/records/:objectName/create-many
  • POST /internal/connectors/sources/$SOURCE_ID/records/:objectName/update-many
  • POST /internal/connectors/sources/$SOURCE_ID/records/:objectName/upsert-many
  • POST /internal/connectors/sources/$SOURCE_ID/records/:objectName/delete-many

Apex REST controllato:

bash
curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/apex/invoke" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "method": "POST",
    "path": "/services/apexrest/example",
    "payload": {"id":"001xx000003DGbYAAW"}
  }'

La vecchia surface /internal/connectors/salesforce/* e stata rimossa: i consumer devono passare dagli endpoint source-based.

Un consumer reale lato backend e sfdc-external/backend/src/platform/platform-clients.ts.

Endpoint reference

Header comuni per tutte le route interne:

  • x-platform-internal-token: $INTERNAL_TOKEN
  • Content-Type: application/json sulle POST e PUT

GET /health aggrega state store, registry provider e sole sorgenti configurate. Se non esiste nessuna sorgente configurata risponde degraded; una sorgente placeholder non configurata, per esempio salesforce-default, non degrada un runtime che usa un'altra sorgente configurata.

Discovery e configurazione

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
GET /healthVerifica liveness del servizio e delle sorgenti configurate.Nessun body.curl -sS "$CONNECTORS_URL/health"{"status":"ok","checks":{"sources":{"status":"up","configuredSources":1}}}degraded se nessuna sorgente e configurata o solo alcune sorgenti configurate non sono disponibili; down per state store, registry o tutte le sorgenti configurate non disponibili. Consumer: status dashboard.
GET /internal/connectors/healthHealth interna dei provider datasource.Header interno.curl -sS "$CONNECTORS_URL/internal/connectors/health" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"status":"ok","sources":[{"sourceId":"salesforce-default","configured":true}]}401/403 token. Consumer: operations e MCP.
GET /internal/connectors/sourcesLista sorgenti note e capability.Header interno.curl -sS "$CONNECTORS_URL/internal/connectors/sources" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"sourceId":"salesforce-default","providerKey":"salesforce","configured":true,"capabilities":{"data":true,"describe":true,"paging":true,"aggregates":true,"rawQuery":true,"sosl":true,"bulkMutation":true,"apexRest":true}}]}401/403 token. Consumer: BFF, MCP e operations.
POST /internal/connectors/sources/:sourceId/testProva una configurazione provider senza salvarla.providerKey?, config.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/test" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"providerKey":"salesforce","config":{"mode":"username-password","loginUrl":"https://login.salesforce.com","username":"integration@example.com","password":"<PASSWORD>","securityToken":"<TOKEN>"}}'{"sourceId":"salesforce-default","providerKey":"salesforce","success":true,"capabilities":{"data":true,"describe":true,"paging":true,"aggregates":true,"rawQuery":true}}400 config non valida, 502 login provider fallito. Consumer: operations.
POST /internal/connectors/sources/:sourceId/configureSalva una configurazione provider dopo probe riuscito.providerKey?, config.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/configure" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"providerKey":"salesforce","config":{"mode":"username-password","loginUrl":"https://login.salesforce.com","username":"integration@example.com","password":"<PASSWORD>","securityToken":"<TOKEN>"}}'{"sourceId":"salesforce-default","providerKey":"salesforce","success":true,"capabilities":{"data":true,"describe":true,"paging":true}}400 config non valida, 502 provider, 5xx state store. Consumer: operations.
GET /internal/connectors/sources/:sourceId/statusStato persistito di una sorgente.Path sourceId.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/status" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"sourceId":"salesforce-default","providerKey":"salesforce","configured":true,"lastConfiguredAt":"2026-05-03T10:00:00.000Z","lastSuccessAt":"2026-05-03T10:10:00.000Z"}401/403 token. Consumer: BFF e operations.
GET /internal/connectors/sources/:sourceId/capabilitiesCapability provider per una sorgente.Path sourceId.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/capabilities" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"sourceId":"salesforce-default","providerKey":"salesforce","capabilities":{"data":true,"describe":true,"paging":true,"aggregates":true,"rawQuery":true,"sosl":true,"bulkMutation":true,"apexRest":true}}404 provider non supportato. Consumer: MCP e BFF.

Describe e query

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
GET /internal/connectors/sources/:sourceId/describe/objectsLista oggetti disponibili.Path sourceId.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/describe/objects" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"name":"Contact","label":"Contact","custom":false,"searchable":true}]}409 sorgente non configurata, 502 provider. Consumer: BFF metadata e MCP.
GET /internal/connectors/sources/:sourceId/describe/objects/:objectNameDettaglio oggetto.Path objectName.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/describe/objects/Contact" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"name":"Contact","label":"Contact","custom":false,"searchable":true}400 nome oggetto non valido, 404 non trovato. Consumer: BFF metadata.
GET /internal/connectors/sources/:sourceId/describe/objects/:objectName/fieldsCampi oggetto, inclusi filtri e picklist.Path objectName.curl -sS "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/describe/objects/Contact/fields" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"name":"Email","label":"Email","type":"email","required":false,"filterable":true}]}400/404/502. Consumer: UI builder e query builder BFF.
POST /internal/connectors/sources/:sourceId/query/runEsegue query strutturata o aggregata senza cursor paging.query o piano al top-level.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/run" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"query":{"kind":"structured","query":{"objectName":"Contact","select":["Id","Name"],"limit":10}}}'{"done":true,"totalSize":1,"records":[{"Id":"003xx000004TmiYAAS","Name":"Mario Rossi"}],"renderedQueryText":"SELECT Id, Name FROM Contact LIMIT 10"}400 piano non valido, 409 non configurata, 502 provider. Consumer: BFF.
POST /internal/connectors/sources/:sourceId/query/pageEsegue query strutturata/aggregata e restituisce una pagina.query, pageSize?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/page" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"pageSize":100,"query":{"kind":"structured","query":{"objectName":"Contact","select":["Id","Name","Email"],"limit":100}}}'{"done":false,"totalSize":250,"records":[{"Id":"003xx000004TmiYAAS","Name":"Mario Rossi","Email":"mario@example.test"}],"nextCursor":"cursor_123"}400 piano o page size, 502 provider. Consumer: BFF liste.
POST /internal/connectors/sources/:sourceId/query/moreContinua una query paginata.cursor, pageSize?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/more" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"cursor":"cursor_123","pageSize":100}'{"done":true,"totalSize":250,"records":[]}400 cursor mancante/scaduto, 502 provider. Consumer: BFF liste.
POST /internal/connectors/sources/:sourceId/query/rawEsegue SOQL read-only con opzioni runtime.soql, options?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/raw" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"soql":"SELECT Id, Name FROM Contact LIMIT 10","options":{"maxFetch":10}}'{"done":true,"totalSize":1,"records":[{"Id":"003xx000004TmiYAAS","Name":"Mario Rossi"}]}400 SOQL mutante/non read-only, 502 provider. Consumer: BFF con template SOQL propri e MCP high-risk.
POST /internal/connectors/sources/:sourceId/query/raw/pageSOQL read-only paginato.soql, pageSize?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/raw/page" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"soql":"SELECT Id, Name FROM Contact","pageSize":50}'{"done":false,"totalSize":200,"records":[{"Id":"003xx000004TmiYAAS","Name":"Mario Rossi"}],"nextCursor":"cursor_456"}400 SOQL non valido, 502 provider. Consumer: BFF e MCP.
POST /internal/connectors/sources/:sourceId/query/raw/moreContinua SOQL raw paginato.cursor, pageSize?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/raw/more" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"cursor":"cursor_456","pageSize":50}'{"done":true,"totalSize":200,"records":[]}400 cursor mancante/scaduto. Consumer: BFF e MCP.
POST /internal/connectors/sources/:sourceId/query/soslEsegue SOSL Salesforce read-only.sosl.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/query/sosl" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"sosl":"FIND {mario} IN ALL FIELDS RETURNING Contact(Id, Name, Email LIMIT 10)"}'{"done":true,"totalSize":1,"records":[{"attributes":{"type":"Contact"},"Id":"003xx000004TmiYAAS","Name":"Mario Rossi"}]}400 SOSL non valido, 502 provider. Consumer: search BFF e MCP high-risk.

Record e Apex

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
POST /internal/connectors/sources/:sourceId/records/:objectNameCrea un record singolo.record o values.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"record":{"LastName":"Rossi","Email":"mario.rossi@example.test"}}'{"id":"003xx000004TmiYAAS","success":true,"errors":[]}400 valori non validi, 502 provider. Consumer: BFF prodotto.
POST /internal/connectors/sources/:sourceId/records/:objectName/create-manyCrea record in batch.records, options.allOrNone?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/create-many" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"records":[{"LastName":"Rossi"},{"LastName":"Bianchi"}],"options":{"allOrNone":false}}'{"results":[{"id":"003xx000004TmiYAAS","success":true,"errors":[]}]}400 array mancante, 502 provider. Consumer: import BFF.
POST /internal/connectors/sources/:sourceId/records/:objectName/update-manyAggiorna record in batch.records con Id, options?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/update-many" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"records":[{"Id":"003xx000004TmiYAAS","Email":"new@example.test"}]}'{"results":[{"id":"003xx000004TmiYAAS","success":true,"errors":[]}]}400 Id mancante, 502 provider. Consumer: sync BFF.
POST /internal/connectors/sources/:sourceId/records/:objectName/upsert-manyUpsert batch su external id.records, externalIdField, options?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/upsert-many" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"externalIdField":"External_Id__c","records":[{"External_Id__c":"ext-1","LastName":"Rossi"}]}'{"results":[{"id":"003xx000004TmiYAAS","success":true,"created":false,"errors":[]}]}400 external id mancante. Consumer: sync/import BFF.
POST /internal/connectors/sources/:sourceId/records/:objectName/delete-manyCancella record in batch.recordIds, options?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/delete-many" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"recordIds":["003xx000004TmiYAAS"],"options":{"allOrNone":false}}'{"results":[{"id":"003xx000004TmiYAAS","success":true,"errors":[]}]}400 recordIds vuoto, 502 provider. Consumer: admin BFF controllati.
PUT /internal/connectors/sources/:sourceId/records/:objectName/:recordIdAggiorna un record singolo.Path recordId, body record o values.curl -sS -X PUT "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/003xx000004TmiYAAS" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"record":{"Email":"updated@example.test"}}'{"id":"003xx000004TmiYAAS","success":true,"errors":[]}400 recordId/values non validi, 404/502 provider. Consumer: BFF prodotto.
DELETE /internal/connectors/sources/:sourceId/records/:objectName/:recordIdCancella un record singolo.Path recordId.curl -sS -X DELETE "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/records/Contact/003xx000004TmiYAAS" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"deleted":true}400 recordId mancante, 404/502 provider. Consumer: BFF admin controllati.
POST /internal/connectors/sources/:sourceId/apex/invokeInvoca Apex REST controllato.method POST o PATCH, path, payload?.curl -sS -X POST "$CONNECTORS_URL/internal/connectors/sources/$SOURCE_ID/apex/invoke" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"method":"POST","path":"/services/apexrest/example","payload":{"id":"001xx000003DGbYAAW"}}'{"status":200,"payload":{"ok":true}}400 metodo/path non ammesso, 502 provider. Consumer: BFF con integrazioni Apex approvate.

I consumer devono preferire query/page e query/more quando possono esprimere il filtro con il contratto strutturato; le query raw e SOSL restano superfici ad alto rischio e vanno esposte dai BFF solo dietro policy applicative esplicite.

Workspace reference: /Users/jeanpaul/projects/cs-repository