qodev-apollo-cli 0.1.0__py3-none-any.whl

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.
Files changed (37) hide show
  1. apollo_cli/__init__.py +3 -0
  2. apollo_cli/__main__.py +5 -0
  3. apollo_cli/app.py +120 -0
  4. apollo_cli/commands/__init__.py +0 -0
  5. apollo_cli/commands/accounts.py +51 -0
  6. apollo_cli/commands/calls.py +54 -0
  7. apollo_cli/commands/contacts.py +147 -0
  8. apollo_cli/commands/deals.py +51 -0
  9. apollo_cli/commands/emails.py +37 -0
  10. apollo_cli/commands/enrich.py +34 -0
  11. apollo_cli/commands/install.py +59 -0
  12. apollo_cli/commands/jobs.py +36 -0
  13. apollo_cli/commands/news.py +35 -0
  14. apollo_cli/commands/notes.py +68 -0
  15. apollo_cli/commands/people.py +34 -0
  16. apollo_cli/commands/pipelines.py +106 -0
  17. apollo_cli/commands/tasks.py +79 -0
  18. apollo_cli/commands/usage.py +56 -0
  19. apollo_cli/context.py +35 -0
  20. apollo_cli/formatters/__init__.py +0 -0
  21. apollo_cli/formatters/accounts.py +62 -0
  22. apollo_cli/formatters/contacts.py +80 -0
  23. apollo_cli/formatters/deals.py +46 -0
  24. apollo_cli/formatters/generic.py +101 -0
  25. apollo_cli/help_reference.py +123 -0
  26. apollo_cli/output.py +150 -0
  27. apollo_cli/skills/SKILL.md +200 -0
  28. apollo_cli/skills/__init__.py +1 -0
  29. apollo_cli/skills/references/__init__.py +1 -0
  30. apollo_cli/skills/references/account-workflows.md +202 -0
  31. apollo_cli/skills/references/contact-workflows.md +110 -0
  32. apollo_cli/skills/references/deal-workflows.md +142 -0
  33. qodev_apollo_cli-0.1.0.dist-info/METADATA +224 -0
  34. qodev_apollo_cli-0.1.0.dist-info/RECORD +37 -0
  35. qodev_apollo_cli-0.1.0.dist-info/WHEEL +4 -0
  36. qodev_apollo_cli-0.1.0.dist-info/entry_points.txt +2 -0
  37. qodev_apollo_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,123 @@
