aiondtech 2.0.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.
aiondtech/models.py ADDED
@@ -0,0 +1,428 @@
1
+ """
2
+ Data models and exceptions for the AiondTech SDK v2.
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import List, Optional, Dict, Any
7
+ from datetime import datetime
8
+
9
+
10
+ # ============================================================================
11
+ # EXCEPTIONS
12
+ # ============================================================================
13
+
14
+ class APIError(Exception):
15
+ """Base exception for all API errors."""
16
+
17
+ def __init__(
18
+ self,
19
+ message: str,
20
+ status_code: Optional[int] = None,
21
+ response: Optional[Dict[str, Any]] = None
22
+ ):
23
+ self.message = message
24
+ self.status_code = status_code
25
+ self.response = response or {}
26
+ super().__init__(self.message)
27
+
28
+ def __str__(self):
29
+ if self.status_code:
30
+ return f"[{self.status_code}] {self.message}"
31
+ return self.message
32
+
33
+
34
+ class AuthenticationError(APIError):
35
+ """Raised when API key is invalid or missing."""
36
+ pass
37
+
38
+
39
+ class RateLimitError(APIError):
40
+ """Raised when rate limit is exceeded."""
41
+
42
+ def __init__(
43
+ self,
44
+ message: str = "Rate limit exceeded",
45
+ retry_after: Optional[int] = None,
46
+ **kwargs
47
+ ):
48
+ self.retry_after = retry_after
49
+ super().__init__(message, **kwargs)
50
+
51
+
52
+ class ValidationError(APIError):
53
+ """Raised when request validation fails."""
54
+ pass
55
+
56
+
57
+ class NotFoundError(APIError):
58
+ """Raised when a resource is not found."""
59
+ pass
60
+
61
+
62
+ class InsufficientCreditsError(APIError):
63
+ """Raised when user has insufficient API credits."""
64
+
65
+ def __init__(
66
+ self,
67
+ message: str = "Insufficient API credits",
68
+ credits_remaining: int = 0,
69
+ credits_required: int = 0,
70
+ **kwargs
71
+ ):
72
+ self.credits_remaining = credits_remaining
73
+ self.credits_required = credits_required
74
+ super().__init__(message, **kwargs)
75
+
76
+
77
+ # ============================================================================
78
+ # RESPONSE MODELS
79
+ # ============================================================================
80
+
81
+ @dataclass
82
+ class ResumeUploadResult:
83
+ """Result from uploading a resume (upload only, no parsing)."""
84
+ resume_id: int
85
+ message: str
86
+ credits_used: int = 1
87
+ _raw: Dict[str, Any] = field(default_factory=dict, repr=False)
88
+
89
+ @classmethod
90
+ def from_response(cls, data: Dict[str, Any]) -> "ResumeUploadResult":
91
+ return cls(
92
+ resume_id=data.get("resume_id"),
93
+ message=data.get("message", "Resume uploaded successfully"),
94
+ credits_used=data.get("credits_used", 1),
95
+ _raw=data
96
+ )
97
+
98
+ def __repr__(self):
99
+ return f"<ResumeUploadResult resume_id={self.resume_id}>"
100
+
101
+
102
+ @dataclass
103
+ class ResumeAnalysisResult:
104
+ """Result from uploading and analyzing a resume."""
105
+ resume_id: int
106
+ parsed_data: Dict[str, Any]
107
+ credits_used: int = 3
108
+ _raw: Dict[str, Any] = field(default_factory=dict, repr=False)
109
+
110
+ @classmethod
111
+ def from_response(cls, data: Dict[str, Any]) -> "ResumeAnalysisResult":
112
+ return cls(
113
+ resume_id=data.get("resume_id"),
114
+ parsed_data=data.get("parsed_data", {}),
115
+ credits_used=data.get("credits_used", 3),
116
+ _raw=data
117
+ )
118
+
119
+ @property
120
+ def full_name(self) -> Optional[str]:
121
+ return self.parsed_data.get("full_name")
122
+
123
+ @property
124
+ def email(self) -> Optional[str]:
125
+ return self.parsed_data.get("email")
126
+
127
+ @property
128
+ def skills(self) -> List[str]:
129
+ skills = self.parsed_data.get("skills", [])
130
+ if isinstance(skills, str):
131
+ return [s.strip() for s in skills.split(",") if s.strip()]
132
+ return skills or []
133
+
134
+ @property
135
+ def job_titles(self) -> List[str]:
136
+ titles = self.parsed_data.get("job_titles", [])
137
+ if isinstance(titles, str):
138
+ return [t.strip() for t in titles.split(",") if t.strip()]
139
+ return titles or []
140
+
141
+ @property
142
+ def total_experience(self) -> Optional[str]:
143
+ return self.parsed_data.get("total_experience")
144
+
145
+ def __repr__(self):
146
+ name = self.full_name or "Unknown"
147
+ return f"<ResumeAnalysisResult resume_id={self.resume_id} name='{name}'>"
148
+
149
+
150
+ @dataclass
151
+ class ResumeComparisonResult:
152
+ """Result from comparing a resume to a job posting."""
153
+ resume_id: int
154
+ job_id: int
155
+ comparison_score: float
156
+ comparison_reason: str
157
+ parsed_data: Optional[Dict[str, Any]] = None
158
+ matched_keywords: List[str] = field(default_factory=list)
159
+ missing_keywords: List[str] = field(default_factory=list)
160
+ suggestions: List[str] = field(default_factory=list)
161
+ credits_used: int = 3
162
+ _raw: Dict[str, Any] = field(default_factory=dict, repr=False)
163
+
164
+ @classmethod
165
+ def from_response(cls, data: Dict[str, Any]) -> "ResumeComparisonResult":
166
+ return cls(
167
+ resume_id=data.get("resume_id"),
168
+ job_id=data.get("job_id"),
169
+ comparison_score=data.get("comparison_score", 0) or data.get("ats_score", 0),
170
+ comparison_reason=data.get("comparison_reason", "") or data.get("reason", ""),
171
+ parsed_data=data.get("parsed_data"),
172
+ matched_keywords=data.get("matched_keywords", []),
173
+ missing_keywords=data.get("missing_keywords", []),
174
+ suggestions=data.get("suggestions", []),
175
+ credits_used=data.get("credits_used", 3),
176
+ _raw=data
177
+ )
178
+
179
+ @property
180
+ def is_good_match(self) -> bool:
181
+ """Returns True if score is 70% or higher."""
182
+ return self.comparison_score >= 70
183
+
184
+ @property
185
+ def match_level(self) -> str:
186
+ """Returns match level: 'excellent', 'good', 'fair', 'poor'."""
187
+ if self.comparison_score >= 85:
188
+ return "excellent"
189
+ elif self.comparison_score >= 70:
190
+ return "good"
191
+ elif self.comparison_score >= 50:
192
+ return "fair"
193
+ return "poor"
194
+
195
+ def __repr__(self):
196
+ return f"<ResumeComparisonResult resume_id={self.resume_id} score={self.comparison_score}%>"
197
+
198
+
199
+ @dataclass
200
+ class JobResult:
201
+ """Result from creating a job posting."""
202
+ job_id: int
203
+ title: str
204
+ description: str
205
+ created_by: str
206
+ created_at: Optional[str] = None
207
+ credits_used: int = 1
208
+ _raw: Dict[str, Any] = field(default_factory=dict, repr=False)
209
+
210
+ @classmethod
211
+ def from_response(cls, data: Dict[str, Any]) -> "JobResult":
212
+ return cls(
213
+ job_id=data.get("job_id"),
214
+ title=data.get("title", ""),
215
+ description=data.get("description", ""),
216
+ created_by=data.get("created_by", ""),
217
+ created_at=data.get("created_at"),
218
+ credits_used=data.get("credits_used", 1),
219
+ _raw=data
220
+ )
221
+
222
+ def __repr__(self):
223
+ return f"<JobResult job_id={self.job_id} title='{self.title}'>"
224
+
225
+
226
+ @dataclass
227
+ class ParsedResumeResult:
228
+ """Result from parsing/analyzing a resume by ID."""
229
+ resume_id: int
230
+ partner: str
231
+ parsed_data: Dict[str, Any]
232
+ credits_used: int = 2
233
+ _raw: Dict[str, Any] = field(default_factory=dict, repr=False)
234
+
235
+ @classmethod
236
+ def from_response(cls, data: Dict[str, Any]) -> "ParsedResumeResult":
237
+ return cls(
238
+ resume_id=data.get("resume_id"),
239
+ partner=data.get("partner", ""),
240
+ parsed_data=data.get("parsed_data", {}),
241
+ credits_used=data.get("credits_used", 2),
242
+ _raw=data
243
+ )
244
+
245
+ @property
246
+ def full_name(self) -> Optional[str]:
247
+ return self.parsed_data.get("full_name")
248
+
249
+ @property
250
+ def email(self) -> Optional[str]:
251
+ return self.parsed_data.get("email")
252
+
253
+ @property
254
+ def phone(self) -> Optional[str]:
255
+ return self.parsed_data.get("contact_number")
256
+
257
+ @property
258
+ def linkedin(self) -> Optional[str]:
259
+ return self.parsed_data.get("linkedin")
260
+
261
+ @property
262
+ def location(self) -> Optional[str]:
263
+ return self.parsed_data.get("location")
264
+
265
+ @property
266
+ def skills(self) -> List[str]:
267
+ skills = self.parsed_data.get("skills", [])
268
+ if isinstance(skills, str):
269
+ return [s.strip() for s in skills.split(",") if s.strip()]
270
+ return skills or []
271
+
272
+ @property
273
+ def job_titles(self) -> List[str]:
274
+ titles = self.parsed_data.get("job_titles", [])
275
+ if isinstance(titles, str):
276
+ return [t.strip() for t in titles.split(",") if t.strip()]
277
+ return titles or []
278
+
279
+ @property
280
+ def companies(self) -> List[str]:
281
+ companies = self.parsed_data.get("companies", [])
282
+ if isinstance(companies, str):
283
+ return [c.strip() for c in companies.split(",") if c.strip()]
284
+ return companies or []
285
+
286
+ @property
287
+ def education(self) -> List[str]:
288
+ edu = self.parsed_data.get("education", [])
289
+ if isinstance(edu, str):
290
+ return [e.strip() for e in edu.split(",") if e.strip()]
291
+ return edu or []
292
+
293
+ @property
294
+ def total_experience(self) -> Optional[str]:
295
+ return self.parsed_data.get("total_experience")
296
+
297
+ @property
298
+ def certifications(self) -> List[str]:
299
+ certs = self.parsed_data.get("certifications", [])
300
+ if isinstance(certs, str):
301
+ return [c.strip() for c in certs.split(",") if c.strip()]
302
+ return certs or []
303
+
304
+ def __repr__(self):
305
+ name = self.full_name or "Unknown"
306
+ return f"<ParsedResumeResult resume_id={self.resume_id} name='{name}'>"
307
+
308
+
309
+ @dataclass
310
+ class ResumeListItem:
311
+ """Single resume in a list."""
312
+ id: int
313
+ filename: str
314
+ full_name: Optional[str] = None
315
+ email: Optional[str] = None
316
+ uploaded_at: Optional[str] = None
317
+ is_parsed: bool = False
318
+
319
+ @classmethod
320
+ def from_dict(cls, data: Dict[str, Any]) -> "ResumeListItem":
321
+ return cls(
322
+ id=data.get("id"),
323
+ filename=data.get("filename", ""),
324
+ full_name=data.get("full_name"),
325
+ email=data.get("email"),
326
+ uploaded_at=data.get("uploaded_at"),
327
+ is_parsed=data.get("is_parsed", False)
328
+ )
329
+
330
+
331
+ @dataclass
332
+ class ResumeListResult:
333
+ """Result from listing resumes."""
334
+ resumes: List[Dict[str, Any]]
335
+ total: int
336
+ page: int
337
+ limit: int
338
+ has_more: bool
339
+ _raw: Dict[str, Any] = field(default_factory=dict, repr=False)
340
+
341
+ @classmethod
342
+ def from_response(cls, data: Dict[str, Any]) -> "ResumeListResult":
343
+ return cls(
344
+ resumes=data.get("resumes", []),
345
+ total=data.get("total", 0),
346
+ page=data.get("page", 1),
347
+ limit=data.get("limit", 50),
348
+ has_more=data.get("has_more", False),
349
+ _raw=data
350
+ )
351
+
352
+ def __len__(self):
353
+ return len(self.resumes)
354
+
355
+ def __iter__(self):
356
+ return iter(self.resumes)
357
+
358
+ def __repr__(self):
359
+ return f"<ResumeListResult count={len(self.resumes)} total={self.total}>"
360
+
361
+
362
+ @dataclass
363
+ class JobListItem:
364
+ """Single job in a list."""
365
+ id: int
366
+ title: str
367
+ description: str
368
+ created_at: Optional[str] = None
369
+
370
+ @classmethod
371
+ def from_dict(cls, data: Dict[str, Any]) -> "JobListItem":
372
+ return cls(
373
+ id=data.get("id"),
374
+ title=data.get("title", ""),
375
+ description=data.get("description", ""),
376
+ created_at=data.get("created_at")
377
+ )
378
+
379
+
380
+ @dataclass
381
+ class JobListResult:
382
+ """Result from listing jobs."""
383
+ jobs: List[Dict[str, Any]]
384
+ total: int
385
+ page: int
386
+ limit: int
387
+ has_more: bool
388
+ _raw: Dict[str, Any] = field(default_factory=dict, repr=False)
389
+
390
+ @classmethod
391
+ def from_response(cls, data: Dict[str, Any]) -> "JobListResult":
392
+ return cls(
393
+ jobs=data.get("jobs", []),
394
+ total=data.get("total", 0),
395
+ page=data.get("page", 1),
396
+ limit=data.get("limit", 50),
397
+ has_more=data.get("has_more", False),
398
+ _raw=data
399
+ )
400
+
401
+ def __len__(self):
402
+ return len(self.jobs)
403
+
404
+ def __iter__(self):
405
+ return iter(self.jobs)
406
+
407
+ def __repr__(self):
408
+ return f"<JobListResult count={len(self.jobs)} total={self.total}>"
409
+
410
+
411
+ @dataclass
412
+ class CreditBalance:
413
+ """Credit balance information."""
414
+ total: int
415
+ used_mtd: int
416
+ remaining: int
417
+ plan_name: str
418
+ resets_at: Optional[str] = None
419
+
420
+ @classmethod
421
+ def from_response(cls, data: Dict[str, Any]) -> "CreditBalance":
422
+ return cls(
423
+ total=data.get("total", 0),
424
+ used_mtd=data.get("used_mtd", 0),
425
+ remaining=data.get("remaining", 0),
426
+ plan_name=data.get("plan_name", ""),
427
+ resets_at=data.get("resets_at")
428
+ )
aiondtech/py.typed ADDED
File without changes
@@ -0,0 +1,285 @@
1
+ Metadata-Version: 2.4
2
+ Name: aiondtech
3
+ Version: 2.0.0
4
+ Summary: Official Python SDK for AiondTech Resume Analyser API
5
+ Author-email: AiondTech <support@aiondtech.com>
6
+ Maintainer-email: AiondTech <support@aiondtech.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://aiondtech.com
9
+ Project-URL: Documentation, https://docs.aiondtech.com
10
+ Project-URL: Repository, https://github.com/aiondtech/aiondtech-python
11
+ Project-URL: Bug Tracker, https://github.com/aiondtech/aiondtech-python/issues
12
+ Project-URL: Changelog, https://github.com/aiondtech/aiondtech-python/blob/main/CHANGELOG.md
13
+ Keywords: aiondtech,resume,cv,parser,analyzer,ats,recruitment,hr,api,sdk
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: requests>=2.25.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
+ Requires-Dist: black>=23.0.0; extra == "dev"
34
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # AiondTech Resume Analyser SDK
38
+
39
+ Official Python SDK for the [AiondTech Resume Analyser API](https://aiondtech.com).
40
+
41
+ [![PyPI version](https://badge.fury.io/py/aiondtech.svg)](https://badge.fury.io/py/aiondtech)
42
+ [![Python Versions](https://img.shields.io/pypi/pyversions/aiondtech.svg)](https://pypi.org/project/aiondtech/)
43
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install aiondtech
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ ```python
54
+ from aiondtech import ResumeAnalyser
55
+
56
+ # Initialize client
57
+ client = ResumeAnalyser(api_key="your-api-key")
58
+
59
+ # Or use environment variable
60
+ # export AIONDTECH_API_KEY="your-api-key"
61
+ client = ResumeAnalyser()
62
+
63
+ # Upload a resume (1 credit)
64
+ result = client.resumes.upload("resume.pdf")
65
+ print(f"Resume ID: {result.resume_id}")
66
+
67
+ # Upload and analyze (3 credits)
68
+ analysis = client.resumes.upload_and_analyze("resume.pdf")
69
+ print(f"Name: {analysis.full_name}")
70
+ print(f"Skills: {analysis.skills}")
71
+
72
+ # Create a job posting (1 credit)
73
+ job = client.jobs.create(
74
+ title="Senior Python Developer",
75
+ description="We are looking for an experienced Python developer..."
76
+ )
77
+ print(f"Job ID: {job.job_id}")
78
+
79
+ # Compare resume to job (3 credits)
80
+ comparison = client.matching.compare(
81
+ resume_id=result.resume_id,
82
+ job_id=job.job_id
83
+ )
84
+ print(f"Match Score: {comparison.comparison_score}%")
85
+ print(f"Reason: {comparison.comparison_reason}")
86
+
87
+ # Check credit balance (free)
88
+ balance = client.credits.balance()
89
+ print(f"Remaining: {balance['remaining_credits']}")
90
+ ```
91
+
92
+ ## API Endpoints
93
+
94
+ | Method | Endpoint | Credits | Description |
95
+ |--------|----------|---------|-------------|
96
+ | `resumes.upload()` | POST /external/upload-resume | 1 | Upload PDF |
97
+ | `resumes.upload_and_analyze()` | POST /external/upload-resume-analyze | 3 | Upload + AI parsing |
98
+ | `resumes.upload_analyze_compare()` | POST /external/upload-resume-analyze-compare | 5 | Upload + parse + compare |
99
+ | `resumes.analyze()` | POST /external/analyze-resume-by-id | 2 | Parse existing resume |
100
+ | `resumes.list()` | GET /external/list-resumes | 0 | List all resumes |
101
+ | `jobs.create()` | POST /external/create-job | 1 | Create job posting |
102
+ | `jobs.list()` | GET /external/list-jobs | 0 | List all jobs |
103
+ | `matching.compare()` | POST /external/compare-resumes | 3 | Compare resume to job |
104
+ | `credits.balance()` | GET /external/credits/balance | 0 | Check balance |
105
+ | `credits.usage()` | GET /external/credits/usage | 0 | View history |
106
+
107
+ ## Detailed Examples
108
+
109
+ ### Upload and Analyze Resume
110
+
111
+ ```python
112
+ from aiondtech import ResumeAnalyser
113
+
114
+ client = ResumeAnalyser(api_key="your-api-key")
115
+
116
+ # Upload and get structured data
117
+ analysis = client.resumes.upload_and_analyze("john_doe_resume.pdf")
118
+
119
+ print(f"Name: {analysis.full_name}")
120
+ print(f"Email: {analysis.email}")
121
+ print(f"Skills: {', '.join(analysis.skills)}")
122
+ print(f"Experience: {analysis.total_experience}")
123
+ print(f"Job Titles: {', '.join(analysis.job_titles)}")
124
+
125
+ # Access raw parsed data
126
+ print(analysis.parsed_data)
127
+ ```
128
+
129
+ ### Full Recruitment Workflow
130
+
131
+ ```python
132
+ from aiondtech import ResumeAnalyser
133
+
134
+ client = ResumeAnalyser(api_key="your-api-key")
135
+
136
+ # 1. Create a job posting
137
+ job = client.jobs.create(
138
+ title="Senior Python Developer",
139
+ description="""
140
+ Requirements:
141
+ - 5+ years Python experience
142
+ - FastAPI or Django expertise
143
+ - PostgreSQL knowledge
144
+ - AWS experience preferred
145
+ """
146
+ )
147
+
148
+ # 2. Upload and compare multiple resumes
149
+ resumes = ["candidate1.pdf", "candidate2.pdf", "candidate3.pdf"]
150
+ results = []
151
+
152
+ for resume_path in resumes:
153
+ result = client.resumes.upload_analyze_compare(
154
+ file_path=resume_path,
155
+ job_id=job.job_id
156
+ )
157
+ results.append({
158
+ "resume_id": result.resume_id,
159
+ "name": result.parsed_data.get("full_name"),
160
+ "score": result.comparison_score,
161
+ "matched": result.matched_keywords,
162
+ "missing": result.missing_keywords
163
+ })
164
+
165
+ # 3. Rank candidates by score
166
+ ranked = sorted(results, key=lambda x: x["score"], reverse=True)
167
+
168
+ for i, candidate in enumerate(ranked, 1):
169
+ print(f"{i}. {candidate['name']} - {candidate['score']}% match")
170
+ ```
171
+
172
+ ### Error Handling
173
+
174
+ ```python
175
+ from aiondtech import (
176
+ ResumeAnalyser,
177
+ AuthenticationError,
178
+ RateLimitError,
179
+ InsufficientCreditsError,
180
+ ValidationError,
181
+ NotFoundError,
182
+ )
183
+
184
+ client = ResumeAnalyser(api_key="your-api-key")
185
+
186
+ try:
187
+ result = client.resumes.upload_and_analyze("resume.pdf")
188
+ except AuthenticationError:
189
+ print("Invalid API key")
190
+ except InsufficientCreditsError as e:
191
+ print(f"Not enough credits. Remaining: {e.credits_remaining}")
192
+ except RateLimitError as e:
193
+ print(f"Rate limited. Retry after {e.retry_after} seconds")
194
+ except ValidationError as e:
195
+ print(f"Invalid input: {e.message}")
196
+ except NotFoundError:
197
+ print("Resource not found")
198
+ ```
199
+
200
+ ### Using Environment Variables
201
+
202
+ ```bash
203
+ # Set your API key
204
+ export AIONDTECH_API_KEY="your-api-key"
205
+
206
+ # Optional: Use production URL
207
+ export AIONDTECH_BASE_URL="https://api.aiondtech.com"
208
+ ```
209
+
210
+ ```python
211
+ from aiondtech import ResumeAnalyser
212
+
213
+ # Client automatically uses environment variables
214
+ client = ResumeAnalyser()
215
+ ```
216
+
217
+ ### Production vs Development
218
+
219
+ ```python
220
+ from aiondtech import ResumeAnalyser
221
+
222
+ # Development (default)
223
+ client = ResumeAnalyser(api_key="your-key")
224
+ # Uses: https://api.dev.aiondtech.com
225
+
226
+ # Production
227
+ client = ResumeAnalyser(api_key="your-key", production=True)
228
+ # Uses: https://api.aiondtech.com
229
+
230
+ # Custom URL
231
+ client = ResumeAnalyser(api_key="your-key", base_url="https://custom.api.com")
232
+ ```
233
+
234
+ ## Response Models
235
+
236
+ All API responses are converted to typed Python objects:
237
+
238
+ ```python
239
+ # ResumeUploadResult
240
+ result.resume_id # int
241
+ result.message # str
242
+ result.credits_used # int
243
+
244
+ # ResumeAnalysisResult
245
+ analysis.resume_id # int
246
+ analysis.parsed_data # dict
247
+ analysis.full_name # str (shortcut)
248
+ analysis.email # str (shortcut)
249
+ analysis.skills # List[str] (shortcut)
250
+
251
+ # ResumeComparisonResult
252
+ comparison.resume_id # int
253
+ comparison.job_id # int
254
+ comparison.comparison_score # float (0-100)
255
+ comparison.comparison_reason # str
256
+ comparison.matched_keywords # List[str]
257
+ comparison.missing_keywords # List[str]
258
+ comparison.is_good_match # bool (score >= 70)
259
+ comparison.match_level # str ("excellent", "good", "fair", "poor")
260
+
261
+ # JobResult
262
+ job.job_id # int
263
+ job.title # str
264
+ job.description # str
265
+ job.created_at # str
266
+ ```
267
+
268
+ ## Rate Limits
269
+
270
+ | Tier | Per Minute | Per Day |
271
+ |------|------------|---------|
272
+ | Starter | 50 | 500 |
273
+ | Growth | 100 | 1,000 |
274
+ | Enterprise | 500 | Unlimited |
275
+
276
+ ## Support
277
+
278
+ - **Documentation**: [https://docs.aiondtech.com](https://docs.aiondtech.com)
279
+ - **API Reference**: [https://api.aiondtech.com/docs](https://api.aiondtech.com/docs)
280
+ - **Email**: support@aiondtech.com
281
+ - **Issues**: [GitHub Issues](https://github.com/aiondtech/aiondtech-python/issues)
282
+
283
+ ## License
284
+
285
+ MIT License - see [LICENSE](LICENSE) for details.