Skoči na glavni sadržaj

Scoutello API

The Scoutello API is a server-to-server API for synchronizing an organization's contacts, tasks, protocols, opportunities, projects, and events in both directions. It also exposes read-only outbound mail history and entity-aware tag discovery. Incremental GET/list routes and idempotent batch upserts are the synchronization primitives in this version. It does not expose delete operations or webhooks.

The API base URL is:

https://admin.scoutello.com/api/v1

It is also shown in Organization settings → Developer/API. The OpenAPI document is available at:

https://admin.scoutello.com/api/v1/openapi.json

Create and protect a key

An organization administrator can create an integration and API key in Organization settings → Developer/API. The complete key is shown once. Copy it into a secret manager before closing the dialog; Scoutello cannot display the complete key again.

  • Use a separate integration and key for each external system and environment.
  • Grant only the scopes that system needs.
  • Never put a key in browser code, a URL, source control, logs, support messages, or client-side storage.
  • Rotate keys on a schedule. When an active integration is rotated, the replacement key works immediately and the previous key remains valid for no more than 15 minutes so the external system can switch without downtime. An existing earlier expiry still applies, so the previous key may stop working sooner.
  • If a key may have been exposed, pause the integration before rotating it. Pausing temporarily rejects all of the integration's keys. A rotation while paused has no overlap: the previous key stays disabled, and the replacement key remains disabled until the integration is resumed.
  • Permanently revoke the entire integration when disconnecting an external system. Revocation disables every one of its keys and cannot be undone.

Send the key on every authenticated request:

Authorization: Bearer YOUR_API_KEY

Bearer authentication is preferred. Clients that cannot set Authorization may use x-api-key instead. Do not send both headers; requests with different values in both headers are rejected.

The API is intended for backend clients and does not enable cross-origin browser access.

Use GET /whoami after configuring a key to verify the integration, organization, scopes, and key limits without retrieving user personal data.

Scopes

ScopeAllows
contacts.readList and retrieve contacts. Organization-account and access-bearing member contacts are excluded.
contacts.writeValidate and apply contact upserts.
tasks.readList and retrieve non-private tasks.
tasks.writeValidate and apply task upserts.
protocols.readList and retrieve non-private protocols.
protocols.writeValidate and apply protocol upserts.
opportunities.readList and retrieve opportunities.
opportunities.writeValidate and apply opportunity upserts.
projects.readList and retrieve projects.
projects.writeValidate and apply project upserts.
events.readList and retrieve events and core schedules.
events.writeValidate and apply event upserts.
mails.readRead sent and failed organization-owned outbound mail history.
tags.readDiscover selectable tags for supported entity types.

Private tasks and protocols are never returned by this API. A missing scope returns an authorization error; it is not treated as an empty result.

New keys use the sctl_api_ prefix and start with contacts.write only. Every other scope is an explicit opt-in that an organization administrator must grant when creating or rotating a key. Existing credentials keep their current scopes and remain valid.

MCP access

The same Scoutello API key and scopes can be used with the streamable HTTP MCP endpoint:

https://admin.scoutello.com/api/v1/mcp

Configure the MCP client to send Authorization: Bearer YOUR_API_KEY. The legacy /api/mcp URL remains a path compatibility alias, but new clients should use the versioned endpoint. Former sctl_mcp_ credentials are not accepted; replace them with a Scoutello API integration key.

MCP tools map directly to the REST contract and use the same runtime schemas, tenant checks, integration lifecycle, rate limit, and service methods. For example, contacts_list, contacts_get, contacts_get_by_external_id, and contacts_batch_upsert mirror the four contact routes. The same pattern applies to tasks, protocols, opportunities, projects, and events; mail exposes mails_list and mails_get, while tags expose tags_list.

