Diamond automations

A Diamond is a marketing automation built around three lessons and an offer. It follows a lead from sign-up to purchase, records the stage the lead is in, and, on every stage transition, delivers an event to the trigger configured for that stage (HTTP, WhatsApp, SMS, or email). The public term is always diamond: no field, permission, or message uses funnel.

All endpoints live under /automations/diamond, because diamond is one of the automation types an account can have. If Diamond automations are not enabled for the account, every endpoint in this group returns 409 feature_disabled, never 200 with an empty list.

For the full schemas, see the Diamonds reference.

Diamonds

GET /automations/diamond lists the account's Diamonds. It requires diamonds.read, uses cursor pagination, and accepts the product_id, showcase_id, status, and deleted filters. GET /automations/diamond/{diamond_id} returns a single Diamond, with an ETag for its current revision:

{
  "object": "diamond",
  "id": "dmd_42",
  "type": "diamond",
  "name": "September launch",
  "status": "active",
  "product_id": "prd_501",
  "showcase_id": "shw_7",
  "delivery_id": "dlv_900",
  "lesson_ids": ["les_11", "les_12", "les_13"],
  "settings": {
    "steps_interval": {
      "class_1_not_initiated": 60,
      "class_2_not_initiated": 120,
      "class_3_not_initiated": 180,
      "checkout_not_initiated": 60,
      "checkout_abandoned": 60
    },
    "expected_delivery": "dlv_777"
  },
  "memberships_count": 318,
  "deleted": false,
  "revision": "...",
  "created_at": "2026-09-01T10:00:00Z",
  "updated_at": "2026-09-20T12:00:00Z"
}

delivery_id is the Diamond's internal delivery, which grants free access to the three lessons. Do not confuse it with settings.expected_delivery: that is the delivery of the actual offer, and purchasing it moves the lead to won. steps_interval lists only the time-based stages, with the wait time in minutes. A stage you never configured shows its default value.

POST /automations/diamond creates a complete Diamond from a showcase: {showcase_id, name?, type: "diamond"}. Send an Idempotency-Key header. Besides the Diamond itself, the operation creates its product, three lessons, internal delivery, and eleven triggers. Credentials scoped to that showcase can access all of them. Because the operation creates products and deliveries, it requires products.create and deliveries.create in addition to diamonds.create: permission to create automations alone does not grant permission to create products or deliveries.

PATCH /automations/diamond/{diamond_id} requires diamonds.update and accepts name, settings.steps_interval, and status (active or inactive). Send the current ETag in If-Match. steps_interval is a partial update: send only the time-based stages you want to change. Changing status also requires diamonds.activate, because activating or deactivating a Diamond changes what leads receive.

DELETE /automations/diamond/{diamond_id} requires diamonds.delete and moves the Diamond to the trash. It returns 409 state_conflict while the Diamond has any membership, and there is no parameter to force deletion: triggers and scheduled communications remain in place after deletion, so deleting a Diamond that still has leads would keep sending messages from an automation you consider removed.

Stages

GET /automations/diamond/{diamond_id}/stages requires diamonds.read and always returns the Diamond's eleven fixed stages, in order. If a stage's trigger is missing, the stage is still returned, with trigger_id set to null:

{
  "object": "stage",
  "id": "class_1_not_initiated",
  "position": 1,
  "kind": "class",
  "lesson_id": "les_11",
  "interval_minutes": 60,
  "trigger_id": "trg_103",
  "memberships_count": 42
}

id is the stage's stable literal value, which automations already rely on. Each stage belongs to one of four kind groups: lead, class (six stages: not initiated and concluded for each of the three lessons), checkout (not initiated, initiated, and abandoned), and won. The set of stages is fixed: you cannot create or delete stages.

