yirlo 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yirlo-1.0.0/PKG-INFO +27 -0
- yirlo-1.0.0/README.md +16 -0
- yirlo-1.0.0/pyproject.toml +16 -0
- yirlo-1.0.0/setup.cfg +4 -0
- yirlo-1.0.0/src/yirlo/__init__.py +206 -0
- yirlo-1.0.0/src/yirlo.egg-info/PKG-INFO +27 -0
- yirlo-1.0.0/src/yirlo.egg-info/SOURCES.txt +7 -0
- yirlo-1.0.0/src/yirlo.egg-info/dependency_links.txt +1 -0
- yirlo-1.0.0/src/yirlo.egg-info/top_level.txt +1 -0
yirlo-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yirlo
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Yirlo SDK: run your AI cold email outreach (projects, campaigns, inbox, hot leads, budgets) from Python.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://yirlo.com
|
|
7
|
+
Project-URL: Documentation, https://yirlo.com/api-docs
|
|
8
|
+
Keywords: yirlo,cold email,outbound,lead generation,sdk
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Yirlo
|
|
13
|
+
|
|
14
|
+
Run Yirlo, the AI outbound agent for B2B companies, from Python: projects, campaigns, budgets, the inbox and replies, hot leads, webhooks and your team. No dependencies.
|
|
15
|
+
|
|
16
|
+
Get an API key at https://yirlo.com/app/api-keys. Full API docs: https://yirlo.com/api-docs
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import os
|
|
20
|
+
from yirlo import Yirlo
|
|
21
|
+
y = Yirlo(os.environ["YIRLO_API_KEY"])
|
|
22
|
+
me = y.get_account()
|
|
23
|
+
campaigns = y.list_campaigns(project_id=me["projects"][0]["id"])["campaigns"]
|
|
24
|
+
hot = y.list_hot_leads()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Errors raise `YirloError` with `.status` and `.body`. MCP server: `https://yirlo.com/mcp` (header `Authorization: Bearer <key>`).
|
yirlo-1.0.0/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Yirlo
|
|
2
|
+
|
|
3
|
+
Run Yirlo, the AI outbound agent for B2B companies, from Python: projects, campaigns, budgets, the inbox and replies, hot leads, webhooks and your team. No dependencies.
|
|
4
|
+
|
|
5
|
+
Get an API key at https://yirlo.com/app/api-keys. Full API docs: https://yirlo.com/api-docs
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import os
|
|
9
|
+
from yirlo import Yirlo
|
|
10
|
+
y = Yirlo(os.environ["YIRLO_API_KEY"])
|
|
11
|
+
me = y.get_account()
|
|
12
|
+
campaigns = y.list_campaigns(project_id=me["projects"][0]["id"])["campaigns"]
|
|
13
|
+
hot = y.list_hot_leads()
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Errors raise `YirloError` with `.status` and `.body`. MCP server: `https://yirlo.com/mcp` (header `Authorization: Bearer <key>`).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "yirlo"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Yirlo SDK: run your AI cold email outreach (projects, campaigns, inbox, hot leads, budgets) from Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["yirlo", "cold email", "outbound", "lead generation", "sdk"]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://yirlo.com"
|
|
16
|
+
Documentation = "https://yirlo.com/api-docs"
|
yirlo-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Yirlo SDK for Python 3.8+ (standard library only). https://yirlo.com/sdk/yirlo.py
|
|
2
|
+
Every method is one call to https://yirlo.com/public/api/v1 (docs: https://yirlo.com/api-docs).
|
|
3
|
+
Usage: from yirlo import Yirlo; y = Yirlo(os.environ["YIRLO_API_KEY"]); print(y.get_account())"""
|
|
4
|
+
import json, urllib.request, urllib.error, urllib.parse
|
|
5
|
+
|
|
6
|
+
class YirloError(Exception):
|
|
7
|
+
def __init__(self, status, body):
|
|
8
|
+
super().__init__((body or {}).get("detail") or f"HTTP {status}"); self.status, self.body = status, body
|
|
9
|
+
|
|
10
|
+
class Yirlo:
|
|
11
|
+
def __init__(self, api_key, base_url="https://yirlo.com"):
|
|
12
|
+
if not api_key:
|
|
13
|
+
raise ValueError("Pass your Yirlo API key (https://yirlo.com/app/api-keys)")
|
|
14
|
+
self.api_key, self.base_url = api_key, base_url.rstrip("/")
|
|
15
|
+
|
|
16
|
+
def request(self, method, path, query=None, body=None):
|
|
17
|
+
q = {k: v for k, v in (query or {}).items() if v is not None}
|
|
18
|
+
url = self.base_url + "/public/api/v1" + path + ("?" + urllib.parse.urlencode(q) if q else "")
|
|
19
|
+
data = json.dumps(body).encode() if body is not None else (None if method == "GET" else b"{}")
|
|
20
|
+
req = urllib.request.Request(url, data=data, method=method, headers={"X-API-Key": self.api_key, "Content-Type": "application/json", "User-Agent": "yirlo-python/1.0"})
|
|
21
|
+
try:
|
|
22
|
+
with urllib.request.urlopen(req, timeout=120) as r:
|
|
23
|
+
t = r.read()
|
|
24
|
+
return json.loads(t) if t else {}
|
|
25
|
+
except urllib.error.HTTPError as e:
|
|
26
|
+
t = e.read()
|
|
27
|
+
try:
|
|
28
|
+
body = json.loads(t) if t else {}
|
|
29
|
+
except ValueError:
|
|
30
|
+
body = {"detail": t.decode("utf-8", "replace")}
|
|
31
|
+
raise YirloError(e.code, body)
|
|
32
|
+
|
|
33
|
+
def get_account(self):
|
|
34
|
+
"""Who this API key is: email, credit balance and projects. A good first call."""
|
|
35
|
+
a = {}
|
|
36
|
+
path = "/me"
|
|
37
|
+
return self.request("GET", path)
|
|
38
|
+
|
|
39
|
+
def get_balance(self):
|
|
40
|
+
"""Credit balance (1 credit = $0.01)."""
|
|
41
|
+
a = {}
|
|
42
|
+
path = "/billing/balance"
|
|
43
|
+
return self.request("GET", path)
|
|
44
|
+
|
|
45
|
+
def list_projects(self):
|
|
46
|
+
"""Every outreach project on the account."""
|
|
47
|
+
a = {}
|
|
48
|
+
path = "/agentapp/projects"
|
|
49
|
+
return self.request("GET", path)
|
|
50
|
+
|
|
51
|
+
def create_project(self, domain=None, no_website=None, sender_name=None):
|
|
52
|
+
"""Create a project from a company website (or, with no website, from what it sells, to whom and where). Answers at once with the project id and status creating; Yirlo analyses the company in about 1-2 minutes, then get_project shows status ready and its campaigns are paused until you start one."""
|
|
53
|
+
a = {"domain": domain, "no_website": no_website, "sender_name": sender_name}
|
|
54
|
+
path = "/agentapp/projects"
|
|
55
|
+
return self.request("POST", path, body={k: v for k, v in a.items() if v is not None})
|
|
56
|
+
|
|
57
|
+
def get_project(self, project_id):
|
|
58
|
+
"""One project: company, settings, campaigns. For a project from create_project: status creating, ready or failed (with failure_reason and next_step)."""
|
|
59
|
+
a = {}
|
|
60
|
+
path = "/agentapp/projects/{project_id}".replace("{project_id}", urllib.parse.quote(str(project_id), safe=""))
|
|
61
|
+
return self.request("GET", path)
|
|
62
|
+
|
|
63
|
+
def project_analytics(self, project_id):
|
|
64
|
+
"""Emails sent, replies and hot leads for a project."""
|
|
65
|
+
a = {}
|
|
66
|
+
path = "/agentapp/projects/{project_id}/analytics".replace("{project_id}", urllib.parse.quote(str(project_id), safe=""))
|
|
67
|
+
return self.request("GET", path)
|
|
68
|
+
|
|
69
|
+
def get_project_budget(self, project_id):
|
|
70
|
+
"""Daily budget and monthly cap."""
|
|
71
|
+
a = {}
|
|
72
|
+
path = "/agentapp/projects/{project_id}/budget".replace("{project_id}", urllib.parse.quote(str(project_id), safe=""))
|
|
73
|
+
return self.request("GET", path)
|
|
74
|
+
|
|
75
|
+
def set_project_budget(self, project_id, daily_budget_usd):
|
|
76
|
+
"""Set the project daily budget in USD."""
|
|
77
|
+
a = {"daily_budget_usd": daily_budget_usd}
|
|
78
|
+
path = "/agentapp/projects/{project_id}/budget".replace("{project_id}", urllib.parse.quote(str(project_id), safe=""))
|
|
79
|
+
return self.request("PATCH", path, body={k: v for k, v in a.items() if v is not None})
|
|
80
|
+
|
|
81
|
+
def list_campaigns(self, project_id=None):
|
|
82
|
+
"""Campaigns (audiences), optionally for one project."""
|
|
83
|
+
a = {"project_id": project_id}
|
|
84
|
+
path = "/agentapp/campaigns"
|
|
85
|
+
return self.request("GET", path, query=a)
|
|
86
|
+
|
|
87
|
+
def get_campaign(self, campaign_id):
|
|
88
|
+
"""One campaign: audience, targeting and email instructions."""
|
|
89
|
+
a = {}
|
|
90
|
+
path = "/agentapp/campaigns/{campaign_id}".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe=""))
|
|
91
|
+
return self.request("GET", path)
|
|
92
|
+
|
|
93
|
+
def create_campaign(self, project_id, name, target_geo=None):
|
|
94
|
+
"""Create a campaign from a name that describes the people to reach, e.g. "Heads of support at Shopify fashion brands". Takes about 30 seconds."""
|
|
95
|
+
a = {"project_id": project_id, "name": name, "target_geo": target_geo}
|
|
96
|
+
path = "/agentapp/campaigns"
|
|
97
|
+
return self.request("POST", path, body={k: v for k, v in a.items() if v is not None})
|
|
98
|
+
|
|
99
|
+
def start_campaign(self, campaign_id):
|
|
100
|
+
"""Start (resume) a campaign."""
|
|
101
|
+
a = {}
|
|
102
|
+
path = "/agentapp/campaigns/{campaign_id}/start".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe=""))
|
|
103
|
+
return self.request("POST", path)
|
|
104
|
+
|
|
105
|
+
def stop_campaign(self, campaign_id):
|
|
106
|
+
"""Pause a campaign."""
|
|
107
|
+
a = {}
|
|
108
|
+
path = "/agentapp/campaigns/{campaign_id}/stop".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe=""))
|
|
109
|
+
return self.request("POST", path)
|
|
110
|
+
|
|
111
|
+
def archive_campaign(self, campaign_id):
|
|
112
|
+
"""Archive a campaign."""
|
|
113
|
+
a = {}
|
|
114
|
+
path = "/agentapp/campaigns/{campaign_id}/archive".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe=""))
|
|
115
|
+
return self.request("POST", path)
|
|
116
|
+
|
|
117
|
+
def set_campaign_budget(self, campaign_id, daily_limit_usd):
|
|
118
|
+
"""Set a campaign daily limit in USD."""
|
|
119
|
+
a = {"daily_limit_usd": daily_limit_usd}
|
|
120
|
+
path = "/agentapp/campaigns/{campaign_id}/budget".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe=""))
|
|
121
|
+
return self.request("PATCH", path, body={k: v for k, v in a.items() if v is not None})
|
|
122
|
+
|
|
123
|
+
def campaign_analytics(self, campaign_id):
|
|
124
|
+
"""Leads found, contacted, replied, hot, bounced for a campaign."""
|
|
125
|
+
a = {}
|
|
126
|
+
path = "/agentapp/campaigns/{campaign_id}/analytics".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe=""))
|
|
127
|
+
return self.request("GET", path)
|
|
128
|
+
|
|
129
|
+
def list_hot_leads(self, project_id=None):
|
|
130
|
+
"""Interested replies (hot leads), optionally for one project."""
|
|
131
|
+
a = {"project_id": project_id}
|
|
132
|
+
path = "/agentapp/hot-leads"
|
|
133
|
+
return self.request("GET", path, query=a)
|
|
134
|
+
|
|
135
|
+
def list_inbox(self, project_id, tab=None):
|
|
136
|
+
"""Conversations in a project inbox. tab: need_reply, hot, all or replied."""
|
|
137
|
+
a = {"tab": tab}
|
|
138
|
+
path = "/agentapp/projects/{project_id}/inbox".replace("{project_id}", urllib.parse.quote(str(project_id), safe=""))
|
|
139
|
+
return self.request("GET", path, query=a)
|
|
140
|
+
|
|
141
|
+
def read_conversation(self, campaign_id, person_id):
|
|
142
|
+
"""The full thread with one lead."""
|
|
143
|
+
a = {}
|
|
144
|
+
path = "/agentapp/campaigns/{campaign_id}/inbox/{person_id}".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe="")).replace("{person_id}", urllib.parse.quote(str(person_id), safe=""))
|
|
145
|
+
return self.request("GET", path)
|
|
146
|
+
|
|
147
|
+
def reply(self, campaign_id, person_id, body_text):
|
|
148
|
+
"""Send a reply to a lead in a thread. This sends a real email."""
|
|
149
|
+
a = {"body_text": body_text}
|
|
150
|
+
path = "/agentapp/campaigns/{campaign_id}/inbox/{person_id}/reply".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe="")).replace("{person_id}", urllib.parse.quote(str(person_id), safe=""))
|
|
151
|
+
return self.request("POST", path, body={k: v for k, v in a.items() if v is not None})
|
|
152
|
+
|
|
153
|
+
def set_lead_note(self, campaign_id, person_id, note):
|
|
154
|
+
"""Set the note on a lead."""
|
|
155
|
+
a = {"note": note}
|
|
156
|
+
path = "/agentapp/campaigns/{campaign_id}/inbox/{person_id}/note".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe="")).replace("{person_id}", urllib.parse.quote(str(person_id), safe=""))
|
|
157
|
+
return self.request("POST", path, body={k: v for k, v in a.items() if v is not None})
|
|
158
|
+
|
|
159
|
+
def set_lead_status(self, campaign_id, person_id, status):
|
|
160
|
+
"""Set a lead's deal status."""
|
|
161
|
+
a = {"status": status}
|
|
162
|
+
path = "/agentapp/campaigns/{campaign_id}/inbox/{person_id}/status".replace("{campaign_id}", urllib.parse.quote(str(campaign_id), safe="")).replace("{person_id}", urllib.parse.quote(str(person_id), safe=""))
|
|
163
|
+
return self.request("POST", path, body={k: v for k, v in a.items() if v is not None})
|
|
164
|
+
|
|
165
|
+
def suppress_emails(self, emails, list_name=None):
|
|
166
|
+
"""Never email these addresses (every project)."""
|
|
167
|
+
a = {"emails": emails, "list_name": list_name}
|
|
168
|
+
path = "/agentapp/suppress-list/people"
|
|
169
|
+
return self.request("POST", path, body={k: v for k, v in a.items() if v is not None})
|
|
170
|
+
|
|
171
|
+
def suppress_domains(self, domains, list_name=None):
|
|
172
|
+
"""Never email anyone at these company domains (every project)."""
|
|
173
|
+
a = {"domains": domains, "list_name": list_name}
|
|
174
|
+
path = "/agentapp/suppress-list/companies"
|
|
175
|
+
return self.request("POST", path, body={k: v for k, v in a.items() if v is not None})
|
|
176
|
+
|
|
177
|
+
def list_webhooks(self, project_id=None):
|
|
178
|
+
"""Webhooks for a project (events: reply.received, hot_lead.received)."""
|
|
179
|
+
a = {"project_id": project_id}
|
|
180
|
+
path = "/agentapp/webhooks"
|
|
181
|
+
return self.request("GET", path, query=a)
|
|
182
|
+
|
|
183
|
+
def add_webhook(self, project_id, url, events=None):
|
|
184
|
+
"""Add an https webhook to a project."""
|
|
185
|
+
a = {"project_id": project_id, "url": url, "events": events}
|
|
186
|
+
path = "/agentapp/webhooks"
|
|
187
|
+
q = {"project_id": a.pop("project_id", None)}
|
|
188
|
+
return self.request("POST", path, query=q, body={k: v for k, v in a.items() if v is not None})
|
|
189
|
+
|
|
190
|
+
def test_webhook(self, project_id, webhook_id):
|
|
191
|
+
"""Send one signed sample event to a webhook."""
|
|
192
|
+
a = {"project_id": project_id}
|
|
193
|
+
path = "/agentapp/webhooks/{webhook_id}/test".replace("{webhook_id}", urllib.parse.quote(str(webhook_id), safe=""))
|
|
194
|
+
return self.request("POST", path, query=a)
|
|
195
|
+
|
|
196
|
+
def list_team(self, project_id):
|
|
197
|
+
"""People on a project."""
|
|
198
|
+
a = {}
|
|
199
|
+
path = "/agentapp/projects/{project_id}/team".replace("{project_id}", urllib.parse.quote(str(project_id), safe=""))
|
|
200
|
+
return self.request("GET", path)
|
|
201
|
+
|
|
202
|
+
def invite_teammate(self, project_id, email):
|
|
203
|
+
"""Invite someone to a project by email (owner only)."""
|
|
204
|
+
a = {"email": email}
|
|
205
|
+
path = "/agentapp/projects/{project_id}/team".replace("{project_id}", urllib.parse.quote(str(project_id), safe=""))
|
|
206
|
+
return self.request("POST", path, body={k: v for k, v in a.items() if v is not None})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yirlo
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Yirlo SDK: run your AI cold email outreach (projects, campaigns, inbox, hot leads, budgets) from Python.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://yirlo.com
|
|
7
|
+
Project-URL: Documentation, https://yirlo.com/api-docs
|
|
8
|
+
Keywords: yirlo,cold email,outbound,lead generation,sdk
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Yirlo
|
|
13
|
+
|
|
14
|
+
Run Yirlo, the AI outbound agent for B2B companies, from Python: projects, campaigns, budgets, the inbox and replies, hot leads, webhooks and your team. No dependencies.
|
|
15
|
+
|
|
16
|
+
Get an API key at https://yirlo.com/app/api-keys. Full API docs: https://yirlo.com/api-docs
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import os
|
|
20
|
+
from yirlo import Yirlo
|
|
21
|
+
y = Yirlo(os.environ["YIRLO_API_KEY"])
|
|
22
|
+
me = y.get_account()
|
|
23
|
+
campaigns = y.list_campaigns(project_id=me["projects"][0]["id"])["campaigns"]
|
|
24
|
+
hot = y.list_hot_leads()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Errors raise `YirloError` with `.status` and `.body`. MCP server: `https://yirlo.com/mcp` (header `Authorization: Bearer <key>`).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
yirlo
|