The server only advertises tools allowed by the current key's scopes. A key with contacts.read can discover the contact read tools but not contacts_batch_upsert; a key with contacts.write gets the batch-upsert tool but not the read tools. whoami is always available. Write tools have the same immediate effects as their REST equivalents, so use mode: "validate" when a caller should inspect a batch without applying it.

MCP is also server-to-server and does not enable cross-origin browser access. Its JSON-RPC transport needs a dedicated URL, so it cannot share an individual REST resource URL even though authentication and data behavior are shared.

For complete client configuration, verification, tool mapping, and troubleshooting instructions, see the MCP integration guide.

Read records

Contacts, tasks, protocols, opportunities, projects, and events use the same read routes:

GET /contacts?limit=50&updatedSince=2026-09-01T00:00:00.000Z
GET /contacts/{scoutelloId}
GET /contacts/external/{externalId}

Replace contacts with tasks, protocols, opportunities, projects, or events for the other synchronization resources. externalId appears in a read DTO when the record has been linked by this integration.

A list response has this envelope:

{
"data": [],
"page": {
"nextCursor": null,
"hasMore": false,
"snapshotAt": "2026-09-01T12:00:00.000Z"
},
"requestId": "request-id"
}

limit must be from 1 to 100. Results are ordered by the effective time when their public API representation changed and then by Scoutello ID. This effective time can be later than the returned entity updatedAt when integration-link metadata changed. Cursors are opaque: store and send them unchanged, and do not derive meaning from their contents.

The first response fixes snapshotAt for that traversal. Follow nextCursor until hasMore is false before starting another incremental read. This prevents records changing during pagination from making the current traversal unstable.

For incremental synchronization, consume every cursor page and then persist that traversal's snapshotAt as the high-water timestamp. Pass it as the ISO 8601 updatedSince value on the next traversal. The lower bound is inclusive, so retrying a completed snapshot can return boundary records but cannot skip them.

A single-record response has this envelope:

{
"data": {},
"requestId": "request-id"
}

Single-contact responses also include a relations array. Each relation keeps its Scoutello relation ID, creation time, type, notes, and connectedFrom and connectedTo contact references. Contact references contain the Scoutello ID and this integration's external ID when one exists. Contacts excluded by the API's organization-access privacy boundary are not exposed through relations. The contact list response stays compact and does not include relations; use either single-contact route when relationship detail is needed.

Batch upsert records

Write up to 100 records with the resource's batch route:

POST /contacts/batch-upsert
Content-Type: application/json
{
"mode": "apply",
"records": [
{
"externalId": "record-123",
"externalUpdatedAt": "2026-09-01T11:45:00.000Z",
"type": "person",
"firstName": "Alex",
"lastName": "Example",
"email": "alex@example.com"
}
]
}

Use the OpenAPI document for each resource's fields and validation constraints. Every record must have a stable externalId between 1 and 200 characters. Treat it as an opaque technical identifier: do not use an email address, name, API secret, or other personal data. externalUpdatedAt is optional, but strongly recommended when the source system provides a reliable modification timestamp.

The request body is limited to 1 MiB. mode defaults to apply:

  • apply validates and writes accepted records.
  • validate performs the same validation and matching without writing, returning would_* outcomes.

For contact imports, matchByEmail can be set to true on the request. It defaults to false; external ID is the safer default identity boundary.

Omitted fields preserve the current value. An explicit null clears a nullable field, and a supplied empty array clears that relation. Contact, task, opportunity, and event tag writes use Scoutello tag IDs. Relationship external IDs are resolved only through links belonging to the current integration, while Scoutello user IDs used for ownership or responsibility must identify accepted members of the current organization.

Opportunity creates require an account or contact, and lost opportunities require a loss reason. Monetary amounts use integer cents. Project and event names and descriptions are read and written in the organization's configured language. Event writes support explicit dates and daily, weekly, or monthly recurrence with exclusions; published visibility tiers remain subject to Scoutello's legal-readiness gates. The API does not expose event ticketing, participants, media, templates, newsletters, collaborations, or other advanced event-management controls.