PATCH /automations/diamond/{diamond_id}/stages/{stage_id} is the only write available on a stage. It requires diamonds.update and sets the waiting interval, in minutes, of a time-based stage: {"interval_minutes": 30}. Stages share the Diamond's revision, so send the Diamond's ETag in If-Match. The time-based stages are class_1_not_initiated, class_2_not_initiated, class_3_not_initiated, checkout_not_initiated, and checkout_abandoned.

Only these interval values are accepted: 15, 30, 60, 120, 360, 720, 1440, and 2880. The same values apply to settings.steps_interval. Any other value returns 422 validation_failed instead of silently scheduling something other than what you asked for. The change applies to future scheduling; communications already scheduled for leads are not affected.

Triggers

Each of the eleven stages has one trigger, created together with the Diamond. A trigger defines where the communication for that stage is sent.

GET /automations/diamond/{diamond_id}/triggers requires diamonds.read and lists all eleven. GET .../triggers/{trigger_id} returns one, with an ETag for its current revision:

{
  "object": "trigger",
  "id": "trg_103",
  "diamond_id": "dmd_42",
  "stage_id": "class_1_not_initiated",
  "event_type": "spyhub.diamond.class_1_not_initiated",
  "channel": "http",
  "destination": { "url": "https://automation.example.com/***" },
  "enabled": true,
  "revision": "...",
  "created_at": "2026-09-01T10:00:00Z",
  "updated_at": "2026-09-01T10:00:00Z"
}

destination is always masked: HTTP destinations show only the scheme and host, and phone destinations show only the last four digits. Neither the list nor the retrieve operation returns the full destination, and email destinations are returned as null. event_type has the form spyhub.diamond.{stage} and never changes, because external automations already recognize it.

POST /automations/diamond/{diamond_id}/triggers requires diamonds.manage_triggers and an Idempotency-Key header. It ensures that the trigger for a stage exists: {"stage_id": "lead"}. It does not create free-form triggers, because the set of stages is fixed. Normally the response is 200 with the existing trigger; 201 is returned only if the stage's trigger was missing and had to be created.

PATCH .../triggers/{trigger_id} requires diamonds.manage_triggers. Send the trigger's current ETag in If-Match. It configures enabled, channel (http, whatsapp, sms, or email), destination ({url}, {phone}, or {email}, matching the channel), and template. HTTP destinations go through the same public-host check as webhooks: private, loopback, and metadata addresses are rejected with 422 validation_failed. Triggers cannot be deleted: to turn a stage off, set enabled: false or clear the destination. The change applies to future communications with leads; nothing is sent when you update the trigger.

Memberships (leads)

A membership is a lead inside a Diamond.

GET /automations/diamond/{diamond_id}/memberships lists the leads. It requires diamonds.read, uses cursor pagination, and accepts the user_id, stage_id, and updated_after filters. GET .../memberships/{membership_id} returns one, with an ETag:

{
  "object": "membership",
  "id": "mbr_9001",
  "diamond_id": "dmd_42",
  "user_id": "usr_777",
  "stage_id": "class_1_not_initiated",
  "stage_position": 1,
  "progress": { "confirmed_stages": ["lead"] },
  "pending": { "next_check_at": "2026-09-23T14:00:00Z", "queued_events": 1 },
  "last_run": null,
  "enrollment_id": "enr_555",
  "revision": "...",
  "created_at": "2026-09-23T10:00:00Z",
  "updated_at": "2026-09-23T13:00:00Z"
}

progress.confirmed_stages is the lead's actual progress: a stage appears there once its transition is confirmed, which is also when its communication is sent. pending shows what is scheduled for the lead. last_run, when not null, is the most recent failed communication for this membership. Failed communications are not retried automatically; see Processing attempts. enrollment_id is the enrollment that grants the lead access to the Diamond's internal delivery.

