Mert
TÜRKSAT, uydu dünyasının büyük buluşmasında yeni işbirliklerine imza attı
Her 10 kadından biri “çikolata kisti” ile yaşıyor
Türkiye'de katılım esaslı FinTeklerin büyüklüğü 8,4 milyar dolar
Türkiye alçak yörünge uyduları için SpaceX ve Amazon gibi firmalarla masada
Nintendo, oyun fiyatlarında çift tarifeye geçiyor
Nintendo, Switch 2 oyunlarında fiziksel ve dijital sürümler için farklı fiyatlandırmaya geçiyor. İlk örnek Yoshi and the Mysterious Book olurken, dijital sürüm daha uygun fiyatla sunulacak.Meta, yapay zeka yatırımları nedeniyle yüzlerce çalışanını işten çıkarıyor
HBO’nun Harry Potter dizisinden ilk fragman geldi: Bu yıl başlayacak
HBO, merakla beklenen Harry Potter dizisini beklenenden de önce izleyicilere sunacak. Bu yıl bitmeden başlayacak diziden ilk fragman yayınlandı. İşte karşınızda yeni Harry Potter!Fetching X Timelines with API v2 Pay-Per-Use: Cost Breakdown, Caching, and the Gotchas
X’s Pay-Per-Use pricing changed how I think about API integrations. Before it existed, “display a Twitter/X timeline on a website” meant either a bloated embed widget or a $200/month API plan — neither made sense for small projects.
Pay-Per-Use flips that. You pay per request. For fetching 10 posts three times a day, the real-world cost lands around $2–3/month. That’s a different conversation.
This article covers how I implemented X API v2 timeline fetching in PHP/WordPress — including the cost math, the API structure, retweet handling, caching strategy, and two WP-Cron gotchas that will bite you if you’re not ready.
Cost Breakdown First
Before writing a line of code, it’s worth understanding what you’re paying for.
Timeline fetching is a two-step process:
| Action | Cost |
|---|---|
| User lookup (Step 1) | $0.010 / cycle |
| Per post fetched (Step 2) | $0.005 / post |
| 5 posts per cycle | $0.035 |
| 10 posts per cycle | $0.060 |
At 10 posts × 3 updates/day, the ceiling is $0.060 × 90 = $5.40/month. In practice it’s lower.
The no-duplicate-billing rule
X API does not charge twice for the same post within a 24-hour UTC window. On the second and third daily updates, you only pay for newly published posts. For accounts that don’t post constantly, the second and third updates often cost almost nothing. Real-world cost for most use cases: $2–3/month.
One important caveat: X has stated that Pay-Per-Use is a pilot program with provisional pricing that may change. Always verify current rates at developer.x.com before building anything that depends on these numbers.
Authentication
Bearer Token only. App-only OAuth 2.0. No user context needed for public timelines.
$response = wp_remote_get( $url, [
'headers' => [
'Authorization' => 'Bearer ' . $bearer_token,
],
'timeout' => 15,
] );
If you’re in WordPress, use wp_remote_get() instead of curl or file_get_contents. It respects WordPress SSL settings and timeout configuration out of the box.
Step 1: User Lookup
GET https://api.twitter.com/2/users/by/username/{username}
You need the numeric user ID to call the timeline endpoint. This call costs $0.010.
$url = 'https://api.twitter.com/2/users/by/username/' . rawurlencode( $username );
$response = wp_remote_get( $url, [ 'headers' => $headers ] );
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$user_id = $data['data']['id'];
Note: use rawurlencode() on the username, not urlencode(). The latter encodes spaces as +, which breaks the endpoint.
Step 2: Timeline Fetch
GET https://api.twitter.com/2/users/{id}/tweets
The expansions and field parameters are where most of the complexity lives.
$params = http_build_query( [
'max_results' => 10,
'tweet.fields' => 'created_at,entities,attachments,referenced_tweets',
'expansions' => 'attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id',
'media.fields' => 'url,preview_image_url,type,variants,width,height',
'user.fields' => 'name,profile_image_url,username',
'exclude' => 'replies', // optional: hide replies
] );
$url = 'https://api.twitter.com/2/users/' . $user_id . '/tweets?' . $params;
Important: max_results has a minimum of 5. Requesting fewer than 5 returns an API error. Plan your display logic accordingly.
Retweet Handling (The Tricky Part)
When a retweet appears in the timeline, the API returns a reference to the original post — not the content itself. This trips a lot of people up.
The fix: include referenced_tweets.id in your expansions. The original post data will appear in includes.tweets in the response.
$tweets = $data['data']; // timeline posts
$includes = $data['includes'] ?? []; // expanded data
foreach ( $tweets as $tweet ) {
$ref_tweets = $tweet['referenced_tweets'] ?? [];
foreach ( $ref_tweets as $ref ) {
if ( $ref['type'] === 'retweeted' ) {
// find the original in includes.tweets
$original = array_filter(
$includes['tweets'] ?? [],
fn( $t ) => $t['id'] === $ref['id']
);
$original = array_values( $original )[0] ?? null;
}
if ( $ref['type'] === 'quoted' ) {
// same lookup, different type
}
}
}
Type values to know:
-
retweeted— standard retweet -
quoted— quote tweet -
replied_to— reply (usually excluded via theexcludeparam)
Media attached to referenced tweets is also in includes.media, keyed by media_key. You’ll need to cross-reference tweet.attachments.media_keys against includes.media to render images and video.
Caching Strategy: Stale-While-Revalidate
Never call the API on every page load. It defeats the purpose of caching and burns through your credits.
The pattern I use: WordPress Transients + Stale-While-Revalidate (SWR).
function get_timeline_data() {
$cached = get_transient( 'xtl_timeline_data' );
if ( $cached !== false ) {
$age = time() - $cached['last_updated'];
if ( $age > 12 * HOUR_IN_SECONDS ) {
// Stale but present: serve it, refresh in background
wp_schedule_single_event( time(), 'xtl_swr_update_event' );
}
return $cached;
}
// No cache: schedule a fetch, return nothing for now
wp_schedule_single_event( time(), 'xtl_swr_update_event' );
return false;
}
The tradeoff: first-time visitors see nothing until the background fetch completes (usually a few seconds). Every subsequent visitor gets instant cached content. For most use cases this is acceptable.
Store your fetched data with a TTL long enough that it won’t expire between scheduled updates:
set_transient( 'xtl_timeline_data', $data, DAY_IN_SECONDS );
Two WP-Cron Gotchas
1. DST drift with wp_schedule_event('daily')
WordPress’s daily interval is a fixed 86400 seconds. During daylight saving time transitions, this causes scheduled events to drift by an hour. If you need updates at specific times, use wp_schedule_single_event() and re-schedule after each run:
// After executing, schedule the next occurrence
$next = strtotime( 'tomorrow ' . $time_str, current_time( 'timestamp' ) );
wp_schedule_single_event(
$next,
'xtl_cron_update_event',
[ [ 'time_str' => $time_str ] ]
);
This keeps the scheduled time accurate regardless of DST changes.
2. wp_clear_scheduled_hook() doesn’t remove argument-keyed events
If you’re scheduling cron events with arguments (as above), wp_clear_scheduled_hook() will silently fail to remove them. Old events accumulate every time you save settings.
The fix: iterate _get_cron_array() and unschedule explicitly.
function clear_cron_events() {
$crons = _get_cron_array();
foreach ( (array) $crons as $timestamp => $cron ) {
if ( ! isset( $cron['xtl_cron_update_event'] ) ) {
continue;
}
foreach ( $cron['xtl_cron_update_event'] as $data ) {
wp_unschedule_event(
$timestamp,
'xtl_cron_update_event',
$data['args'] ?? []
);
}
}
}
Call this before registering new cron events whenever settings change.
Rate Limit Handling
When you hit a 429, back off for at least an hour. A simple Transient-based cooldown works well:
if ( 429 === wp_remote_retrieve_response_code( $response ) ) {
set_transient( 'xtl_api_cooldown', true, HOUR_IN_SECONDS );
return new WP_Error( 'rate_limited', 'X API rate limit exceeded.' );
}
Check for the cooldown before every API call:
if ( get_transient( 'xtl_api_cooldown' ) ) {
return; // skip until cooldown clears
}
Key Takeaways
On cost: The no-duplicate-billing rule makes Pay-Per-Use more affordable than the raw numbers suggest. Second and third daily updates often cost almost nothing for low-volume accounts.
On retweets: The most common implementation mistake. Always use expansions=referenced_tweets.id and look up the original in includes.tweets. Don’t assume the content is in data.
On caching: SWR is the right pattern for external API data in WordPress. Serve stale content immediately and refresh in the background — don’t make visitors wait.
On WP-Cron: Two silent failure modes that are easy to miss: DST drift with daily interval, and argument-keyed events not being removed by wp_clear_scheduled_hook().
I packaged this implementation into two WordPress plugins if you’d rather skip the plumbing:
- X Timeline — displays a configurable timeline feed with retweet support, filters, and auto-update scheduling
- X Pinned Post — fetches only the pinned post from an account, at a lower API cost per cycle
ikka.inc — design and web production studio based in Japan.
Zero Downtime Cloud Migration: The 6-Phase Playbook
Phase 1 is never ‘lift and shift.’ Here’s the framework that keeps production stable throughout the entire move.
After leading migrations for organizations ranging from 200 to 4,000 engineers, I’ve distilled it into 6 phases that keep production alive and teams sane.
Phase 1: Discovery & Dependency Mapping
Before touching a single VM, audit everything.
-
Inventory all services: apps, databases, middleware, integrations
-
Map inter service dependencies including the undocumented ones (ask the engineers who’ve been there longest)
-
Tag everything by criticality, data sensitivity, migration complexity
-
Identify the “spiders in the web” services everything else depends on
Tools: AWS Application Discovery Service, Cloudamize, or a structured spreadsheet + interviews.
Phase 2: Cloud Foundation & Landing Zone
Build the platform before you migrate anything.
-
Set up your VPC architecture (hub spoke or flat decide now)
-
Implement IAM roles, SCPs, and guardrails
-
Deploy centralized logging, monitoring, and alerting
-
Establish your IaC standard (Terraform modules, Pulumi stacks — standardize before day one)
-
Set cost budgets and alerts BEFORE anything runs
Never skip this. Teams that migrate first and “sort out governance later” always regret it.
Phase 3: Pilot Migration (Noncritical workloads)
Start small. Build confidence.
-
Choose a non-critical, low traffic service
-
Run full migration lifecycle: move → validate → monitor → optimize
-
Document everything. This becomes your runbook for every subsequent migration
-
Identify gaps in tooling and process while the stakes are low
Phase 4: Wave Based Migration
Organize workloads into migration waves by complexity:
-
Wave 1: Stateless apps (easiest)
-
Wave 2: Stateful apps with managed DB alternatives
-
Wave 3: Complex integrations and legacy services
-
Wave 4 (optional): Services that require refactoring before migration
Run each wave over 2–4-week sprints. Include a 2-week stabilization window after each wave before proceeding.
Phase 5: Cutover & Traffic Management
Zero-downtime means dual running, not big-bang.
-
Use feature flags and DNS-based traffic shifting (Route 53 weighted routing or equivalent)
-
Implement a circuit breaker to instantly roll back traffic if error rates spike
-
Keep on prem running in parallel for 30–60 days post cutover
-
Don’t decommission anything until you’ve run at least one full billing cycle on cloud
Phase 6: Optimize & Decommission
The migration isn’t over when the apps are running.
-
Right-size instances based on real usage data (wait at least 2 weeks)
-
Implement autoscaling where you were running fixed capacity
-
Set up FinOps dashboards cloud spend will be surprising at first
-
Decommission on prem systematically, not in a rush
-
Run a migration retrospective with every team involved
The Rule I Never Break
No service goes to production without observability in place (logs, metrics, traces), a documented runbook, an on-call rotation assigned, and a tested rollback procedure.
Save for your next migration kickoff




