Guide
Prospect to sequence
The whole loop in one page: find someone in the shared pool, reveal their details, turn them into a contact, put them in a sequence, and read the funnel back. Four surfaces — the CLI, Python, Go and TypeScript — doing the same six steps, with the real output from the run that produced this page.
Executed 2026-08-12api.vxcloud.iovxcli · Python · Go · TypeScript
The six steps
- 1
Search the pool
Cross-tenant, and masked. Costs nothing. - 2
Reveal one person
Un-masks email, phone and LinkedIn together — one reveal, all three. - 3
Convert to a contact
The only step that produces something mailable. - 4
Create a sequence
Two touches, three days apart, stopping on reply. - 5
Activate and enrol
Suppressed and unsubscribed contacts are skipped here, with a reason. - 6
Read the funnel
Measured against tracking rows, not incremented counters.
With vxcli
The fastest way to see the whole thing work. Each block is a real command; the output beneath it is pasted.
vxcli salesshift leads search --title "engineer" --limit 5 Matches 13
Sort (applied) score desc
Backend go-node
NAME TITLE COMPANY LOCATION SCORE
Ingrid Beaumont VP Engineering Makersite Edinburgh, GB 62
dfa655ce-7fe3-5632-a7ae-9ed7d4cbefbe i•••@makersite.de (masked — not revealed) · unverified · phone×1
Devon Delacroix VP Engineering Boast Toronto, CA 60
7f539023-7aad-581f-9e14-7a32e9ea4ca7 d•••@boast.ai (masked — not revealed) · unverified · phone×1
(masked) is not a real address — `vxcli salesshift leads reveal <pool-id>` spends one reveal.
Next page: rerun with --cursor NTQ.c42ddbb5-4d55-5712-8b49-df7a20541461vxcli salesshift leads reveal 7f539023-7aad-581f-9e14-7a32e9ea4ca7 --yes ! This will use up to 1 reveal(s) — you have 2499973 of 2500000 left this period.
A person you have already revealed is free to reveal again.
▎ Revealed ────────────────────────────────────────────────────
Pool ID 7f539023-7aad-581f-9e14-7a32e9ea4ca7
Email [email protected]
Phone +1 416 8973 4254
LinkedIn https://www.linkedin.com/in/devon-delacroix-83
Reveals used 27 / 2500000
Remaining 2499973
Revealing does not make this person mailable — only `leads convert` creates a Contact.One reveal returned the address, the phone and the LinkedIn profile. They are not metered separately.
vxcli salesshift leads save 7f539023-7aad-581f-9e14-7a32e9ea4ca7
vxcli salesshift leads convert <lead-id>
# then confirm it is a contact, and therefore mailable
vxcli salesshift contacts list --q "Devon Delacroix"vxcli salesshift sequences create \
--name "CLI parity — 2-touch intro" \
--subject "{{first_name}}, quick question about {{company_name}}" \
--body "<p>Hi {{first_name}},</p><p>We build and host the sending, the pool and the automation ourselves.</p>" \
--delay-days 0
vxcli salesshift sequences add-step <sequence-id> \
--subject "Re: {{company_name}}" \
--body "<p>Bumping this once in case it slipped.</p>" \
--delay-days 3 ▎ Sequence ────────────────────────────────────────────────────
Name CLI parity — 2-touch intro
Status DRAFT
Stop on reply true
Send days mon, tue, wed, thu, fri
▎ Steps (2) ───────────────────────────────────────────────────
+0d email {{first_name}}, quick question about {{company_name…
+3d email Re: {{company_name}}Delays are measured from the previous step, not from enrolment — so+3d means three days after touch one goes out.
vxcli salesshift sequences activate <sequence-id>
vxcli salesshift sequences enroll <sequence-id> --contacts <id>,<id>,<id>
vxcli salesshift sequences enrollments <sequence-id> Active — due steps will dispatch.
✓ 3 enrolled · 0 skipped
ACTIVE [email protected] step 1 —
ACTIVE [email protected] step 1 —
ACTIVE [email protected] step 1 —vxcli salesshift sequences analytics <sequence-id> ▎ Sequence analytics ──────────────────────────────────────────
Enrolled 4
Sent 8
Opened 6 (75.0%)
Clicked 0 (0.0%)
Replied 0 (0.0%)
Bounced 0 (0.0%)
▎ Per step ────────────────────────────────────────────────────
Step 1 call 6 sent · 0 opened · 0 replied
Step 2 email 4 sent · 3 opened · 0 replied
Wait 2d 4h delay 0 sent · 0 opened · 0 replied
Step 4 email 4 sent · 3 opened · 0 repliedEvery counter there is measured against the email-tracking rows rather than incremented in place, which is why a step can show more sends than the step after it: enrolments that replied or bounced never reached the later touch.
The same flow in an SDK
Python, Go and TypeScript expose the same six calls. The Python block below is the one that produced the output underneath it.
import vxsdk
c = vxsdk.Client.load_from_vxcli()
c.authenticate()
ss = c.salesshift
# 1-3 — find a contact that already exists, or convert one from the pool
page = ss.list_contacts(search="Joel Wembo", limit=3)
contact_id = page["data"][0]["id"]
# 4 — a two-touch sequence, built in one call
seq = ss.create_sequence(
"SDK parity — single touch",
description="created by the python SDK",
steps=[{
"subject": "{{first_name}}, a note from the SDK",
"body_html": "<p>Sent by the python SDK.</p>",
"delay_days": 0,
}],
)
sid = seq["id"]
ss.add_sequence_step(sid, subject="Re: following up",
body_html="<p>Second touch.</p>", delay_days=2)
# 5 — activate, then enrol
ss.activate_sequence(sid)
enr = ss.enroll_in_sequence(sid, [contact_id])
print(enr["enrolled"], "enrolled;", enr["skipped"], "skipped")
for s in enr.get("skipped_details", []):
print(" skipped", s["email"], "->", s["reason"])
# 6 — the funnel. NOTE the nesting: enrolled is top level, the counters
# are under "totals". Reading them beside enrolled returns None.
a = ss.sequence_analytics(sid)
print(a["enrolled"], "enrolled,", a["totals"]["sent"], "sent")ctx := context.Background()
ss := client.SalesShift()
contacts, _, err := ss.ListContacts(ctx, salesshift.ContactFilter{
Search: "Joel Wembo", Limit: 3,
})
if err != nil {
return err
}
contactID := contacts[0].ID
seq, err := ss.CreateSequence(ctx, salesshift.NewSequence{
Name: "SDK parity — single touch",
StopOnReply: true,
Steps: []salesshift.NewStep{{
StepType: "email",
Subject: "{{first_name}}, a note from the SDK",
BodyHTML: "<p>Sent by the Go SDK.</p>",
}},
})
if err != nil {
return err
}
if _, err := ss.AddStep(ctx, seq.ID, salesshift.NewStep{
StepType: "email", Subject: "Re: following up",
BodyHTML: "<p>Second touch.</p>", DelayDays: 2,
}); err != nil {
return err
}
if _, err := ss.ActivateSequence(ctx, seq.ID); err != nil {
return err
}
enr, err := ss.EnrollInSequence(ctx, seq.ID, []string{contactID})
if err != nil {
return err
}
for _, s := range enr.SkippedDetails {
log.Printf("skipped %s: %s", s.Email, s.Reason)
}
// Counters live on Totals, not on the analytics struct itself.
a, err := ss.Analytics(ctx, seq.ID)
if err != nil {
return err
}
log.Printf("%d enrolled, %d sent, %.1f%% open", a.Enrolled, a.Totals.Sent, a.Totals.OpenRate)const c = VxCloud.loadFromVxcli();
const { data } = await c.contacts.list({ search: 'Joel Wembo', limit: 3 });
const contactId = data[0].id;
const seq = await c.sequences.create({
name: 'SDK parity — single touch',
stopOnReply: true,
steps: [{
subject: '{{first_name}}, a note from the SDK',
bodyHtml: '<p>Sent by the TypeScript SDK.</p>',
delayDays: 0,
}],
});
await c.sequences.addStep(seq.id, {
subject: 'Re: following up',
bodyHtml: '<p>Second touch.</p>',
delayDays: 2,
});
await c.sequences.activate(seq.id);
const enr = await c.sequences.enroll(seq.id, [contactId]);
for (const s of enr.skippedDetails) {
console.log(`skipped ${s.email}: ${s.reason}`);
}
const a = await c.sequences.analytics(seq.id);
console.log(a);authenticated as joelwembo · https://api.vxcloud.io
──── contacts ────
full-name search -> 3 of 3
first: [email protected] (613f2aec-14b8-4281-9ced-19fc2c8d38a8)
──── sequences: create -> add step -> activate -> enroll -> analytics ────
created e16ce88a-43ab-4d7c-a2de-a8170962d705
add step -> step_number=2 delay=2d
steps -> 2 hydrated (list rows carry steps_count only)
activated
enroll -> 1 enrolled, [] skipped
enrollments -> 1
active [email protected] at step 1
analytics -> enrolled=1 sent=0 opened=0 replied=0
paused (left parked, not archived)