# Async Client

The Cubit SDK includes an asynchronous client for high-throughput applications.

## Installation

The async client uses the same package:

```bash
pip install cubit-api
```

## Basic Usage

```python
import asyncio
from cubit import CubitAsyncClient

async def main():
    async with CubitAsyncClient("cubit_your_api_key") as client:
        # All methods are async
        job = await client.get_job("15-1252.00")
        print(job["title"])

asyncio.run(main())
```

## Concurrent Requests

The async client shines when making multiple requests:

```python
import asyncio
from cubit import CubitAsyncClient

async def fetch_multiple_jobs():
    async with CubitAsyncClient("cubit_your_api_key") as client:
        # Fetch 5 jobs concurrently
        soc_codes = [
            "15-1252.00",
            "29-1141.00",
            "43-3031.00",
            "23-1011.00",
            "11-1021.00"
        ]
        
        jobs = await asyncio.gather(*[
            client.get_job(soc) for soc in soc_codes
        ])
        
        for job in jobs:
            print(f"{job['title']}: {job['scores']['balanced_impact_score']:+.0f}")

asyncio.run(fetch_multiple_jobs())
```

## All Methods Are Async

The async client mirrors the sync client, but all methods are coroutines:

```python
async with CubitAsyncClient("cubit_key") as client:
    # Search
    results = await client.search_jobs("nurse")
    results = await client.search_jobs_semantic("outdoor careers")
    
    # Jobs
    job = await client.get_job("15-1252.00")
    tasks = await client.get_job_tasks("15-1252.00")
    reqs = await client.get_job_requirements("15-1252.00")
    
    # Skills
    skills = await client.list_skills()
    skill = await client.get_skill("2.B.3.e")
    
    # Regions
    regions = await client.list_regions(state="CA")
    region = await client.get_region("31080")
    
    # Enterprise
    custom = await client.calculate_custom_score(weights, jobs)
    transitions = await client.find_transitions("43-3031.00")
```

## Context Manager

Always use the async context manager to ensure proper cleanup:

```python
async with CubitAsyncClient("cubit_key") as client:
    # Use client here
    pass
# Connection pool is properly closed
```

Or manually close:

```python
client = CubitAsyncClient("cubit_key")
try:
    job = await client.get_job("15-1252.00")
finally:
    await client.close()
```

## Error Handling

Exceptions work the same as the sync client:

```python
from cubit import CubitAsyncClient, NotFoundError, RateLimitError

async with CubitAsyncClient("cubit_key") as client:
    try:
        job = await client.get_job("99-9999.00")
    except NotFoundError:
        print("Job not found")
    except RateLimitError as e:
        print(f"Rate limited. Retry after {e.retry_after}s")
```

## Rate Limiting with Concurrency

When making many concurrent requests, implement rate limiting:

```python
import asyncio
from cubit import CubitAsyncClient

async def fetch_with_rate_limit(soc_codes: list[str], max_concurrent: int = 10):
    semaphore = asyncio.Semaphore(max_concurrent)
    
    async def fetch_one(client, soc):
        async with semaphore:
            return await client.get_job(soc)
    
    async with CubitAsyncClient("cubit_key") as client:
        tasks = [fetch_one(client, soc) for soc in soc_codes]
        return await asyncio.gather(*tasks, return_exceptions=True)

# Fetch 100 jobs, max 10 concurrent
soc_codes = [...]  # 100 SOC codes
results = asyncio.run(fetch_with_rate_limit(soc_codes))
```

## Integration with Web Frameworks

### FastAPI

```python
from fastapi import FastAPI, HTTPException
from cubit import CubitAsyncClient, NotFoundError
from contextlib import asynccontextmanager

# Create client on startup
cubit_client = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global cubit_client
    cubit_client = CubitAsyncClient(os.environ["CUBIT_API_KEY"])
    yield
    await cubit_client.close()

app = FastAPI(lifespan=lifespan)

@app.get("/api/jobs/{soc_code}")
async def get_job(soc_code: str):
    try:
        return await cubit_client.get_job(soc_code)
    except NotFoundError:
        raise HTTPException(404, "Job not found")
```

### aiohttp

```python
from aiohttp import web
from cubit import CubitAsyncClient

async def get_job(request):
    soc_code = request.match_info['soc_code']
    client = request.app['cubit_client']
    job = await client.get_job(soc_code)
    return web.json_response(job)

async def on_startup(app):
    app['cubit_client'] = CubitAsyncClient(os.environ["CUBIT_API_KEY"])

async def on_cleanup(app):
    await app['cubit_client'].close()

app = web.Application()
app.on_startup.append(on_startup)
app.on_cleanup.append(on_cleanup)
app.router.add_get('/jobs/{soc_code}', get_job)
```

## Performance Comparison

The async client can significantly improve throughput for multi-request workflows:

| Scenario                   | Sync Client | Async Client |
| -------------------------- | ----------- | ------------ |
| Fetch 10 jobs sequentially | \~3.0s      | \~0.4s       |
| Search + fetch + tasks     | \~0.9s      | \~0.3s       |
| Batch comparison (50 jobs) | \~15s       | \~1.5s       |

*Times are approximate and depend on network latency.*

## When to Use Async

**Use async when:**

* Making multiple API calls that can run concurrently
* Building async web applications (FastAPI, aiohttp)
* Integrating with other async libraries
* Throughput is a priority

**Use sync when:**

* Simple scripts with sequential logic
* Already in a sync codebase
* Single requests at a time
* Simpler debugging preferred


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.maidenlabs.tools/sdk-reference/async-client.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
