Signal API
Integration patterns as MarkdownUse a clean Markdown version of this page with an agent or local tooling.

Integration patterns

Keep partner data synchronized while handling retries, pagination, background work, and resource deletion predictably.

Synchronize resources with external IDs

Assign each project and location the stable identifier from your own system. Project external IDs are unique within the workspace. Location external IDs are unique within their parent project.

Find a project by external IDcURL
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/by-external-id/customer-acme-eu' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"
Use exact lookupsExternal-ID lookups are case-sensitive. URL-encode the identifier in /projects/by-external-id/{external_id} or the corresponding nested location path.
Use IDs in resource pathsProject and location path parameters accept either a Signal UUID or the configured external ID. A location identifier is resolved only inside its parent project.
Reconcile before writingLook up the resource, create it after a 404, and patch only fields that changed when it already exists.
Changing an external IDProject and location updates can replace external_id. Save the returned value before the next synchronization because the previous external-ID lookup will no longer match.

Build idempotent provisioning flows

Choose one idempotency key for one intended operation and persist it with the job that sends the request.

Native idempotency for bulk createcURL
curl --request POST \
  --url 'https://api.signal.ceyo.ai/v1/projects/bulk' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: sync-run-2026-08-04-001' \
  --data '{
    "projects": [
      {
        "name": "Acme Europe",
        "external_id": "customer-acme-eu",
        "package_id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
        "website": "https://acme.example",
        "start": false
      }
    ]
  }'
Native supportBulk project creation, bulk location creation, embed session creation, and embed session refresh require an Idempotency-Key header.
Retry with the same keyRepeating the same request returns the original operation or response. Reusing its key with a different request returns 409 idempotency_conflict.
Client-managed createSingle project and location create endpoints do not provide native idempotency keys. Look up a stable external ID before creating; if concurrent creation returns 409, look up that external ID again.
Do not rotate keys during an uncertain retryA new key represents a new intended native-idempotent operation. Keep the original key when a timeout or connection failure leaves the first request's result unknown.

Paginate and poll efficiently

List endpoints use 1-based offset pages. The default page size is 25, the maximum is 100, and every response includes page, per_page, total, and total_pages.

Read every project pageJavaScript
let page = 1;

while (true) {
  const response = await fetch(
    `https://api.signal.ceyo.ai/v1/projects?page=${page}&per_page=100&sort=created_at&direction=asc`,
    { headers: { Authorization: `Bearer ${process.env.SIGNAL_API_KEY}` } }
  );
  if (!response.ok) throw new Error(`List failed: ${response.status}`);

  const { projects, pagination } = await response.json();
  await synchronizePage(projects);

  if (page >= pagination.total_pages) break;
  page += 1;
}
Use a deterministic sortProject and location lists use the resource ID as an ascending final tie-breaker. Keep the same filters, sort, and direction throughout one scan. A page beyond the result set returns an empty array.

Bulk creates return 202 Accepted with a status_url. Poll that URL until the operation reaches a terminal status, then inspect every item.

Project and location creates with start: true return an onboarding_operation. Poll its status_url until it succeeds, completes partially, fails, or is cancelled.

Poll a bulk operationJavaScript
const terminal = new Set([
  "completed",
  "completed_with_errors",
  "failed",
]);

let operation;
do {
  const response = await fetch(
    `https://api.signal.ceyo.ai${statusUrl}`,
    { headers: { Authorization: `Bearer ${process.env.SIGNAL_API_KEY}` } }
  );
  if (!response.ok) throw new Error(`Poll failed: ${response.status}`);

  ({ bulk_operation: operation } = await response.json());
  if (!terminal.has(operation.status)) await new Promise((r) => setTimeout(r, 2000));
} while (!terminal.has(operation.status));

for (const item of operation.items) {
  if (item.status === "failed") recordFailure(item.index, item.error);
}
Continue pollingpending and running are non-terminal operation statuses.
Stop pollingcompleted, completed_with_errors, and failed are terminal statuses.
Handle partial successOne failed bulk item does not roll back successful items. Store each returned resource ID and retry failed records deliberately.

Handle deletion and lifecycle changes

Project and location deletion is asynchronous. A successful request returns 202 Accepted with no response body.

Request project deletioncURL
curl --request DELETE \
  --url 'https://api.signal.ceyo.ai/v1/projects/customer-acme-eu' \
  --header "Authorization: Bearer ${SIGNAL_API_KEY}"

# HTTP/1.1 202 Accepted
# No response body
Project deletionThe project enters a deleting state and disappears from ordinary public project reads while its locations and scoped resources are purged.
Location deletionThe location is archived and its visibility scope is made inactive before its location-scoped resources are purged.
Repeated requestsRepeating a delete while the same deletion is active is accepted. After the resource has been purged, its path returns 404.
Track deletion in your systemThe delete response does not include an operation ID or completion status. Record that deletion was requested. Do not treat a project 404 as proof that purging has finished, and retry a conflicting recreation later.
Lifecycle fields are read-onlyProject and location updates reject status. Use the supported delete endpoint for removal and treat returned lifecycle status as server-managed state.