Discover tags

Use the tags endpoint before supplying tagIds:

GET /tags?entity=events&type=category

entity is required and accepts contacts, tasks, opportunities, or events. It maps to the corresponding stored tag relevancy. type is optional and accepts category or attribute. Selectable categories may be organization-owned or global; selectable attributes come from Scoutello's curated global attribute set. Results include scoutelloId, the name localized to the organization language, type, and relevancy. Cross-organization, wrong-relevancy, and non-selectable tag IDs reject the affected batch item.

Read outbound mail history

Mail history is read-only:

GET /mails?updatedSince=2026-09-01T00:00:00.000Z
GET /mails/{scoutelloId}

Only organization-owned mail in sent or failed status is returned. Responses contain common stored headers and body, BCC, status and timestamps, project/event/newsletter references, attachment IDs and types, and aggregate recipient delivery/open/click/bounce/complaint/rejection statistics. Drafts, queued or scheduled mail, per-recipient rows, rendered snapshots, raw provider errors, and attachment download URLs are never exposed. A later recipient delivery update advances the mail's effective synchronization timestamp so the mail appears in incremental reads again. There are no mail write routes.

An apply result can be created, updated, linked, unchanged, stale_ignored, or rejected. Validation uses would_create, would_update, would_link, would_leave_unchanged, would_ignore_stale, or rejected. A valid batch request returns HTTP 200 even when individual records are rejected, so always inspect every item and the response summary. Keep the response requestId when asking Scoutello support to investigate a failure.

Timestamps and conflicts

When externalUpdatedAt is present, Scoutello uses it to reject stale changes without failing the rest of the batch. A stale_ignored result is terminal for that older payload; fetch the current record before deciding whether to send a newer version.

Treat timestamps as UTC ISO 8601 values. Preserve their original precision and do not substitute the time when a sync job happened for the source record's modification time.

Idempotency

The idempotency boundary is the integration plus externalId. Scoutello compares a canonical payload hash and the optional externalUpdatedAt value, so retrying the same record does not create duplicates and can return unchanged.

With externalUpdatedAt, the same timestamp and payload is unchanged, an older timestamp is stale_ignored, and the same timestamp with a different payload is a version conflict. For a link that has never accepted a timestamp, the same timestamp-less payload remains unchanged while a changed payload is applied in arrival order. After a link has accepted a timestamp, a changed payload that omits externalUpdatedAt is rejected as a version conflict so unversioned data cannot overwrite versioned state. This is why source modification timestamps are strongly recommended.

Audit trail

Accepted API creates, updates, and link-only synchronization actions appear in Scoutello changelogs with Public API as the source and are tied to the responsible integration. Operational run summaries retain request IDs and aggregate result counts for troubleshooting; they do not retain record payloads or API secrets.

Errors and retries

Request-level errors use this envelope:

{
"error": {
"code": "error_code",
"message": "Safe error message",
"requestId": "request-id",
"details": {}
}
}

Retry HTTP 429 and transient 5xx responses with exponential backoff and jitter. Honor Retry-After when it is present. Network failures may have happened after Scoutello accepted a request; sending the same batch again is safe because each record is idempotent by external ID and canonical payload. Do not automatically retry authentication, authorization, validation, version-conflict, or individual rejected outcomes without changing the request.

  1. Call /whoami during setup and verify the expected organization and scopes.
  2. Read every page changed since the last completed high-water mark and apply those changes to the external system idempotently.
  3. Send external changes to /batch-upsert, using stable external IDs and source modification timestamps.
  4. Persist checkpoints only after all pages and item results have been handled.
  5. Resolve conflicts according to the source system's ownership rules; do not emulate deletes by sending empty records.

Scoutello deliberately provides no public delete route in this first version. Handle deletion or archival policies outside the API until a documented lifecycle operation is introduced.