Metadata-Version: 2.4
Name: serpent-api
Version: 1.1.0
Summary: Official Python SDK for the Serpent SERP API (apiserpent.com) — web, news, images, videos, shopping, maps, AI rank, YouTube, Instagram, LinkedIn, eBay, Amazon, TikTok, X and Reddit
Home-page: https://apiserpent.com
Author: Serpent Co.
Author-email: support@apiserpent.com
License: MIT
Project-URL: Documentation, https://apiserpent.com/docs
Project-URL: Source, https://github.com/serpent-api/python-sdk
Keywords: serp api search google yahoo bing ddg brave news images shopping maps seo
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: project-url
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# serpent-api

Official Python SDK for the [Serpent SERP API](https://apiserpent.com): web search, news, images, videos,
shopping, Google Maps, AI rank, YouTube, Instagram, LinkedIn, eBay, Amazon, TikTok, X and Reddit — one key,
one client.

Python 3.9+, one dependency (`requests`), fully type-hinted.

```bash
pip install serpent-api
```

## Quick start

```python
import os
from serpent_api import SerpentAPI

client = SerpentAPI(os.environ["SERPENT_API_KEY"])

res = client.quick_search("best laptop 2026", engine="google", country="us")
for row in res["results"]["organic"]:
    print(row["position"], row["title"], row["url"])
```

Every method sends one `GET` with your key in the `X-API-Key` header and returns the parsed JSON body.
Keyword arguments are the API's own query parameters, spelled exactly as in the
[API reference](https://apiserpent.com/docs). Lists are sent comma-separated and booleans as `true`/`false`.

A free account includes 10 free calls shared across services. `client.catalog()` lists the products live on
the API right now; a product that is not listed there answers `404` and is not billed.

## Every method

| Family | Method | Endpoint | Priced by |
|---|---|---|---|
| Search | `quick_search(q, engine=None, **kw)` | `GET /api/search/quick` | per call |
| | `deep_search(q, engine=None, **kw)` | `GET /api/search` | per page requested (`pages`, or `num` ÷ 10 rounded up); `include_ai_mode` adds a surcharge |
| | `news(q, engine=None, **kw)` | `GET /api/news` | per call; on a Default balance under $10, per page requested |
| | `images(q, engine=None, **kw)` | `GET /api/images` | per call |
| | `videos(q, engine=None, **kw)` | `GET /api/videos` | per call |
| | `shopping(q, engine=None, **kw)` | `GET /api/shopping` | per call |
| Maps | `maps_quick(q, **kw)` | `GET /api/maps/search/quick` | per call |
| | `maps_search(q, **kw)` | `GET /api/maps/search` | `detail` band: 1–20 standard, 21–100 premium |
| | `maps_list(q, **kw)` | `GET /api/maps/search` with `detail=0` | `limit` band: up to 20 standard, 21–100 premium |
| | `maps_place(place_id= \| data_id= \| url=)` | `GET /api/maps/place` | per call |
| | `maps_reviews(place_id= \| data_id= \| url=, **kw)` | `GET /api/maps/reviews` | per call |
| AI Rank | `ai_rank(q, **kw)` | `GET /api/ai/rank` | one engine: single rate; two or more: all-engines rate |
| | `ai_rank_engine(engine, q, **kw)` | `GET /api/ai/rank/:engine` | per call |
| | `ai_rank_claude` · `ai_rank_chatgpt` · `ai_rank_gemini` · `ai_rank_perplexity` `(q, **kw)` | `GET /api/ai/rank/{claude,chatgpt,gemini,perplexity}` | per call |
| YouTube | `youtube_search(q, **kw)` | `GET /api/social/youtube/search` | per call |
| | `youtube_video(video_id=None, video_ids=None)` | `GET /api/social/youtube/video` | per call |
| | `youtube_channel(id= \| handle= \| url=, **kw)` | `GET /api/social/youtube/channel` | per call |
| | `youtube_playlist(id_or_url, **kw)` | `GET /api/social/youtube/playlist` | per call |
| Instagram | `instagram_profile(username, **kw)` (a username, @handle or URL) | `GET /api/social/instagram/profile` | `include_posts` / `include_post_details` pick the tier; other `include_*` groups +20% each |
| LinkedIn | `linkedin_profile(url_or_username, **kw)` | `GET /api/linkedin/profile` | per call; `include_*` groups +20% each |
| | `linkedin_profile_full(url_or_username)` | `GET /api/linkedin/profile/full` | per call |
| | `linkedin_company(url_or_slug)` | `GET /api/linkedin/company` | per call |
| | `linkedin_jobs(keywords, **kw)` | `GET /api/linkedin/jobs` | per call |
| | `linkedin_job(job_id)` | `GET /api/linkedin/job` | per call |
| | `linkedin_search(name, **kw)` | `GET /api/linkedin/search` | per call |
| | `linkedin_posts(url_or_username, **kw)` | `GET /api/linkedin/posts` | `limit` (per 10 posts); `include_*` groups +20% each |
| eBay | `ebay_search(q, **kw)` | `GET /api/ebay/search` | per call |
| | `ebay_item(item_id, **kw)` | `GET /api/ebay/item` | per call; `include_description=True` is the full-item rate |
| | `ebay_seller(username, **kw)` | `GET /api/ebay/seller` | per call |
| | `ebay_seller_items(username, **kw)` | `GET /api/ebay/seller/items` | per call |
| | `ebay_product_reviews(product_id, **kw)` | `GET /api/ebay/product/reviews` | per call |
| | `ebay_seller_feedback(username, **kw)` | `GET /api/ebay/seller/feedback` | per call |
| Amazon | `amazon_product(asin_or_url, **kw)` | `GET /api/amazon/product` | per call |
| | `amazon_search(q, **kw)` | `GET /api/amazon/search` | per call |
| | `amazon_bestsellers(**kw)` | `GET /api/amazon/bestsellers` | per call |
| TikTok | `tiktok_profile(username_or_url, **kw)` | `GET /api/tiktok/profile` | basic per call; any `include_*` is Enriched, priced by `limit` |
| | `tiktok_video(video_id_or_url, **kw)` | `GET /api/tiktok/video` | as above; `include_media` adds the media surcharge |
| | `tiktok_hashtag(tag, **kw)` | `GET /api/tiktok/hashtag` | Enriched, priced by `limit` |
| | `tiktok_search(q, **kw)` | `GET /api/tiktok/search` | Enriched, priced by `limit` |
| | `tiktok_music(music_id_or_url, **kw)` | `GET /api/tiktok/music` | Enriched, priced by `limit` |
| X | `x_tweet(id_or_url)` | `GET /api/x/tweet` | per call |
| | `x_profile(handle_or_url, **kw)` | `GET /api/x/profile` | per call; `hydrate=True` is 1.5× |
| Reddit | `reddit_posts(subreddit, **kw)` | `GET /api/reddit/posts` | per started 25 of `limit` |
| | `reddit_comments(subreddit, post_id, **kw)` | `GET /api/reddit/comments` | per started 25 of `limit` |
| | `reddit_search(q, **kw)` | `GET /api/reddit/search` | per call |
| | `reddit_user(username)` | `GET /api/reddit/user` | per call |
| | `reddit_subreddit(name)` | `GET /api/reddit/subreddit` | per call |
| Account | `status()` · `key_usage()` · `countries()` · `pricing()` · `catalog()` | `GET /api/status` · `/api/key/usage` · `/api/countries` · `/api/pricing` · `/api/catalog` | free |
| Jobs | `get_job(job_id)` | `GET /api/bulk/jobs/:jobId` | free |
| Any | `get(path, **params)` | any `GET /api/...` path | as that endpoint |

Current rates for every tier: `client.pricing()` or [apiserpent.com/pricing](https://apiserpent.com/pricing).

A method whose first argument is an identifier (`username_or_url`, `asin_or_url`, …) accepts a URL — sent as
`url` — or a plain identifier; you can also pass the parameter by name instead, e.g. `amazon_product(url=...)`
or `linkedin_posts(name="Jane Doe")`.

## Examples with the cURL they send

Set `SERPENT_API_KEY` in your environment first. Every cURL line is exactly the request the SDK call makes.

### Search

```python
client.quick_search("coffee shops", engine="bing", num=10)
client.deep_search("mesothelioma lawyer", engine="google", num=50)
client.news("artificial intelligence", freshness="d", sort="date")
client.images("golden gate bridge", size="large", license="cc")
client.videos("node.js tutorial", engine="ddg", duration="medium")
client.shopping("wireless earbuds", engine="google", num=10)
```

```bash
curl "https://apiserpent.com/api/search/quick?q=coffee+shops&engine=bing&num=10" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/search?q=mesothelioma+lawyer&engine=google&num=50" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/news?q=artificial+intelligence&freshness=d&sort=date" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/images?q=golden+gate+bridge&size=large&license=cc" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/videos?q=node.js+tutorial&engine=ddg&duration=medium" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/shopping?q=wireless+earbuds&engine=google&num=10" -H "X-API-Key: $SERPENT_API_KEY"
```

`engine` takes `google`, `yahoo`, `bing`, `ddg` or `brave` (Shopping: `google`, `yahoo`, `ddg` or `brave`).
Leave it out to use the endpoint's default.

### Maps

```python
client.maps_quick("coffee", location="New York, NY")
client.maps_search("dentist", location="Austin, TX", detail=20)
client.maps_list("dentist", location="Austin, TX", limit=20)
client.maps_place(place_id="ChIJN1t_tDeuEmsRUsoyG83frY4")
client.maps_reviews(place_id="ChIJN1t_tDeuEmsRUsoyG83frY4")
```

```bash
curl "https://apiserpent.com/api/maps/search/quick?q=coffee&location=New+York%2C+NY" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/maps/search?q=dentist&location=Austin%2C+TX&detail=20" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/maps/search?q=dentist&location=Austin%2C+TX&limit=20&detail=0" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/maps/place?place_id=ChIJN1t_tDeuEmsRUsoyG83frY4" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/maps/reviews?place_id=ChIJN1t_tDeuEmsRUsoyG83frY4" -H "X-API-Key: $SERPENT_API_KEY"
```

`detail` on `maps_search` sets how many places are fully enriched and picks the price band — 20 costs the
standard rate, 21 the premium rate. `maps_list` is the same endpoint with `detail=0`: the ranked list alone.

### AI Rank

Brand citation and mention checks for the `claude`, `chatgpt`, `gemini` and `perplexity` engine labels.

```python
client.ai_rank("best project management tool", domain="asana.com", engines=["claude", "gemini"])
client.ai_rank_engine("chatgpt", "best crm software", domain="hubspot.com", prompt_type="brand")
```

```bash
curl "https://apiserpent.com/api/ai/rank?q=best+project+management+tool&domain=asana.com&engines=claude%2Cgemini" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/ai/rank/chatgpt?q=best+crm+software&domain=hubspot.com&prompt_type=brand" -H "X-API-Key: $SERPENT_API_KEY"
```

### YouTube and Instagram

```python
client.youtube_search("javascript tutorial", type="video", order="viewCount", num=5)
client.youtube_video(video_ids=["dQw4w9WgXcQ", "jNQXAC9IVRw"])
client.youtube_channel(handle="@mkbhd", include_videos=True)
client.youtube_playlist("PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf", video_count=20)
client.instagram_profile("natgeo", include_posts=True)
```

```bash
curl "https://apiserpent.com/api/social/youtube/search?q=javascript+tutorial&type=video&order=viewCount&num=5" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/social/youtube/video?ids=dQw4w9WgXcQ%2CjNQXAC9IVRw" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/social/youtube/channel?handle=%40mkbhd&include_videos=true" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/social/youtube/playlist?id=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf&video_count=20" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/social/instagram/profile?username=natgeo&include_posts=true" -H "X-API-Key: $SERPENT_API_KEY"
```

YouTube `safe` takes `none`, `moderate` or `strict`.

### LinkedIn

```python
client.linkedin_profile("https://www.linkedin.com/in/williamhgates", include_company_details=True)
client.linkedin_company("microsoft")
client.linkedin_jobs("software engineer", location="United States", date_posted="past_week", remote=["remote", "hybrid"])
client.linkedin_job("4012345678")
client.linkedin_search("Satya Nadella", company="Microsoft")
client.linkedin_posts("williamhgates", limit=10)
```

```bash
curl "https://apiserpent.com/api/linkedin/profile?url=https%3A%2F%2Fwww.linkedin.com%2Fin%2Fwilliamhgates&include_company_details=true" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/linkedin/company?slug=microsoft" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/linkedin/jobs?keywords=software+engineer&location=United+States&date_posted=past_week&remote=remote%2Chybrid" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/linkedin/job?job_id=4012345678" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/linkedin/search?name=Satya+Nadella&company=Microsoft" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/linkedin/posts?username=williamhgates&limit=10" -H "X-API-Key: $SERPENT_API_KEY"
```

### eBay

```python
client.ebay_search("vintage camera", marketplace_id="EBAY_US", sort="price_asc")
client.ebay_item("335678901234", include_description=True)
client.ebay_seller_feedback("some_seller", page=2)
```

```bash
curl "https://apiserpent.com/api/ebay/search?q=vintage+camera&marketplace_id=EBAY_US&sort=price_asc" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/ebay/item?item_id=335678901234&include_description=1" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/ebay/seller/feedback?username=some_seller&page=2" -H "X-API-Key: $SERPENT_API_KEY"
```

`ebay_seller`, `ebay_seller_items` and `ebay_product_reviews` follow the same pattern.
`include_description=True` is sent as `1`, the form that endpoint expects.

### Amazon

```python
client.amazon_product("B0D1XD1ZV3", domain="com")
client.amazon_search("electric kettle", domain="co.uk", page=2)
client.amazon_bestsellers(category="electronics")
```

```bash
curl "https://apiserpent.com/api/amazon/product?asin=B0D1XD1ZV3&domain=com" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/amazon/search?q=electric+kettle&domain=co.uk&page=2" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/amazon/bestsellers?category=electronics" -H "X-API-Key: $SERPENT_API_KEY"
```

### TikTok

```python
client.tiktok_profile("tiktok", include_posts=True, limit=30)
client.tiktok_video("https://www.tiktok.com/@tiktok/video/7106594312292453675")
client.tiktok_hashtag("cooking", limit=30)
client.tiktok_search("easy recipes")
client.tiktok_music("7016547803243022337")
```

```bash
curl "https://apiserpent.com/api/tiktok/profile?username=tiktok&include_posts=true&limit=30" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/tiktok/video?url=https%3A%2F%2Fwww.tiktok.com%2F%40tiktok%2Fvideo%2F7106594312292453675" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/tiktok/hashtag?tag=cooking&limit=30" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/tiktok/search?q=easy+recipes" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/tiktok/music?music_id=7016547803243022337" -H "X-API-Key: $SERPENT_API_KEY"
```

### X (Twitter)

```python
client.x_tweet("https://x.com/jack/status/20")
client.x_profile("nasa", hydrate=True)
```

```bash
curl "https://apiserpent.com/api/x/tweet?url=https%3A%2F%2Fx.com%2Fjack%2Fstatus%2F20" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/x/profile?handle=nasa&hydrate=true" -H "X-API-Key: $SERPENT_API_KEY"
```

### Reddit

```python
client.reddit_posts("python", sort="top", t="week", limit=50)
client.reddit_comments("python", "1abcdef", limit=100, tree=True)
client.reddit_search("rust async runtime", limit=25)
client.reddit_user("spez")
client.reddit_subreddit("python")
```

```bash
curl "https://apiserpent.com/api/reddit/posts?subreddit=python&sort=top&t=week&limit=50" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/reddit/comments?subreddit=python&post_id=1abcdef&limit=100&tree=true" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/reddit/search?q=rust+async+runtime&limit=25" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/reddit/user?username=spez" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/reddit/subreddit?name=python" -H "X-API-Key: $SERPENT_API_KEY"
```

### Account and reference

```python
client.status()       # credits, free calls left, time and rate limits
client.key_usage()    # this key's own usage — free
client.countries()
client.pricing()
client.catalog()      # products live right now
client.get_job("job_9f2c41a8b7d05e6314ca7b90f2ee18d3")
```

```bash
curl "https://apiserpent.com/api/status" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/key/usage" -H "X-API-Key: $SERPENT_API_KEY"
curl "https://apiserpent.com/api/countries"
curl "https://apiserpent.com/api/pricing"
curl "https://apiserpent.com/api/catalog"
curl "https://apiserpent.com/api/bulk/jobs/job_9f2c41a8b7d05e6314ca7b90f2ee18d3" -H "X-API-Key: $SERPENT_API_KEY"
```

`get_job()` answers only while async jobs are available for your account — `status()` reports it per endpoint
under `data.limits.endpoints[…].async_available`.

## Reading a response

**SERP endpoints** take `format`: the default `full` returns `results` as a dict (`"organic"`, `"articles"`,
`"images"`, `"videos"`, …); `format="simple"` returns `results` as the row list itself.

```python
block = res["results"]
rows = block if isinstance(block, list) else block.get("organic", [])
```

A result's link is always `url`, and `publishedTime` is a date only (`"2026-03-12"`).

**Short answers are delivered, not raised.** When a call returns less than you asked for — the time limit ran
out, or fewer results exist — you still get a normal `200` with what was gathered plus a top-level `delivery`
block. A complete answer has no `delivery` key.

```python
res = client.deep_search("mesothelioma lawyer", num=100, timeout=20)
if "delivery" in res:
    d = res["delivery"]
    print(f'{d["returned"]}/{d["requested"]}: {d["note"]}')
```

`timeout` (seconds) is an API parameter: it lowers that endpoint's time budget for this call and never raises
it. The client-side limit is the constructor's `timeout`, or `request_timeout=` on one call (seconds).

## Errors

Every failure raises `SerpentAPIError` or a subclass. For an API error the attributes come straight from the
API's error envelope — branch on `code`, never on the message text.

```python
import time
from serpent_api import SerpentAPIError, SerpentRateLimitError

try:
    client.news("ai")
except SerpentRateLimitError as err:
    time.sleep(err.retry_after or 60)
except SerpentAPIError as err:
    print(err.status_code, err.code, err.message)
```

| Attribute | Meaning |
|---|---|
| `status_code` | HTTP status, or `0` when no response arrived |
| `code` | Stable error code (table below) |
| `title` | Short error title (`error` in the JSON) |
| `message` | What was wrong and what to do next |
| `param` | The parameter at fault, on a `400` |
| `retry_after` | Seconds to wait, on a `429` |
| `error_id` | Quote it to support, when present |
| `body` | The full parsed error body |

| Class | Status | `code` |
|---|---|---|
| `SerpentValidationError` | 400 | `missing_parameter`, `invalid_parameter`, `unsupported_engine`, `unsupported_country`, `unsupported_language`, `query_too_long`, `cursor_expired` |
| `SerpentAuthError` | 401 | `missing_api_key`, `invalid_api_key` |
| `SerpentPaymentRequiredError` | 402 | `insufficient_credits` — no credits and no free calls left |
| `SerpentAPIError` | 403 | `forbidden` |
| `SerpentNotFoundError` | 404 | `not_found` — unknown route, a product not live on the API, or a resource that does not exist |
| `SerpentRateLimitError` | 429 | `rate_limit_exceeded` — wait `retry_after` seconds |
| `SerpentServerError` | 500 · 502 · 503 · 504 | `internal_error`, `request_failed`, `temporarily_unavailable` (maintenance), `request_timeout` — credits are refunded |

SDK-side codes (never sent by the API): `network_error`, `client_timeout`, `invalid_response` (a body that is
not JSON) and `unexpected_redirect`.

**The SDK never repeats a call on its own.** Each method sends exactly one request, because a repeated
request is a new billed call. If you want to try again — for example after `request_failed` or
`request_timeout`, or after `retry_after` on a `429` — do it in your own code, where you decide what it costs.

## Configuration

```python
client = SerpentAPI(
    api_key=os.environ["SERPENT_API_KEY"],
    base_url="https://apiserpent.com",  # default
    timeout=120,                        # client-side limit per call, seconds (default 120)
    session=None,                       # or your own requests.Session
)

with SerpentAPI(os.environ["SERPENT_API_KEY"]) as client:   # closes the HTTP session on exit
    client.quick_search("test")
```

The SDK refuses to follow redirects, so your key is only ever sent to `base_url`. Never pass `api_key` as a
parameter — the key belongs in the constructor, which sends it in the `X-API-Key` header.

**Upgrading from 0.1:** `timeout=` on a method call is now sent to the API (it lowers the endpoint's own time
budget); use `request_timeout=` for the client-side limit. Methods no longer send `engine` unless you pass it —
the API's defaults are unchanged.

## License

MIT
