Skip to main content

tapaz-python-sdk

Python tap.az Typed Async Ruff License

Typed Python client for tap.az. SMS code login, the category catalog with every property, ads, photos and the account's own ad list. Sync and async, with retry rules built so an ad is never posted twice.

Unofficial. It talks to the same GraphQL API, photo service and login service as the tap.az website, as one signed in account.

pip install tapaz-python-sdk

Sign in with an SMS code

from tapaz_sdk import TapAz

with TapAz() as tap:
    tap.auth.send_code("050 123 45 67")
    login = tap.auth.verify("050 123 45 67", "1234")
    login.user.name
    saved = login.cookie

tap.az signs in by phone only. send_code asks the login service for a 4 digit SMS code, verify turns the code into a tap.az session. Keep login.cookie and pass it next time, so the user does not need a new SMS:

tap = TapAz(saved)
tap.me().phone

The session is a plain Cookie header (tap.cookie, tap.use_cookie(...)). Cloudflare cookies such as cf_clearance and __cf_bm are dropped on the way in and on the way out, and the client never keeps them between calls.

Post an apartment

category = tap.catalog.category(635)
for prop in category.properties:
    print(prop.id, prop.name, prop.kind.value, prop.required, [(o.id, o.name) for o in prop.options])

photo = tap.photos.upload("flat-1.jpg")
ad = tap.ads.create(
    635,
    "3 room flat in Yasamal, renovated, close to the metro.",
    properties={"740": "3722", "737": 85, "812": "Elmler Akademiyasi metro", "900": True},
    price=150000,
    photo_ids=[photo.id],
)
ad.id, ad.status, ad.url

properties maps property ids to values: an option id for a collection, a number for a range, True or False for a boolean, text for a string. Before anything is sent the client loads the category and checks every value the way the tap.az form does: required properties, option ids, ranges, properties that only apply after another option is chosen (depends_on, catalog.property_options(...)), and the category rules for price, photos and title. Every problem is listed in InvalidRequestError.errors. tap.ads.validate(...) runs the same check and returns the property set without posting.

The contact phone and email come from the account unless you pass phones, email and name. contact_type="ONLY_MESSAGES" hides the phone number. New ads go to region_id 420 (Baku) unless you pass another region from tap.catalog.regions().

tap.az covers every kind of ad, so the same calls work for any leaf category: find it in tap.catalog.categories() (walk() goes through the tree).

Manage ads

from tapaz_sdk import AdTab

page = tap.ads.list(AdTab.PUBLISHED, first=20)
everything = tap.ads.all(AdTab.REJECTED)
tap.ads.get(5001).reject_reason
tap.ads.update(5001, 635, "New text", properties={"740": "3722", "737": 85, "812": "Yasamal"}, price=140000)
tap.ads.prolong(5001)
tap.ads.delete(5001)

list pages with a cursor (page.next_cursor), all follows it. delete reads the delete reasons tap.az offers for the ad and picks reason 6 (the delete_reason setting), or the first one that needs no description, unless you pass reason_id from tap.ads.delete_reasons(...). An update sends the text, price, photos, contact type and the boolean properties; the other properties are checked but only set when the ad is created.

Photos

photo = tap.photos.upload("flat.jpg")
photo = tap.photos.upload(open("flat.png", "rb"))
photo = tap.photos.upload(raw_bytes, "flat.jpg")
turned = tap.photos.rotate(photo.id, 90)

Upload before creating the ad and pass the ids in photo_ids. A rotation returns a new id.

Safety

Situation What the client does
Connection refused, nothing sent Retries, then NotSentError
Timeout, cut connection, 5xx or a broken answer on createAd, updateAd, destroyAd, prolongAds or loginUser No retry, OutcomeUnknownError. Look at ads.list() before trying again
Same failure on a read or a photo upload Retries with backoff, then TransportError. A repeated upload can only leave an unused photo
Login service answers without a known error (any status) Asks again up to login_retries times, as the service is known to fail now and then
A send that needed a retry CodeSent.retried and a note: more than one SMS may arrive, use the latest code
A code refused after a retry InvalidCodeError says the code may already be used
Timeout while sending or checking a code No retry, OutcomeUnknownError
Too many codes or wrong codes RateLimitError
Cloudflare challenge page BlockedError. The client does not solve challenges, use no proxies and sends its own User-Agent
Bad property, price, phone or id InvalidRequestError before anything is sent
Session cookie Sent to tap.az and its photo service only, never to the login service
Plain http address ConfigurationError, except localhost or allow_insecure_http

Endpoints

Namespace Methods
auth send_code verify me
catalog categories category property_options regions contact_types
ads create validate update delete delete_reasons prolong get list all
photos upload rotate
client me graphql cookie use_cookie forget_session signed_in

AsyncTapAz has the same methods, awaited. tap.graphql(query, variables, operation=..., safe=True) sends any other operation with the same session and error handling.

Errors

TapAzError is the base. HTTP answers raise APIError subclasses: BadRequestError (400), AuthenticationError (401, 403, 419 and GraphQL authorization errors), NotFoundError, RateLimitError (429), ServerError (5xx). GraphQL errors raise GraphQLError with .errors. Refused mutations raise MutationError with .errors and .codes, or BannedError and UnconfirmedError for a banned account or an unconfirmed phone or email. A wrong SMS code raises InvalidCodeError. Calls that need a session raise SessionRequiredError when there is none.

Config

Pass values in code, or set TAPAZ_COOKIE, TAPAZ_TIMEOUT, TAPAZ_MAX_RETRIES, TAPAZ_LOGIN_RETRIES, TAPAZ_DEFAULT_REGION, TAPAZ_USER_AGENT, or put them in tapaz.toml (example). Every address is a setting (TAPAZ_GRAPHQL_URL, TAPAZ_PHOTOS_URL, TAPAZ_AUTH_URL) and so is every path (TAPAZ_ENDPOINTS__PHOTO_UPLOAD=...). Messages live in messages.yaml and can be replaced with messages.use(...). More in examples.

Develop

pip install -e ".[dev]" && pytest && ruff check . && mypy src

License

MIT

Metadata

Release files for tapaz-python-sdk 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tapaz-python-sdk 0.1.0
File Size Uploaded Provenance Actions
tapaz_python_sdk-0.1.0.tar.gz 32.5 kB Without provenance Details for tapaz_python_sdk-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for tapaz-python-sdk 0.1.0
File Interpreter ABI Platform Actions
tapaz_python_sdk-0.1.0-py3-none-any.whl Python 3 none any Details for tapaz_python_sdk-0.1.0-py3-none-any.whl

Total release size: 61.5 kB

Release files / tapaz_python_sdk-0.1.0.tar.gz

Download URL tapaz_python_sdk-0.1.0.tar.gz
Size 32.5 kB
Tags Source
SHA-256 checksum
How to use checksums
bbd7d79c01b77f11c615c19b3bc16171950e076ac233dc4d3c78544672f6118c
BLAKE2b-256 checksum
How to use checksums
f891bb938c32fbc4cc86639904711dedfede482f03029bdc63349a79992d4903
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / tapaz_python_sdk-0.1.0-py3-none-any.whl

Download URL tapaz_python_sdk-0.1.0-py3-none-any.whl
Size 29.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c056296c88029911746eea86f7ceef775240246d14f8c6c10c6b192e4681b616
BLAKE2b-256 checksum
How to use checksums
63347c1d14a8077a42d4e86a9c97df9cafce07d8183c493e3bde2119fcd84ff4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Security for tapaz-python-sdk 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page