README.md

# XDK Elixir

[![Hex.pm](https://img.shields.io/hexpm/v/xdk_elixir.svg)](https://hex.pm/packages/xdk_elixir)
[![CI](https://github.com/mikehostetler/xdk-elixir/actions/workflows/ci.yml/badge.svg)](https://github.com/mikehostetler/xdk-elixir/actions/workflows/ci.yml)

Auto-generated Elixir client for the [X (Twitter) API v2](https://developer.x.com/en/docs/x-api).

> **Note:** This SDK is auto-generated by a [fork of XDK](https://github.com/mikehostetler/xdk), a Rust-based OpenAPI code generator maintained by X's developer platform team. The Elixir target is an addition to that fork. **Do not hand-edit files under `lib/` or `test/`** — all changes should be made in the generator templates and re-generated.

## Installation

Add `xdk_elixir` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:xdk_elixir, "~> 1.0"}
  ]
end
```

## Prerequisites

You need an X Developer account and API credentials. Go to the [X Developer Portal](https://developer.x.com/en/portal/dashboard) to create a project and app.

Depending on the endpoints you want to use, you'll need one or more of:

| Credential | Env Variable | Used For |
|---|---|---|
| **Bearer Token** | `BEARER_TOKEN` | App-only access (read-only public data: search, user lookup, etc.) |
| **Consumer Key + Secret** | `CONSUMER_KEY`, `SECRET_KEY` | Generating bearer tokens, OAuth 1.0a signing |
| **Access Token + Secret** | `ACCESS_TOKEN`, `ACCESS_TOKEN_SECRET` | User-context access via OAuth 1.0a (post, like, follow, DMs, etc.) |

Most read endpoints work with just a Bearer Token. Write endpoints (posting, liking, following) require OAuth 1.0a user-context auth.

## Usage

### 1. Start a Finch pool

XDK uses [Finch](https://github.com/keathley/finch) for HTTP. Add it to your supervision tree:

```elixir
children = [
  {Finch, name: MyApp.Finch}
]

Supervisor.start_link(children, strategy: :one_for_one)
```

### 2. Create a client

```elixir
# App-only (Bearer Token)
client = Xdk.new(finch: MyApp.Finch, bearer: "YOUR_BEARER_TOKEN")

# Or pass auth explicitly
client = Xdk.new(finch: MyApp.Finch, auth: {:bearer, "YOUR_BEARER_TOKEN"})
```

### 3. Make API calls

```elixir
# Look up a user
{:ok, user} = Xdk.Users.get_by_username(client, "elaborapp",
  user_fields: ["id", "name", "public_metrics"]
)

# Search recent posts
{:ok, results} = Xdk.Posts.search_recent(client,
  query: "elixir lang",
  max_results: 10,
  tweet_fields: ["id", "text", "created_at", "author_id"]
)

# Get a post by ID
{:ok, post} = Xdk.Posts.get_by_id(client, "1234567890",
  tweet_fields: ["id", "text", "author_id"]
)
```

### OAuth 1.0a (User Context)

For endpoints that require user-context authentication (posting, liking, following, DMs):

```elixir
credentials = OAuther.credentials(
  consumer_key: System.get_env("CONSUMER_KEY"),
  consumer_secret: System.get_env("SECRET_KEY"),
  token: System.get_env("ACCESS_TOKEN"),
  token_secret: System.get_env("ACCESS_TOKEN_SECRET")
)

client = Xdk.new(finch: MyApp.Finch, auth: {:oauth1, credentials})

# Post a tweet
{:ok, result} = Xdk.Posts.create(client, %{"text" => "Hello from XDK Elixir!"})

# Like a post
{:ok, _} = Xdk.Users.like_post(client, user_id, %{"tweet_id" => "1234567890"})
```

### Pagination

Use `Xdk.Paginator` to lazily page through results:

```elixir
fetch = fn token ->
  opts = [query: "elixir", max_results: 100]
  opts = if token, do: Keyword.put(opts, :pagination_token, token), else: opts
  Xdk.Posts.search_recent(client, opts)
end

# Get all items across pages as a lazy stream
Xdk.Paginator.items(fetch)
|> Enum.take(500)
```

### Streaming

For streaming endpoints (filtered stream, sample stream):

```elixir
Xdk.Stream.get_filtered_stream(client)
|> Stream.each(fn
  {:ok, tweet} -> IO.inspect(tweet)
  {:error, err} -> IO.warn("Stream error: #{inspect(err)}")
end)
|> Stream.run()
```

## Available Modules

Every X API endpoint group has a corresponding module:

| Module | Description |
|---|---|
| `Xdk.Users` | User lookup, followers, following, muting, blocking, bookmarks |
| `Xdk.Posts` | Tweet CRUD, search, counts, quotes, reposts, analytics |
| `Xdk.Lists` | List management, members, followers |
| `Xdk.Spaces` | Twitter Spaces |
| `Xdk.Communities` | Communities |
| `Xdk.CommunityNotes` | Community Notes |
| `Xdk.DirectMessages` | DM conversations and messages |
| `Xdk.Media` | Media upload |
| `Xdk.Compliance` | Compliance streams and jobs |
| `Xdk.Stream` | Filtered and sample streams |
| `Xdk.Trends` | Trending topics |
| `Xdk.Usage` | API usage metrics |
| `Xdk.Connections` | User connections |
| `Xdk.Activity` | Activity feed |
| `Xdk.AccountActivity` | Account Activity API |
| `Xdk.Webhooks` | Webhook management |
| `Xdk.News` | News |
| `Xdk.Marketplace` | Marketplace |
| `Xdk.Chat` | Chat |

## Error Handling

All API calls return `{:ok, map()}` or `{:error, error}`. Errors are structured using [Splode](https://hex.pm/packages/splode):

```elixir
case Xdk.Users.get_by_username(client, "nonexistent_user_12345") do
  {:ok, data} ->
    IO.inspect(data)

  {:error, %Xdk.Errors.RateLimitError{retry_after_ms: ms}} ->
    Process.sleep(ms)
    # retry...

  {:error, %Xdk.Errors.ApiError{status: 404}} ->
    IO.puts("User not found")

  {:error, %Xdk.Errors.TransportError{reason: reason}} ->
    IO.puts("Network error: #{inspect(reason)}")
end
```

## Configuration

Set defaults via application config so you don't have to pass them every time:

```elixir
# config/config.exs
config :xdk, :default_config,
  finch: MyApp.Finch
```

Then just pass auth when creating a client:

```elixir
client = Xdk.new(bearer: "YOUR_BEARER_TOKEN")
```

## Code Generation

This package is auto-generated from the X API's OpenAPI specification. The generator lives at [mikehostetler/xdk](https://github.com/mikehostetler/xdk) (a fork of `xdevplatform/xdk`).

To regenerate:

```bash
cd xdk && cargo run -- elixir --latest true
cp -r xdk/elixir/lib xdk/elixir/test xdk/elixir/mix.exs ../xdk-elixir/
cd ../xdk-elixir && mix deps.get && mix test
```

## License

Apache-2.0 — see [LICENSE](LICENSE) for details.