1
+ """Build a flat command reference for the root --help epilogue."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import types
7
+ from typing import Annotated, Any, Union, get_args, get_origin, get_type_hints
8
+
9
+ from cyclopts import App, Parameter
10
+
11
+ COL_WIDTH = 46
12
+ MAX_LINE = 78
13
+ MIN_DESC_WIDTH = 10
14
+ ELLIPSIS = "\u2026"
15
+
16
+
17
+ def _is_bool_type(tp: object) -> bool:
18
+ if tp is bool:
19
+ return True
20
+ if tp is None:
21
+ return False
22
+ origin = get_origin(tp)
23
+ if origin is Union or isinstance(tp, types.UnionType):
24
+ return bool in get_args(tp)
25
+ return False
26
+
27
+
28
+ def _display_len(s: str) -> int:
29
+ """Return rendered width (Rich \\[ escapes render as single [)."""
30
+ return len(s.replace("\\[", "["))
31
+
32
+
33
+ def _format_signature(func: Any, prefix_len: int, col_width: int) -> str:
34
+ sig = inspect.signature(func)
35
+ try:
36
+ hints = get_type_hints(func, include_extras=True)
37
+ except (TypeError, NameError):
38
+ hints = {}
39
+
40
+ required: list[str] = []
41
+ optional: list[str] = []
42
+ for pname, param in sig.parameters.items():
43
+ hint = hints.get(pname)
44
+
45
+ cli_param = None
46
+ base_type = hint
47
+ if hint is not None and get_origin(hint) is Annotated:
48
+ args = get_args(hint)
49
+ base_type = args[0]
50
+ for arg in args[1:]:
51
+ if isinstance(arg, Parameter):
52
+ cli_param = arg
53
+ break
54
+
55
+ is_bool = _is_bool_type(base_type)
56
+ has_default = param.default is not inspect.Parameter.empty
57
+
58
+ if param.kind == param.KEYWORD_ONLY:
59
+ if cli_param and cli_param.name:
60
+ names = cli_param.name if isinstance(cli_param.name, (list, tuple)) else [cli_param.name]
61
+ cli_name = names[0]
62
+ else:
63
+ cli_name = f"--{pname.replace('_', '-')}"
64
+
65
+ if has_default:
66
+ optional.append(f"\\[{cli_name}]")
67
+ elif is_bool:
68
+ required.append(cli_name)
69
+ else:
70
+ required.append(f"{cli_name} {pname.upper()}")
71
+ elif param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
72
+ label = pname.upper()
73
+ if has_default:
74
+ optional.append(f"\\[{label}]")
75
+ else:
76
+ required.append(label)
77
+
78
+ max_sig = col_width - prefix_len - 2
79
+ parts = required + optional
80
+ result = " ".join(parts)
81
+ while len(result) > max_sig and optional:
82
+ optional.pop()
83
+ parts = required + optional + ["..."]
84
+ result = " ".join(parts)
85
+
86
+ return result
87
+
88
+
89
+ def build_command_reference(sub_apps: list[App]) -> str:
90
+ """Walk sub-apps and build a formatted "All Commands" block."""
91
+ entries: list[tuple[str, str]] = []
92
+ for sub in sub_apps:
93
+ sub_name = sub.name[0] if sub.name else "?"
94
+ for cmd_name, cmd_app in sub.resolved_commands().items():
95
+ if cmd_name.startswith("-"):
96
+ continue
97
+ func = cmd_app.default_command
98
+ if func is None:
99
+ continue
100
+ prefix = f" {sub_name} {cmd_name} "
101
+ sig_str = _format_signature(func, prefix_len=len(prefix), col_width=COL_WIDTH)
102
+ doc = (func.__doc__ or "").strip().split("\n")[0]
103
+ left = f" {sub_name} {cmd_name}"
104
+ if sig_str:
105
+ left += f" {sig_str}"
106
+ entries.append((left, doc))
107
+ entries.append(("", ""))
108
+
109
+ if entries and entries[-1] == ("", ""):
110
+ entries.pop()
111
+
112
+ lines = ["All Commands:\n"]
113
+ for left, doc in entries:
114
+ if not left:
115
+ lines.append("")
116
+ else:
117
+ display_w = _display_len(left)
118
+ pad = max(2, COL_WIDTH - display_w)
119
+ avail = MAX_LINE - display_w - pad
120
+ if len(doc) > avail > MIN_DESC_WIDTH:
121
+ doc = doc[: avail - 1] + ELLIPSIS
122
+ lines.append(f"{left}{' ' * pad}{doc}")
123
+ return "\n".join(lines)
apollo_cli/output.py ADDED
@@ -0,0 +1,150 @@
1
+ """Output formatting — JSON and Markdown modes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from datetime import datetime
8
+ from decimal import Decimal
9
+ from typing import Any
10
+
11
+ from pydantic import BaseModel
12
+ from rich.console import Console
13
+ from rich.markdown import Markdown
14
+
15
+ from apollo_cli.context import Context
16
+
17
+ # Rich console for stderr diagnostics and markdown rendering
18
+ console = Console(stderr=True)
19
+ stdout_console = Console()
20
+
21
+
22
+ def serialize(obj: Any) -> Any:
23
+ """Convert an object to a JSON-serializable structure."""
24
+ if isinstance(obj, BaseModel):
25
+ return obj.model_dump(mode="json")
26
+ if isinstance(obj, datetime):
27
+ return obj.isoformat()
28
+ if isinstance(obj, Decimal):
29
+ return float(obj)
30
+ if isinstance(obj, list):
31
+ return [serialize(item) for item in obj]
32
+ if isinstance(obj, dict):
33
+ return {k: serialize(v) for k, v in obj.items()}
34
+ return obj
35
+
36
+
37
+ def output_json(data: Any) -> None:
38
+ """Print data as JSON to stdout."""
39
+ print(json.dumps(serialize(data), indent=2, default=str))
40
+
41
+
42
+ def output_markdown(text: str) -> None:
43
+ """Render markdown text to the terminal via rich."""
44
+ stdout_console.print(Markdown(text))
45
+
46
+
47
+ def output(data: Any, *, ctx: Context, format_fn: Any = None) -> None:
48
+ """Route output through the correct formatter.
49
+
50
+ Args:
51
+ data: The data to output (Pydantic model, dict, list, etc.)
52
+ ctx: The global context (determines json vs markdown mode).
53
+ format_fn: Optional callable(data) -> str that returns markdown.
54
+ """
55
+ if ctx.json_mode:
56
+ output_json(data)
57
+ else:
58
+ md = format_fn(data) if format_fn else generic_markdown(data)
59
+ output_markdown(md)
60
+
61
+
62
+ def output_list(
63
+ *,
64
+ items: list[Any],
65
+ total: int,
66
+ page: int,
67
+ limit: int,
68
+ ctx: Context,
69
+ format_fn: Any,
70
+ resource_name: str = "Results",
71
+ ) -> None:
72
+ """Output a paginated list of items.
73
+
74
+ In JSON mode, emits ``{items, total, page, limit}``.
75
+ In markdown mode, calls *format_fn* and appends a pagination footer.
76
+ """
77
+ if ctx.json_mode:
78
+ output_json({"items": serialize(items), "total": total, "page": page, "limit": limit})
79
+ else:
80
+ md = format_fn(items, total=total, page=page)
81
+ if total > page * limit:
82
+ md += f"\n\n*Showing {len(items)} of {total} results. Use `--page {page + 1}` for next page.*"
83
+ elif total > 0:
84
+ md += f"\n\n*Showing {len(items)} of {total} results.*"
85
+ output_markdown(md)
86
+
87
+
88
+ def error(message: str, *, ctx: Context | None = None, code: str = "error", exit_code: int = 1) -> None:
89
+ """Output an error and exit.
90
+
91
+ In JSON mode, writes a JSON error object to stdout.
92
+ In markdown mode, writes to stderr.
93
+ """
94
+ if ctx and ctx.json_mode:
95
+ print(json.dumps({"error": message, "code": code}))
96
+ else:
97
+ console.print(f"[red]Error:[/red] {message}")
98
+ sys.exit(exit_code)
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Generic markdown helpers
103
+ # ---------------------------------------------------------------------------
104
+
105
+
106
+ def generic_markdown(data: Any) -> str:
107
+ """Best-effort markdown rendering for arbitrary data."""
108
+ if isinstance(data, BaseModel):
109
+ return _model_to_md(data)
110
+ if isinstance(data, dict):
111
+ return _dict_to_md(data)
112
+ if isinstance(data, list):
113
+ if not data:
114
+ return "_No results._"
115
+ return "\n\n".join(generic_markdown(item) for item in data)
116
+ return str(data)
117
+
118
+
119
+ def _model_to_md(model: BaseModel) -> str:
120
+ d = model.model_dump(exclude_none=True)
121
+ return _dict_to_md(d)
122
+
123
+
124
+ def _dict_to_md(d: dict) -> str:
125
+ lines = ["| Field | Value |", "|-------|-------|"]
126
+ for key, value in d.items():
127
+ if isinstance(value, (dict, list)):
128
+ continue # skip complex nested fields in generic view
129
+ display_key = key.replace("_", " ").title()
130
+ lines.append(f"| {display_key} | {value} |")
131
+ return "\n".join(lines)
132
+
133
+
134
+ def md_table(rows: list[dict[str, str]], headers: list[tuple[str, str]]) -> str:
135
+ """Build a markdown table from rows.
136
+
137
+ Args:
138
+ rows: List of dicts with values to display.
139
+ headers: List of (header_label, key) tuples.
140
+ """
141
+ if not rows:
142
+ return "_No results._"
143
+
144
+ header_line = "| " + " | ".join(h for h, _ in headers) + " |"
145
+ sep_line = "| " + " | ".join("---" for _ in headers) + " |"
146
+ lines = [header_line, sep_line]
147
+ for row in rows:
148
+ cells = [str(row.get(key, "")) for _, key in headers]
149
+ lines.append("| " + " | ".join(cells) + " |")
150
+ return "\n".join(lines)
@@ -0,0 +1,200 @@
1
+ # qodev-apollo-cli
2
+
3
+ Agent-friendly CLI for the Apollo.io API. Designed for AI coding agents with structured JSON output and predictable exit codes.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ pip install qodev-apollo-cli
9
+ export APOLLO_API_KEY="your_api_key"
10
+
11
+ # Install skill files into the current workspace
12
+ qodev-apollo-cli install --skills
13
+ ```
14
+
15
+ Get your API key from [Apollo.io Settings → API](https://app.apollo.io/#/settings/integrations/api).
16
+
17
+ ## Global Options
18
+
19
+ | Flag | Description |
20
+ |------|-------------|
21
+ | `--json` | Output as JSON (default: rich Markdown) |
22
+ | `--api-key` | Apollo API key (overrides APOLLO_API_KEY) |
23
+ | `--limit` | Results per page (default: 25) |
24
+ | `--page` | Page number (default: 1) |
25
+
26
+ ## Command Reference
27
+
28
+ ### contacts
29
+
30
+ | Command | Description |
31
+ |---------|-------------|
32
+ | `contacts search [--query Q] [--stage-id ID] [--linkedin-url URL]` | Search contacts |
33
+ | `contacts get ID` | Get contact details |
34
+ | `contacts create --first-name F --last-name L [--email E] [--title T] [--company C] [--linkedin-url URL]` | Create contact |
35
+ | `contacts update ID [--title T] [--label-ids IDS]` | Update contact |
36
+ | `contacts find-by-linkedin URL [--create] [--name N] [--stage-id ID]` | Find contact by LinkedIn URL |
37
+ | `contacts stages` | List all contact stages |
38
+
39
+ ### accounts
40
+
41
+ | Command | Description |
42
+ |---------|-------------|
43
+ | `accounts search [--query Q] [--domain D]` | Search companies/accounts |
44
+ | `accounts get ID` | Get account details |
45
+
46
+ ### deals
47
+
48
+ | Command | Description |
49
+ |---------|-------------|
50
+ | `deals search [--query Q] [--stage-id ID]` | Search opportunities/deals |
51
+ | `deals get ID` | Get deal details |
52
+
53
+ ### pipelines
54
+
55
+ | Command | Description |
56
+ |---------|-------------|
57
+ | `pipelines list` | List all deal pipelines |
58
+ | `pipelines get ID` | Get pipeline details |
59
+ | `pipelines stages ID` | List stages in a pipeline |
60
+
61
+ ### stages
62
+
63
+ | Command | Description |
64
+ |---------|-------------|
65
+ | `stages list` | List all contact stages |
66
+ | `stages get ID` | Get stage details |
67
+
68
+ ### enrich
69
+
70
+ | Command | Description |
71
+ |---------|-------------|
72
+ | `enrich org DOMAIN` | Enrich organization by domain (FREE - no credits used) |
73
+ | `enrich person EMAIL` | Enrich person by email (1 credit per lookup) |
74
+
75
+ ### people
76
+
77
+ | Command | Description |
78
+ |---------|-------------|
79
+ | `people search [--person-titles TITLES] [--q-organization-domains DOMAINS]` | Search people database |
80
+
81
+ ### notes
82
+
83
+ | Command | Description |
84
+ |---------|-------------|
85
+ | `notes search [--contact-id ID]` | Search notes |
86
+ | `notes create --contact-ids IDS --note TEXT` | Create a note |
87
+
88
+ ### tasks
89
+
90
+ | Command | Description |
91
+ |---------|-------------|
92
+ | `tasks search [--type TYPE] [--status STATUS]` | Search tasks |
93
+ | `tasks create --contact-ids IDS --note TEXT [--due-at DATE]` | Create a task |
94
+
95
+ ### calls
96
+
97
+ | Command | Description |
98
+ |---------|-------------|
99
+ | `calls search` | Search call activities |
100
+
101
+ ### emails
102
+
103
+ | Command | Description |
104
+ |---------|-------------|
105
+ | `emails search` | Search email activities |
106
+
107
+ ### news
108
+
109
+ | Command | Description |
110
+ |---------|-------------|
111
+ | `news search [--categories CATS]` | Search news |
112
+
113
+ ### jobs
114
+
115
+ | Command | Description |
116
+ |---------|-------------|
117
+ | `jobs search [--job-titles TITLES] [--company-domains DOMAINS]` | Search job postings |
118
+
119
+ ### usage
120
+
121
+ | Command | Description |
122
+ |---------|-------------|
123
+ | `usage` | Show API usage stats and rate limits |
124
+
125
+ ## Exit Codes
126
+
127
+ | Code | Meaning |
128
+ |------|---------|
129
+ | 0 | Success |
130
+ | 80 | Authentication error (invalid API key) |
131
+ | 81 | Rate limit exceeded |
132
+ | 82 | API error (server error, invalid request) |
133
+ | 83 | Validation error (missing required fields) |
134
+
135
+ ## JSON Output
136
+
137
+ All commands support `--json` for structured output:
138
+
139
+ ```bash
140
+ qodev-apollo-cli --json contacts search --query "engineer" | jq '.items[0].name'
141
+ ```
142
+
143
+ Paginated responses include:
144
+
145
+ ```json
146
+ {
147
+ "items": [...],
148
+ "total": 142,
149
+ "page": 1,
150
+ "limit": 25
151
+ }
152
+ ```
153
+
154
+ ## Common Patterns
155
+
156
+ ### Find and enrich a contact
157
+
158
+ ```bash
159
+ # Search by keyword
160
+ qodev-apollo-cli contacts search --query "jane smith"
161
+
162
+ # Get full details
163
+ qodev-apollo-cli contacts get <contact-id>
164
+
165
+ # Enrich person data (1 credit)
166
+ qodev-apollo-cli enrich person jane@example.com
167
+ ```
168
+
169
+ ### Pipeline management
170
+
171
+ ```bash
172
+ # List all pipelines
173
+ qodev-apollo-cli pipelines list
174
+
175
+ # Get stages in a pipeline
176
+ qodev-apollo-cli pipelines stages <pipeline-id>
177
+
178
+ # Search deals in specific stage
179
+ qodev-apollo-cli deals search --stage-id <stage-id>
180
+ ```
181
+
182
+ ### LinkedIn integration
183
+
184
+ ```bash
185
+ # Find contact by LinkedIn URL
186
+ qodev-apollo-cli contacts find-by-linkedin "https://linkedin.com/in/janesmith"
187
+
188
+ # Auto-create if not found
189
+ qodev-apollo-cli contacts find-by-linkedin "https://linkedin.com/in/janesmith" --create
190
+
191
+ # Assign to stage on creation
192
+ qodev-apollo-cli contacts find-by-linkedin "https://linkedin.com/in/janesmith" \
193
+ --create --stage-id <stage-id>
194
+ ```
195
+
196
+ ## References
197
+
198
+ - [Contact Management Workflows](references/contact-workflows.md)
199
+ - [Deal Pipeline Workflows](references/deal-workflows.md)
200
+ - [Account Enrichment Patterns](references/account-workflows.md)
@@ -0,0 +1 @@
1
+ """AI agent skill files for Apollo CLI."""
@@ -0,0 +1 @@
1
+ """Reference documentation for Apollo CLI workflows."""
@@ -0,0 +1,202 @@
1
+ # Account Enrichment Patterns
2
+
3
+ ## Overview
4
+
5
+ Apollo provides two types of enrichment:
6
+ - **Organization enrichment** (FREE - no credits used)
7
+ - **Person enrichment** (1 credit per lookup)
8
+
9
+ ## Organization Enrichment (FREE)
10
+
11
+ Enrich company data by domain without using credits:
12
+
13
+ ```bash
14
+ # Basic enrichment
15
+ qodev-apollo-cli enrich org acme.com
16
+
17
+ # JSON output for scripting
18
+ qodev-apollo-cli --json enrich org acme.com
19
+
20
+ # Extract specific fields
21
+ qodev-apollo-cli --json enrich org acme.com | jq '{name, industry, employees: .estimated_num_employees}'
22
+ ```
23
+
24
+ ### What you get (FREE):
25
+ - Company name
26
+ - Industry classification
27
+ - Employee count estimate
28
+ - Company size category
29
+ - Website URL
30
+ - Social media profiles
31
+ - Founded year
32
+ - Revenue estimates
33
+ - Technologies used
34
+
35
+ ## Account Search
36
+
37
+ Search for companies in Apollo's database:
38
+
39
+ ```bash
40
+ # Search by name
41
+ qodev-apollo-cli accounts search --query "technology"
42
+
43
+ # Search by domain
44
+ qodev-apollo-cli accounts search --domain "acme.com"
45
+
46
+ # Pagination
47
+ qodev-apollo-cli accounts search --query "software" --page 2 --limit 50
48
+ ```
49
+
50
+ ## Account Details
51
+
52
+ ```bash
53
+ # Get full account details
54
+ qodev-apollo-cli accounts get <account-id>
55
+
56
+ # Extract specific data
57
+ qodev-apollo-cli --json accounts get <account-id> | \
58
+ jq '{name, domain, industry, employees: .estimated_num_employees}'
59
+ ```
60
+
61
+ ## Bulk Enrichment
62
+
63
+ Enrich multiple organizations:
64
+
65
+ ```bash
66
+ # From a list of domains
67
+ cat domains.txt | while read domain; do
68
+ echo "Enriching $domain..."
69
+ qodev-apollo-cli --json enrich org "$domain" > "data/${domain}.json"
70
+ sleep 1 # Rate limiting
71
+ done
72
+
73
+ # Extract key fields
74
+ for file in data/*.json; do
75
+ jq '{domain: .domain, name: .name, industry: .industry, employees: .estimated_num_employees}' "$file"
76
+ done | jq -s '.'
77
+ ```
78
+
79
+ ## Person Enrichment (1 credit)
80
+
81
+ Enrich person data by email:
82
+
83
+ ```bash
84
+ # Basic enrichment
85
+ qodev-apollo-cli enrich person jane@example.com
86
+
87
+ # JSON output
88
+ qodev-apollo-cli --json enrich person jane@example.com
89
+
90
+ # Extract contact details
91
+ qodev-apollo-cli --json enrich person jane@example.com | \
92
+ jq '{name, title, company: .organization_name, linkedin: .linkedin_url}'
93
+ ```
94
+
95
+ ### What you get (1 credit):
96
+ - Full name
97
+ - Job title
98
+ - Company
99
+ - Email verification
100
+ - Phone numbers (if available)
101
+ - LinkedIn profile
102
+ - Work history
103
+ - Education
104
+
105
+ ## Rate Limits and Usage
106
+
107
+ Check your API usage:
108
+
109
+ ```bash
110
+ # View usage stats
111
+ qodev-apollo-cli usage
112
+
113
+ # JSON output for monitoring
114
+ qodev-apollo-cli --json usage | jq '{credits_available, requests_used, request_limit}'
115
+ ```
116
+
117
+ ## Enrichment Workflows
118
+
119
+ ### Pre-meeting research
120
+
121
+ ```bash
122
+ # Enrich company (free)
123
+ qodev-apollo-cli enrich org acme.com > company_info.txt
124
+
125
+ # Search for contacts at company
126
+ qodev-apollo-cli --json accounts search --domain "acme.com" | \
127
+ jq -r '.items[0].id' | \
128
+ xargs -I {} qodev-apollo-cli contacts search --account-id {}
129
+ ```
130
+
131
+ ### Lead qualification
132
+
133
+ ```bash
134
+ # Enrich organization to check company size
135
+ company_size=$(qodev-apollo-cli --json enrich org "$domain" | \
136
+ jq -r '.estimated_num_employees')
137
+
138
+ if [ "$company_size" -gt 100 ]; then
139
+ echo "Qualified: Enterprise ($company_size employees)"
140
+ else
141
+ echo "Not qualified: SMB ($company_size employees)"
142
+ fi
143
+ ```
144
+
145
+ ### Data enrichment pipeline
146
+
147
+ ```bash
148
+ #!/bin/bash
149
+ # Enrich contacts from CSV
150
+
151
+ while IFS=, read -r email domain; do
152
+ # Free org enrichment
153
+ org_data=$(qodev-apollo-cli --json enrich org "$domain")
154
+
155
+ # Paid person enrichment (1 credit)
156
+ person_data=$(qodev-apollo-cli --json enrich person "$email")
157
+
158
+ # Combine and save
159
+ echo "{\"organization\": $org_data, \"person\": $person_data}" >> enriched_data.jsonl
160
+
161
+ sleep 1 # Rate limiting
162
+ done < contacts.csv
163
+ ```
164
+
165
+ ## JSON Output Examples
166
+
167
+ ### Organization Enrichment
168
+
169
+ ```json
170
+ {
171
+ "domain": "acme.com",
172
+ "name": "Acme Corp",
173
+ "industry": "Technology",
174
+ "estimated_num_employees": 500,
175
+ "website_url": "https://acme.com",
176
+ "founded_year": 2010,
177
+ "technologies": ["AWS", "React", "Python"]
178
+ }
179
+ ```
180
+
181
+ ### Person Enrichment
182
+
183
+ ```json
184
+ {
185
+ "name": "Jane Smith",
186
+ "email": "jane@example.com",
187
+ "title": "VP Engineering",
188
+ "organization_name": "Acme Corp",
189
+ "linkedin_url": "https://linkedin.com/in/janesmith",
190
+ "phone_numbers": [
191
+ {"number": "+1-555-0100", "type": "mobile"}
192
+ ]
193
+ }
194
+ ```
195
+
196
+ ## Best Practices
197
+
198
+ 1. **Use free enrichment first**: Always enrich organizations (free) before enriching people (1 credit)
199
+ 2. **Batch operations**: Process enrichment requests in batches with rate limiting
200
+ 3. **Cache results**: Save enriched data to avoid duplicate lookups
201
+ 4. **Monitor usage**: Check `qodev-apollo-cli usage` regularly to track credit consumption
202
+ 5. **Validate domains**: Ensure domains are correctly formatted before enrichment