# How to Implement a New Telegram API Method in ApiWrap: A Complete Workflow

> Learn the complete workflow for adding a new Telegram API method to ApiWrap. Extend TL schema, declare wrappers, implement logic, and map responses for seamless integration.

- Repository: [Telegram Desktop/tdesktop](https://github.com/telegramdesktop/tdesktop)
- Tags: how-to-guide
- Published: 2026-04-05

---

**Adding a new Telegram API method to ApiWrap requires extending the TL schema in `api.tl`, declaring the wrapper in [`apiwrap.h`](https://github.com/telegramdesktop/tdesktop/blob/main/apiwrap.h), implementing the request logic in [`apiwrap.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/apiwrap.cpp), and mapping the MTProto response to a domain object.**

The `ApiWrap` class serves as the high-level façade between Telegram Desktop and the MTProto server, abstracting raw protocol details into Qt-friendly C++ interfaces. When implementing a new Telegram API method in ApiWrap, developers must follow a deterministic pipeline that touches the TL schema, auto-generated bindings, and the data model layer. This guide walks through the end-to-end workflow using concrete examples from the `telegramdesktop/tdesktop` codebase.

## 1. Extend the TL Schema Definition

Every API method begins in the Type Language (TL) schema. Open `Telegram/SourceFiles/mtproto/scheme/api.tl` and locate existing method definitions, such as `messages_GetWallPaper` around lines 1–10.

Add your new constructor following the exact MTProto specification:

```tl
messages_getFoo#12345678 flags:# peer:InputPeer limit:int = messages.Foo;

```

Run the CMake build to trigger the TL code generator. This automatically creates the `MTPmessages_GetFoo` C++ wrapper in the generated `mtproto` headers (e.g., [`mtproto_generated.h`](https://github.com/telegramdesktop/tdesktop/blob/main/mtproto_generated.h)). No manual edits to generated files are required.

## 2. Declare the Wrapper Method in ApiWrap

With the MTProto bindings generated, expose the method through the high-level API. Open [`Telegram/SourceFiles/apiwrap.h`](https://github.com/telegramdesktop/tdesktop/blob/main/Telegram/SourceFiles/apiwrap.h) and add a public member declaration following the pattern used by `requestWallPaper` (declared on lines 87–91).

```cpp
void requestFoo(
    const MTPInputPeer &peer,
    int limit,
    FnMut<void(const Data::FooResult&&)> done,
    Fn<void(const MTP::Error&)> fail = nullptr);

```

Include any new headers needed for the result type. Add a private member variable to track the request ID near line 797 where `_wallPaperRequestId` is declared:

```cpp
mtpRequestId _fooRequestId = 0;

```

## 3. Implement the Method in apiwrap.cpp

Open [`Telegram/SourceFiles/apiwrap.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/Telegram/SourceFiles/apiwrap.cpp) and implement the method, typically placing it near related functionality like `requestWallPaper` (implemented around lines 1085–1105).

```cpp
void ApiWrap::requestFoo(
    const MTPInputPeer &peer,
    int limit,
    FnMut<void(const Data::FooResult&&)> done,
    Fn<void(const MTP::Error&)> fail) {
    
    // Cancel any previous identical request to prevent race conditions
    if (_fooRequestId) {
        request(base::take(_fooRequestId)).cancel();
    }
    
    _fooRequestId = request(MTPmessages_GetFoo(
        MTP_flags(0),
        peer,
        MTP_int(limit)
    ))
    .done([=](const MTPmessages_Foo &result) {
        _fooRequestId = 0;
        // Convert raw MTP object to domain object
        auto foo = Data::FooResult::Create(_session, result);
        if (done) done(std::move(foo));
    })
    .fail([=](const MTP::Error &error) {
        _fooRequestId = 0;
        if (fail) fail(error);
    })
    .send();
}

```

Key implementation patterns observed in the source include:

- **Cancel-previous-request logic**: Check the stored request ID and cancel existing requests before issuing new ones
- **Request ID storage**: Assign the return value of `request()` to `_fooRequestId` for lifecycle management
- **Result conversion**: Use `Data::FooResult::Create()` to transform `MTPmessages_Foo` into internal data structures
- **Error propagation**: Forward `MTP::Error` objects through the fail callback
- **Send invocation**: Chain `.send()` to dispatch the request to the network layer

## 4. Create the Domain Data Model

If the method returns new object types, define a corresponding Data class. Create [`FooResult.h`](https://github.com/telegramdesktop/tdesktop/blob/main/FooResult.h) in the appropriate `Telegram/Data/` subdirectory following existing patterns like `Data::WallPaper`.

```cpp
class FooResult {
public:
    static FooResult Create(
        not_null<Main::Session*> session,
        const MTPmessages_Foo &mtproto) {
        
        FooResult result;
        // Parse mtproto fields into result members
        return result;
    }
    
    // Accessors for parsed data
};

```

## 5. Integrate and Test

Hook the result into the application layer by emitting signals or calling update methods on existing Data controllers. If the result influences UI components, expose **reactive streams** (`rpl::producer`) for live data sources.

Add unit tests under `Telegram/Tests/` that mock the MTP response and verify that `ApiWrap::requestFoo` invokes callbacks with correctly parsed `Data::FooResult` objects.

Rebuild the project to regenerate MTProto headers and compile the new implementation:

```bash
cmake --build out --config Debug --target Telegram

```

## Summary

- **Modify** `Telegram/SourceFiles/mtproto/scheme/api.tl` to define the new TL constructor and trigger code generation
- **Declare** the wrapper method in [`Telegram/SourceFiles/apiwrap.h`](https://github.com/telegramdesktop/tdesktop/blob/main/Telegram/SourceFiles/apiwrap.h) using Qt-friendly types and `FnMut` callbacks
- **Implement** the request logic in [`Telegram/SourceFiles/apiwrap.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/Telegram/SourceFiles/apiwrap.cpp) with proper request ID management and error handling
- **Create** domain objects in `Telegram/Data/` to encapsulate MTProto responses away from UI code
- **Test** integration through unit tests and manual verification before committing

## Frequently Asked Questions

### What is the purpose of the `_fooRequestId` member variable?

The request ID stores the handle returned by the `request()` call, allowing you to cancel in-flight requests if the same method is called again before completion. This prevents race conditions where an older network response might overwrite newer data.

### Do I need to manually edit the generated MTProto headers?

No. The TL code generator automatically creates C++ wrappers like `MTPmessages_GetFoo` when you rebuild the project after modifying `api.tl`. Manual changes to generated files will be overwritten on subsequent builds.

### Why use `FnMut<void(const Data::FooResult&&)>` instead of raw MTP types?

The `ApiWrap` layer abstracts MTProto serialization details from UI components. By converting `MTPmessages_Foo` to `Data::FooResult` in the `.done()` handler, you provide type-safe, immutable domain objects that the rest of the application can use without importing MTProto headers.

### Where should I place the Data model class for new API results?

Create new Data classes in `Telegram/Data/` following the existing directory structure. If the result relates to messages, place it near `HistoryItem` or `Message` classes. Ensure the class provides a static `Create` factory method that accepts `not_null<Main::Session*>` and the raw MTP type, mirroring patterns in `Data::WallPaper` or `Data::Chat`.