Skip to main content

geev_unofficial_api

A synchronous Python client library for the Geev API (https://prod.geev.fr). It reproduces exactly what the Geev Android app (v8.6.2) sends on the wire - headers, HMAC request signing and multipart bodies - so it works against the live service without scraping the website.

The library is object-oriented: GeevClient is the entry point, and the network-accessing entities are User and Article. Nothing is fetched at object construction - every method performs its own HTTP request only when you call it.

from geev import GeevClient

geev = GeevClient()
geev.login("you@example.com", "s3cret")          # returns Session, stored on client

# A User handle (no network call yet) ...
user = geev.get_user("618287b1fafd81627a9ad69d")
profile = user.profile()                          # GET /v3/users/{id}  (lazy)
page    = user.articles(operation="donations")    # GET /v3/users/{id}/items

# ... and Articles
article = geev.get_article(page.items[0]["id"])
print(article.title, article.is_reservable)
article.details()                                 # GET /v3/items/{id}  (lazy)

Table of contents

  1. Install
  2. Quick examples
  3. API reference
  4. Models
  5. Errors
  6. How it matches the app
  7. Testing
  8. Project layout

1. Install

uv sync
pip install -e .
# optional, for tests
pip install -e ".[tests]"

Requires Python ≥ 3.11 and requests.


2. Quick examples

Sign in and explore a user's donations

from geev import GeevClient

geev = GeevClient()                                        # prod API by default
session = geev.login("jane@example.com", "s3cret")

user = geev.get_user("618287b1fafd81627a9ad69d")
page = user.articles(operation="donations", limit=10)
print(len(page.items), "articles; next cursor:", page.next_after)

for raw in page.items:
    print(raw["id"], raw["title"], raw.get("status"))

Search offers

geev = GeevClient()
geev.login(email, password)

results = geev.search_articles(text="chaise", limit=20)   # placement defaults to top_categories
for article in results:
    print(article.id, article.title, article.city, article.is_reservable)

Reserve / order an article

While the API has a reservation endpoint, ordering another user's item is a destructive side effect on the platform - use with care and only with accounts you control:

session = geev.login(email, password)
article = geev.get_article(ARTICLE_ID)
reservation = article.reserve()               # recipient = logged-in user
print(reservation.reservationId)

Lazy user methods

user = geev.get_user("618287b1fafd81627a9ad69d")
user.profile()          # first call does the network round-trip
user.articles()         # ...
user.carbon_summary()   # ... on demand, not at construction

3. API reference

3.1 GeevClient

geev.GeevClient(base_url=None, language="fr", token=None, session=None)

Arg Default Meaning
base_url https://prod.geev.fr/v3 also https://dev.geev.fr/v3, https://stage.geev.fr/v3
language "fr" value of the language header on every call
token None skip login if you already have an appToken
session None a pre-built Session (userId + token)

The client stores the current token on .token and the full session on .session, and passes the token to every authenticated request.

Auth

Method Endpoint Notes
check_email(email) -> bool POST /auth/email/check True if available
signup(first_name, last_name, email, password, marketing_consent=False, opted_out=False, pixel_consent=False, picture_path=None) -> Registration POST /accounts/local (multipart) returns accountId/userId; account not yet active
resend_validation(account_id) POST /accounts/{accountId}/resend-validation -
validate_account(account_id, code) -> Session POST /accounts/{accountId}/validate activates account, stores token
login(email, password) -> Session POST /auth/local/login stores token
logout() POST /auth/logout destructive: invalidates the token

signup returns a Registration; you then validate with the 6-digit code emailed by Geev:

reg = geev.signup(first_name="Jane", last_name="Doe",
                  email="jane@example.com", password="S3cret!")
geev.validate_account(reg.accountId, "123456")      # code from the email

Articles

Method Endpoint Notes
search_articles(text=None, article_type=None, states=None, categories=None, distance=None, latitude=None, longitude=None, placement="top_categories", mode="standard", limit=20, skip=1) -> List[Article] POST /search/items skip is 1-based (0 is rejected); placement is one of the server's accepted values, see below
get_article(article_id) -> Article GET /items/{articleId} wraps the payload
reserve_article(article_id, recipient_user_id=None) -> Reservation POST /reservations defaults the recipient to the logged-in user

placement values accepted by the server: home_listing, top_categories, home_exclusivities, home_near_you, home_sales, my_formula_contact_advantages, not_found, explorer, favorites_carousel. top_categories supports keyword text filters.

Messaging / contact the vendor

Method Endpoint Notes
get_conversation(conversation_id) -> Conversation GET /conversations/{conversationId} fetch thread + history
contact_article(article_id, message, dry_run=False, confirm=False) -> Conversation POST /items/{articleId}/contact starts/reuses the chat with the author
request_adoption(article_id, message, dry_run=False) -> dict POST /adoptions {itemIds, message} - expresses intent, does not reserve
list_conversations(item_id=None, with_archived=False) -> list GET /self/conversations one article summary per thread

Inbox, reserved deals, delivery

Method Endpoint Notes
get_inbox(with_archived=False) -> List[ConversationSummary] GET /self/conversations inbox: one summary per thread, with latest message + unread count
get_reserved_collections() -> List[ConversationSummary] ... inbox entries where a deal is reserved (vendor accepted)
confirm_adoption(reservation_id, *, communication_grade, punctuality_grade, feedback=None) -> AdoptionConfirmed PATCH /reservations/{id}/confirm-adoption adopter confirms the donation was delivered; closes the deal
confirm_order(article_id, *, recipient_user_id=None, firstname=None, lastname=None) -> OrderConfirmed POST /reservations buyer confirms a sale order

Users

Method Endpoint Notes
get_user(user_id) -> User no network call
get_me() -> User a User handle for the logged-in session (session.userId); no network call

3.2 User

geev.users.User is created via client.get_user(user_id) and fetches on demand. All listing/profile calls require the client to be logged in.

Method Endpoint Return
profile() GET /v3/users/{userId} raw dict (firstName, lastName, firstIntention, _links, ...)
first_name, last_name (properties) called profile() lazily
articles(operation="donations", status=None, after=None, limit=50) -> Page GET /v3/users/{userId}/items Page{items, next_after, raw}
iter_articles(operation="donations", status=None, page_size=50) -> Iterator[dict] same, cursor-following yields every item across pages
reviews(type=None, after=None, limit=20) -> List[Review] GET /v3/users/{userId}/reviews -
carbon_summary(temporality=None, light=False) -> CarbonSummary GET /v3/users/{id}/carbonSummary temporalityever, thisYear, thisMonth

operation is required by the server: donations or requests. For donations, pass status=["AVAILABLE"] to see only what can be ordered today; the app's default is ["AVAILABLE","RESERVED","GIVEN","ACQUIRED"]. The response exposes a cursor in Page.next_after (an article id) for the next page.

3.3 Article

geev.articles.Article wraps a listing/search payload. Convenience read-only properties (id, title, description, type, state, status, category, universe, picture, pictures, city, author_id, author_name, carbon_value, savings, price, stock, validated, is_reservable) never hit the network - they read the payload that created the object.

Method Endpoint Return
details() GET /v3/items/{articleId} raw dict with description, status, creditCost, donator, pictures
reserve(recipient_user_id=None) -> Reservation POST /v3/reservations destructive; defaults to logged-in user
related() -> List[Article] GET /v3/items/{id}/related similar articles
contact(message, dry_run=False, confirm=False) -> Conversation POST /v3/items/{id}/contact message the vendor; thread is fetched
request_adoption(message, dry_run=False) -> dict POST /v3/adoptions {itemIds, message}; intent, no reserve

Example - start a conversation with the vendor of an article:

article = geev.get_article(ARTICLE_ID)
conversation = article.contact("Bonjour, c'est encore disponible ?")
print(conversation.status)           # e.g. CONTACTED
send = conversation.send_message("Parfait, merci !")

If the account has several conversations without a verified phone number, the server answers 428 and the payload advertises a confirmContact link - retry with **contact(..., confirm=True)**.

3.4 Conversation

geev.conversations.Conversation wraps a messaging thread. Created via client.get_conversation(id), article.contact(...), or implicitly by client.contact_article(...); details are fetched once (populating .raw, .item_id, .status and .messages).

Field / method Meaning
conversation_id thread id
item_id, status, messages fetched fields (after fetch())
reservation_id, reservation deal attached to the thread (after fetch())
fetch() -> Conversation GET /v3/conversations/{id}
send_message(text) -> Message POST /v3/conversations/{id}/message
list_open(client, item_id=None, with_archived=False) -> list GET /v3/self/conversations

Example - complete a donation deal:

reserved = geev.get_reserved_collections()
deal = next(s for s in reserved if s.given and not s.acquired)
conversation = geev.get_conversation(deal.conversation_id)
geev.confirm_adoption(conversation.reservation_id,
                      communication_grade=5.0, punctuality_grade=5.0)

3.5 Auth flow - signup, signin, logout

The sign-up flow mirrors the app:

  1. check_email(email) - optional pre-check.
  2. signup(...) - multipart POST /accounts/local, returns Registration.
  3. validate_account(account_id, code) - POST /accounts/{accountId}/validate; the response carries the appToken = X-Geev-Token used afterwards.
  4. login(email, password) - POST /auth/local/login, same token mechanism.
  5. logout() - POST /auth/logout; invalidates the current token (subsequent requests will 401).

There is no persistence in the library: tokens live only in memory on the client object. To reuse a session across runs, capture session.appToken and session.userId yourself and build a new client with GeevClient(token=..., session=...).

There is no user-lookup-by-name endpoint in the Geev API. Users are identified solely by their userId (the last path segment of a profile URL like https://www.geev.fr/profile/<id>).


4. Models

Class Fields
Session appToken, userId, sso, userType
Registration accountId, userId
Reservation reservationId, itemId, raw
Page items, next_after, raw
Review id, grade, message, raw
CarbonSummary year, month, carbonValue, donations, adoptions, equivalences, raw
Location label, city, postalCode, latitude, longitude, radius, obfuscated
Message id, author_id, timestamp, text, read_by_receiver, raw
Conversation handle class; see §3.4
ConversationSummary inbox entry: id, title, status, reserved/given/acquired/closed, conversation_id, latest_message, unseen_count, raw
OrderConfirmed reservation_id, conversation_id, raw
AdoptionConfirmed big_savings, carbon_value, savings, raw

Every model also carries the raw server payload in .raw so you can access fields the library does not wrap yet.


5. Errors

All exceptions derive from geev.exceptions.GeevError.

Exception Raised when
BadRequest HTTP 4xx, or a malformed/unexpected body
ServerError HTTP 5xx
AuthenticationError HTTP 401/403, incl. wrong validation code
ValidationError client-side argument validation

BadRequest and its subclasses expose .status_code, .payload, .method and .url.

from geev import GeevClient, AuthenticationError

try:
    geev.login("jane@example.com", "wrong-password")
except AuthenticationError as e:
    print(e)                      # includes HTTP status and payload

6. How it matches the app

The library reproduces the exact wire behaviour of Geev 8.6.2:

  • Global headers on every request: User-Agent, x-geev-device-model, geev-app-version, geev-device, timezone, plus per-call language, X-Geev-Token (when logged in), Content-type and Accept.
  • Request signing (x-geev-timestamp + x-geev-request-signature): HMAC-SHA256 over body_bytes || timestamp_ms with the key extracted from the app's SignatureInterceptor. Only present when the request has a body. In this library the body is serialized before signing, so the signed bytes are exactly the bytes on the wire.
  • Multipart sign-up body is built manually (OkHttp byte-for-byte compatible) so signing stays exact.

Reverse-engineered from the decompiled APK; the endpoint reference doc is [RAW_API_DOC.md](docs/RAW_API_DOC.md).


7. Testing

The test suite runs against the live production API (prod.geev.fr). It is marked live; destructive operations (reserve, logout) are not executed automatically.

pytest tests/test_live.py -m live -v

Defaults for the provided test account are embedded in tests/conftest.py; override with environment variables:

Variable Default
GEEV_TEST_TOKEN the provided appToken
GEEV_TEST_USER 6a11e587ef4a89cd2c8ad9ac
GEEV_TARGET_USER 618287b1fafd81627a9ad69d

8. Project layout

./
├── docs
│   ├── API.md                 # doc for the endpoints used in this project
│   └── RAW_API_DOC.md         # doc produced by a LLM while reversing the app
├── pyproject.toml
├── README.md                  # this document
├── geev/
│   ├── __init__.py            # public exports
│   ├── _http.py               # headers, signing, multipart, transport
│   ├── exceptions.py          # error types
│   ├── models.py              # value objects (Session, Page, ...)
│   ├── auth.py                # signup / signin / logout / validate
│   ├── users.py               # User class + user operations
│   ├── articles.py            # Article class + search / reserve
│   └── client.py              # GeevClient facade
└── tests/
    ├── conftest.py            # fixtures (live API credentials)
    └── test_live.py           # live API tests

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

geev_unofficial_api-0.1.0.tar.gz (31.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

geev_unofficial_api-0.1.0-py3-none-any.whl (28.0 kB view details)

Uploaded Python 3

File details

Details for the file geev_unofficial_api-0.1.0.tar.gz.

File metadata

  • Download URL: geev_unofficial_api-0.1.0.tar.gz
  • Upload date:
  • Size: 31.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.14

File hashes

Hashes for geev_unofficial_api-0.1.0.tar.gz
Algorithm Hash digest
SHA256 7c05d5b09cc6b6c0bc0676372a09674f14aa194b25c05a5860f86dc5513fa1f1
MD5 640d9d8c91d0165b4f20cf81133ee0bb
BLAKE2b-256 5053621160e967a33b8f6a4c9e1aa9b16cc596370e6c7c3d47e5651712eeda64

See more details on using hashes here.

File details

Details for the file geev_unofficial_api-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for geev_unofficial_api-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9be683172a5035e5d808f6fe147636cce2a95a58c0032cc5479b5898cff27f7f
MD5 7a25e525b0a062eeedd57a6fd9630797
BLAKE2b-256 8f7b97d673f5016196d019eac040e143d201efefa1a699217a643f8a40b03d23

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page