POST /automations/diamond/{diamond_id}/memberships adds a user to the Diamond as a lead: {"user_id": "usr_777"}. It requires diamonds.manage_memberships and enrollments.create, because the same operation also enrolls the user in the internal delivery. Send an Idempotency-Key header. The response is 201 for a new lead and 200 if the user is already a member of this Diamond; in that case, no communication is sent again. A user can belong to only one Diamond at a time: if the user already belongs to another Diamond, the API returns 409 state_conflict with the already_in_diamond reason.

PATCH .../memberships/{membership_id} requires diamonds.manage_memberships and moves the lead to another stage manually. Send the membership's current ETag in If-Match:

{ "target_stage_id": "checkout_initiated", "reason": "Reassigned by the sales team" }

reason is required and is included in the diamond_membership.stage_changed event. Leads only move forward: a target stage at or before the current one returns 409 state_conflict, so repeating a transition that already happened is rejected and does not send the communication again. A manual move follows the same rules as the automatic progression described in What happens automatically, and the communication for the new stage is sent once.

Processing attempts

A processing attempt is not a standalone resource: it is an item of a diamond.reprocess_membership operation, requested for a membership.

POST .../memberships/{membership_id}/processing-attempts requires diamonds.reprocess and an Idempotency-Key header. It requests that the most recent failed communication for the lead be sent again, queuing the communication for that stage. If the stage has already been confirmed, nothing is sent again, which prevents duplicate messages. A membership with no recorded failure returns 422 validation_failed, never a 202 that does nothing: accepting the request would promise a resend that cannot happen. A successful request returns 202:

{
  "object": "processing_attempt",
  "id": "pat_88",
  "membership_id": "mbr_9001",
  "effects": { "stage_confirmed": false, "communication_sent": true },
  "operation_id": "op_01J8Z3...",
  "status": "queued",
  "error": null,
  "requested_by": { "kind": "credential", "id": "key_..." },
  "created_at": "2026-09-23T15:00:00Z"
}

GET .../memberships/{membership_id}/processing-attempts lists the attempts already requested for the lead, all in a single response without pagination. GET .../processing-attempts/{attempt_id} returns one. An attempt that belongs to another membership returns 404 through this path, even if it exists.

Permissions

diamonds.read, diamonds.create, diamonds.update, diamonds.delete, diamonds.activate (changing status), diamonds.manage_triggers (triggers), diamonds.manage_memberships (adding and moving leads), and diamonds.reprocess. Creating a Diamond also requires products.create and deliveries.create; creating a membership also requires enrollments.create.

Access to Diamonds can be scoped to specific showcases, specific products, or the whole account. Access to memberships can additionally be scoped to specific users: credentials delegated to a user's data see only that user's memberships, without needing access to the whole Diamond. See Access control.

Events

diamond.created, diamond.updated, diamond.deleted, diamond_trigger.updated, diamond_membership.created, diamond_membership.stage_changed, diamond_membership.run_failed, and diamond_membership.reprocess_requested. No payload includes the lead's personal data or the destination configured on a trigger. See Events for the catalog.

What happens automatically

Cademí always advances leads on its own, never through the API: after a stage's waiting interval elapses, when the lead watches a lesson, opens the offer link, or purchases the expected delivery. The API offers only two state changes: adding a new lead (POST .../memberships, always starting at lead) and moving an existing membership to a later stage (PATCH .../memberships/{id}, with reason). The API never moves a lead backward and does not bypass the progression rules.

Errors

CodeStatusWhen
not_found404The Diamond, stage, trigger, membership, or attempt does not exist or is not accessible with the current credentials (including showcase, product, or user scoping)
permission_denied403The credentials lack one of the permissions the operation requires
feature_disabled409Diamond automations are not enabled for the account
state_conflict409The target stage is at or before the current stage; the user already belongs to another Diamond; the Diamond still has memberships on DELETE
revision_mismatch412The If-Match revision does not match the current one
validation_failed422A required field is missing, stage_id is not a valid stage, interval_minutes is not an accepted value, the trigger destination is not a public host, or the membership has no recorded failure to reprocess

See Errors for the error format.

On this page