X Authority Builder — API Reference
Grow high-authority X accounts reliably & consistently. Generate expert-level tweets with Gemini AI, approve/edit them, schedule to a calendar, and dispatch volunteer magic links for hands-free publishing. Or bring your own tweets and use the scheduling, calendar, webhooks, and volunteer dispatch as a composable service.
Quick Start
'''typescript const API = "https://x-authority-builder-api.cloud.zoomgtm.com"; const TOKEN = btoa('{install_id}:{install_secret}'); // from OfficeX install webhook
const headers = { "Content-Type": "application/json", Authorization: 'Bearer {TOKEN}', };
// 1. Create a job (generates 10 tweets by default) const { data: job } = await fetch('{API}/jobs', { method: "POST", headers, body: JSON.stringify({ content_prompt: "Expert insights on B2B SaaS growth strategies", output_quantity: 10, publish_mode: "MANUAL_REVIEW", }), }).then((r) => r.json());
// 2. Poll until complete (~30-90s) let status = "PENDING"; while (status !== "COMPLETED" && status !== "FAILED") { await new Promise((r) => setTimeout(r, 5000)); const { data } = await fetch('{API}/jobs/{job.job_id}', { headers }).then( (r) => r.json() ); status = data.status; }
// 3. List generated tweets const { data: results } = await fetch( '{API}/jobs/{job.job_id}/results', { headers } ).then((r) => r.json());
// 4. Approve a tweet await fetch('{API}/results/{results.items[0].result_id}?job_id={job.job_id}', { method: "PATCH", headers, body: JSON.stringify({ approved: true }), });
// 5. Auto-schedule (AI picks optimal time) const { data: scheduled } = await fetch( '{API}/results/{results.items[0].result_id}/auto-schedule?job_id={job.job_id}', { method: "POST", headers } ).then((r) => r.json()); '''
Authentication
All authenticated endpoints require a Bearer token derived from OfficeX install credentials.
''' Authorization: Bearer <base64(install_id:install_secret)> '''
The 'install_id' and 'install_secret' are provided when a user installs the app via OfficeX. The install webhook ('POST /webhooks/officex') returns these in 'agent_context.auth_token'.
TypeScript Types
'''typescript // ── Enums ──────────────────────────────────────────────────────
type JobStatus = "PENDING" | "PROCESSING" | "COMPLETED" | "FAILED"; type PublishMode = "MANUAL_REVIEW" | "AI_REVIEW" | "AUTO_APPROVED"; type GenerationMode = "AI" | "MANUAL";
// ── Core Entities ──────────────────────────────────────────────
interface Job { job_id: string; user_id: string; status: JobStatus; created_at: string; updated_at: string; title?: string; content_prompt: string; output_quantity: number; instruction_prompt?: string; generation_mode: GenerationMode; publish_mode: PublishMode; publish_prompt?: string; schedule_prompt?: string; on_job_finish_webhook?: string; on_job_finish_email?: string; on_schedule_webhook_default?: string; on_schedule_email_default?: string; on_proof_webhook_default?: string; on_proof_email_default?: string; destination_url?: string; tracer?: string; inbox_tracer?: string; results_created: number; results_approved: number; credits_spent: number; credits_reserved?: number; reservation_id?: string; notes?: string; bookmarked?: boolean; }
interface Result { result_id: string; job_id: string; user_id: string; tweet_text: string; // ≤280 chars match_score: number; // 0–100 ai_analysis: string; approval_decision?: string; approved: boolean; rejected: boolean; user_notes?: string; scheduled_datetime?: string; scheduled: boolean; on_schedule_email?: string; on_schedule_webhook?: string; on_proof_webhook?: string; on_proof_email?: string; destination_url?: string; tracer?: string; inbox_tracer?: string; bookmarked?: boolean; }
interface Scheduled { scheduled_id: string; result_id: string; job_id: string; user_id: string; tweet_text: string; status?: 'pending' | 'completed'; scheduled_datetime: string; volunteer_magic_link?: string; on_schedule_email?: string; on_schedule_webhook?: string; on_proof_webhook?: string; on_proof_email?: string; destination_url?: string; instruction_prompt?: string; fired: boolean; fired_at?: string; proof_url?: string; proof_timestamp?: string; reported_by?: string; tracer?: string; inbox_tracer?: string; }
interface User { user_id: string; email: string; created_at: string; internal_credits: number; }
// ── Request Bodies ─────────────────────────────────────────────
interface CreateJobRequest { content_prompt: string; // REQUIRED output_quantity?: number; // 1–100, default 10 (ignored in MANUAL mode) generation_mode?: GenerationMode; // default "AI" — set "MANUAL" to skip AI generation instruction_prompt?: string; // extra context for AI publish_mode?: PublishMode; // default "MANUAL_REVIEW" publish_prompt?: string; // AI review criteria (AI_REVIEW mode) schedule_prompt?: string; // hints for auto-schedule AI on_job_finish_webhook?: string; // POST on completion on_job_finish_email?: string; // email on completion on_schedule_webhook_default?: string; // default for each result on_schedule_email_default?: string; // default for each result on_proof_webhook_default?: string; // default for each result on_proof_email_default?: string; // default for each result destination_url?: string; // X profile or thread URL tracer?: string; // your correlation ID inbox_tracer?: string; // batch grouping key notes?: string; bookmarked?: boolean; }
interface CreateResultRequest { tweet_text: string; // REQUIRED — the tweet content approved?: boolean; // default true for manual results match_score?: number; // default 100 ai_analysis?: string; // default "Manually created" scheduled_datetime?: string; // ISO 8601 on_schedule_email?: string; // overrides job default on_schedule_webhook?: string; // overrides job default on_proof_webhook?: string; // overrides job default on_proof_email?: string; // overrides job default destination_url?: string; // overrides job default tracer?: string; // overrides job default inbox_tracer?: string; // overrides job default }
// Batch variant: { results: CreateResultRequest[] }
interface UpdateResultRequest { approved?: boolean; rejected?: boolean; user_notes?: string; tweet_text?: string; // edit the tweet text match_score?: number; bookmarked?: boolean; scheduled_datetime?: string; // ISO 8601 on_schedule_email?: string; on_schedule_webhook?: string; on_proof_webhook?: string; on_proof_email?: string; destination_url?: string; }
interface UpdateScheduledRequest { tweet_text?: string; scheduled_datetime?: string; on_schedule_email?: string; on_schedule_webhook?: string; on_proof_webhook?: string; on_proof_email?: string; destination_url?: string; instruction_prompt?: string; }
interface SubmitProofRequest { proof_url: string; // REQUIRED — URL of the posted tweet reported_by?: string; }
// ── Response Wrappers ──────────────────────────────────────────
interface ApiResponse<T = unknown> { success: boolean; data?: T; error?: { code: string; message: string }; }
interface PaginatedResponse { items: T[]; next_cursor?: string; total?: number; }
// ── Webhook Event Payloads (outbound) ──────────────────────────
interface WebhookEvent { id: string; action: string; payload: T; timestamp: string; }
interface JobCompletedPayload { job_id: string; user_id: string; status: "COMPLETED" | "FAILED"; results_created: number; results_approved: number; credits_spent: number; tracer?: string; error_message?: string; }
interface ScheduleFiredPayload { scheduled_id: string; result_id: string; job_id: string; user_id: string; tweet_text: string; volunteer_magic_link: string; submit_proof_endpoint: string; destination_url?: string; tracer?: string; inbox_tracer?: string; }
interface ProofSubmittedPayload { scheduled_id: string; result_id: string; job_id: string; user_id: string; proof_url: string; proof_timestamp: string; tweet_text: string; tracer?: string; inbox_tracer?: string; } '''
Endpoint Reference
All endpoints return 'ApiResponse'. Paginated endpoints return 'ApiResponse<PaginatedResponse>'.
Health
| Method | Path | Auth |
|---|---|---|
| GET | '/health' | No |
Auth
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | '/auth/login' | No | Exchange install credentials for token + user |
| GET | '/auth/me' | Yes | Get current user |
'''typescript // Login const { data } = await fetch('{API}/auth/login', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ officex_customer_id: "...", // required on first login officex_install_id: "...", officex_install_secret: "...", }), }).then((r) => r.json()); // data = { user: User, token: string } '''
Jobs
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | '/jobs' | Yes | List jobs (paginated) |
| POST | '/jobs' | Yes | Create job (AI mode: reserves credits + generates; MANUAL mode: ready immediately) |
| GET | '/jobs/:job_id' | Yes | Get job details |
| PATCH | '/jobs/:job_id' | Yes | Update job metadata (notes, bookmarked, title) |
| DELETE | '/jobs/:job_id' | Yes | Delete job (cancels credit reservation if PENDING) |
'''typescript // Create a job const { data } = await fetch('{API}/jobs', { method: "POST", headers, body: JSON.stringify({ content_prompt: "Thought leadership on AI in healthcare", output_quantity: 20, publish_mode: "AI_REVIEW", publish_prompt: "Approve only tweets that sound authoritative and cite specifics", on_job_finish_webhook: "https://my-server.com/hooks/xab", tracer: "campaign-q1-2026", }), }).then((r) => r.json()); // data = { job_id, status: "PENDING", estimated_cost: 0.42 }
// List jobs with pagination const { data } = await fetch('{API}/jobs?limit=20&cursor={nextCursor}', { headers, }).then((r) => r.json()); // data = { items: Job[], next_cursor?: string } '''
Job lifecycle (AI mode): 'PENDING' → 'PROCESSING' → 'COMPLETED' | 'FAILED' Processing takes ~30–90 seconds depending on 'output_quantity'. Poll 'GET /jobs/:job_id' or use 'on_job_finish_webhook' to avoid polling.
Job lifecycle (MANUAL mode): Immediately 'COMPLETED' — no generation, no credits reserved. Add results via 'POST /jobs/:job_id/results'.
Results
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | '/jobs/:job_id/results' | Yes | List results for a job (paginated) |
| POST | '/jobs/:job_id/results' | Yes | Create results manually (single or batch) |
| PATCH | '/results/:result_id?job_id=X' | Yes | Update result (approve, edit, set datetime, etc.) |
| POST | '/results/:result_id/schedule?job_id=X' | Yes | Schedule an approved result (requires scheduled_datetime) |
| POST | '/results/:result_id/auto-schedule?job_id=X' | Yes | AI picks optimal datetime and schedules |
'job_id' is required on all result endpoints — pass as query param or in body.
'''typescript // Approve and edit a tweet await fetch('{API}/results/{resultId}?job_id={jobId}', { method: "PATCH", headers, body: JSON.stringify({ approved: true, tweet_text: "Edited tweet text here", }), });
// Manual schedule (set datetime first, then schedule) await fetch('{API}/results/{resultId}?job_id={jobId}', { method: "PATCH", headers, body: JSON.stringify({ scheduled_datetime: "2026-02-15T14:30:00Z" }), }); await fetch('{API}/results/{resultId}/schedule?job_id={jobId}', { method: "POST", headers, });
// Auto-schedule (AI picks time, no datetime needed) const { data: scheduled } = await fetch( '{API}/results/{resultId}/auto-schedule?job_id={jobId}', { method: "POST", headers } ).then((r) => r.json()); '''
'''typescript // Create results manually (single) const { data: result } = await fetch('{API}/jobs/{jobId}/results', { method: "POST", headers, body: JSON.stringify({ tweet_text: "Your pre-written tweet here", approved: true, scheduled_datetime: "2026-03-15T14:00:00Z", }), }).then((r) => r.json());
// Create results in batch const { data: batch } = await fetch('{API}/jobs/{jobId}/results', { method: "POST", headers, body: JSON.stringify({ results: [ { tweet_text: "First pre-written tweet", approved: true }, { tweet_text: "Second pre-written tweet", approved: true }, { tweet_text: "Third pre-written tweet", approved: true }, ], }), }).then((r) => r.json()); // batch = { items: Result[], count: 3 } '''
Preconditions for scheduling:
- Result must be 'approved: true'
- Manual schedule requires 'scheduled_datetime' set on the result
- Result must not already be 'scheduled: true'
Scheduled
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | '/scheduled' | Yes | List scheduled posts (filterable by date range) |
| GET | '/scheduled/:scheduled_id' | Yes | Get scheduled post details |
| PATCH | '/scheduled/:scheduled_id' | Yes | Update scheduled post (text, datetime, webhooks) |
| DELETE | '/scheduled/:scheduled_id' | Yes | Cancel/delete scheduled post |
'''typescript // List upcoming scheduled posts const { data } = await fetch( '{API}/scheduled?start_date=2026-02-01T00:00:00Z&end_date=2026-02-28T23:59:59Z&limit=100', { headers } ).then((r) => r.json());
// Reschedule await fetch('{API}/scheduled/{scheduledId}', { method: "PATCH", headers, body: JSON.stringify({ scheduled_datetime: "2026-02-20T09:00:00Z", tweet_text: "Updated tweet content", }), });
// Cancel await fetch('{API}/scheduled/{scheduledId}', { method: "DELETE", headers, }); '''
Send Now: If you PATCH 'scheduled_datetime' to within 2 minutes of the current time and the post hasn't fired yet, the notification fires immediately (email + webhook dispatched right away).
Calendar
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | '/calendar/month/:year/:month' | Yes | Scheduled posts grouped by day for a month |
| GET | '/calendar/week/:year/:week' | Yes | Scheduled posts grouped by day for an ISO week |
'''typescript // Get February 2026 calendar const { data } = await fetch('{API}/calendar/month/2026/2', { headers }) .then((r) => r.json()); // data = { year: 2026, month: 2, days: { "2026-02-10": [Scheduled, ...], ... }, total: 15 }
// Get ISO week 7 of 2026 const { data } = await fetch('{API}/calendar/week/2026/7', { headers }) .then((r) => r.json()); '''
Volunteer (Public — No Auth)
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | '/volunteer/:scheduled_id' | No | View volunteer task (tweet to post) |
| POST | '/volunteer/:scheduled_id/proof' | No | Submit proof of posting |
'''typescript // View task (public) const { data } = await fetch('{API}/volunteer/{scheduledId}') .then((r) => r.json()); // data = { scheduled_id, tweet_text, scheduled_datetime, destination_url?, // fired, proof_url?, instruction_prompt?, // batch?: { total, completed, current_index, next_task_id? } }
// Submit proof (public) const { data } = await fetch('{API}/volunteer/{scheduledId}/proof', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ proof_url: "https://x.com/user/status/123456789", }), }).then((r) => r.json()); // data = { proof_url, proof_timestamp, has_next_task, next_task_link? } '''
Volunteer magic links are auto-generated when a post is scheduled. Use 'inbox_tracer' on the job to group scheduled posts into sequential batches — volunteers see a progress bar and auto-navigate to the next task.
Webhooks (Inbound)
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | '/webhooks/officex' | No | OfficeX install/uninstall events |
Handled automatically by OfficeX. On 'INSTALL', the API creates a user and returns 'agent_context' with the auth token.
NocoDB Views
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | '/nocodb/views' | Yes | Get shareable NocoDB view URLs for jobs/results/scheduled |
| GET | '/nocodb/jobs/:job_id/results-view' | Yes | Get NocoDB view filtered to a specific job's results |
Credit Pricing
Credits are OfficeX ecosystem currency ($0.01 per credit).
| Quantity | Estimated Cost | USD Equivalent |
|---|---|---|
| 10 tweets | ~0.22 credits | $0.0022 |
| 25 tweets | ~0.52 credits | $0.0052 |
| 50 tweets | ~1.02 credits | $0.0102 |
| 100 tweets | ~2.02 credits | $0.0202 |
Formula: '0.1 * ceil(output_quantity / 5) + 0.02'
Additionally, each scheduled email notification costs 0.2 credits (for volunteer dispatch and proof confirmation emails via 'on_schedule_email' / 'on_proof_email').
Credits are reserved on job creation and settled after generation completes. If the job fails, the reservation is cancelled and credits are refunded.
MANUAL mode jobs cost 0 credits for job creation and result submission. You only pay for email notifications (0.2 credits each) if configured.
Outbound Webhooks
The system fires webhooks to URLs you provide. All use POST with JSON body:
JOB_COMPLETED → 'job.on_job_finish_webhook' '''json { "id": "job-complete-{job_id}-{ts}", "action": "JOB_COMPLETED", "payload": { "job_id": "...", "status": "COMPLETED", "results_created": 10, "results_approved": 8, "credits_spent": 0.22, "tracer": "my-ref" }, "timestamp": "2026-02-06T12:00:00Z" } '''
SCHEDULE_FIRED → 'scheduled.on_schedule_webhook' Fired at exact scheduled datetime. Includes 'volunteer_magic_link' and 'submit_proof_endpoint'.
PROOF_SUBMITTED → 'scheduled.on_proof_webhook' Fired when a volunteer submits proof of posting.
Error Codes
All errors return '{ success: false, error: { code, message } }'.
| HTTP | Code | Meaning |
|---|---|---|
| 400 | 'MISSING_CONTENT_PROMPT' | Job creation requires 'content_prompt' |
| 400 | 'MISSING_TWEET_TEXT' | Result creation requires 'tweet_text' |
| 400 | 'EMPTY_RESULTS' | Batch must contain at least one result |
| 400 | 'TOO_MANY_RESULTS' | Maximum 100 results per batch request |
| 400 | 'MISSING_CREDENTIALS' | No auth token or query params provided |
| 400 | 'MISSING_CUSTOMER_ID' | First-time login requires 'officex_customer_id' |
| 400 | 'MISSING_JOB_ID' | Result endpoints require 'job_id' |
| 400 | 'NOT_APPROVED' | Cannot schedule an unapproved result |
| 400 | 'NO_DATETIME' | Manual schedule requires 'scheduled_datetime' |
| 400 | 'INVALID_DATE' | Invalid year/month/week for calendar endpoint |
| 400 | 'MISSING_PROOF_URL' | Proof submission requires 'proof_url' |
| 401 | 'UNAUTHORIZED' | Missing or invalid auth token |
| 401 | 'INVALID_CREDENTIALS' | Wrong install_secret |
| 402 | 'INSUFFICIENT_CREDITS' | Not enough OfficeX credits |
| 404 | 'JOB_NOT_FOUND' | Job ID does not exist |
| 404 | 'RESULT_NOT_FOUND' | Result ID does not exist |
| 404 | 'SCHEDULED_NOT_FOUND' | Scheduled ID does not exist |
| 404 | 'USER_NOT_FOUND' | User not found for given install credentials |
| 409 | 'ALREADY_SCHEDULED' | Result is already scheduled |
Common Automation Patterns
Generate → Auto-Approve → Auto-Schedule (Full Autopilot)
'''typescript // Create with AUTO_APPROVED mode const { data: job } = await fetch('{API}/jobs', { method: "POST", headers, body: JSON.stringify({ content_prompt: "Daily insights on startup fundraising", output_quantity: 7, publish_mode: "AUTO_APPROVED", on_job_finish_webhook: "https://my-server.com/hooks/job-done", on_schedule_email_default: "team@company.com", inbox_tracer: 'week-{Date.now()}', }), }).then((r) => r.json());
// Wait for completion (or use webhook) // ...then auto-schedule all approved results: const { data: results } = await fetch( '{API}/jobs/{job.job_id}/results', { headers } ).then((r) => r.json());
for (const result of results.items.filter((r: Result) => r.approved)) { await fetch( '{API}/results/{result.result_id}/auto-schedule?job_id={job.job_id}', { method: "POST", headers } ); } '''
Webhook-Driven (No Polling)
'''typescript // 1. Create job with webhook await fetch('{API}/jobs', { method: "POST", headers, body: JSON.stringify({ content_prompt: "...", on_job_finish_webhook: "https://my-server.com/hooks/tweets-ready", tracer: "order-abc123", }), });
// 2. Your webhook handler receives JOB_COMPLETED // 3. Approve/schedule in your handler // 4. on_schedule_webhook fires at scheduled time with volunteer link // 5. on_proof_webhook fires when volunteer posts proof '''
Batch Calendar Population
'''typescript const topics = [ "AI trends in 2026", "Remote team management", "SaaS pricing strategies", ];
const jobs = await Promise.all( topics.map((topic) => fetch('{API}/jobs', { method: "POST", headers, body: JSON.stringify({ content_prompt: topic, output_quantity: 5, publish_mode: "AUTO_APPROVED", inbox_tracer: 'batch-{Date.now()}', }), }).then((r) => r.json()) ) ); // After jobs complete, auto-schedule all results — AI spaces them across days '''
Bring Your Own Tweets (MANUAL Mode)
'''typescript // 1. Create a MANUAL job (no AI generation, no credits) const { data: job } = await fetch('{API}/jobs', { method: "POST", headers, body: JSON.stringify({ content_prompt: "Q1 product launch tweets", generation_mode: "MANUAL", on_schedule_webhook_default: "https://my-server.com/hooks/post-tweet", inbox_tracer: 'launch-march-2026', }), }).then((r) => r.json()); // data = { job_id, status: "COMPLETED", generation_mode: "MANUAL", estimated_cost: 0 }
// 2. Push your pre-written tweets await fetch('{API}/jobs/{job.job_id}/results', { method: "POST", headers, body: JSON.stringify({ results: [ { tweet_text: "We just launched v2.0!", approved: true }, { tweet_text: "Here's what changed in v2.0...", approved: true }, { tweet_text: "The #1 feature request was...", approved: true }, ], }), });
// 3. Auto-schedule all results (AI picks optimal times) const { data: results } = await fetch( '{API}/jobs/{job.job_id}/results', { headers } ).then((r) => r.json());
for (const result of results.items) { await fetch( '{API}/results/{result.result_id}/auto-schedule?job_id={job.job_id}', { method: "POST", headers } ); } // Scheduling, calendar, webhooks, and volunteer flow all work the same as AI mode '''
Data Flow Summary
''' ┌──────────────────────────────────────────────────┐ │ AI MODE (default) │ MANUAL MODE │ │ │ │ │ POST /jobs ──→ credits reserved │ POST /jobs ──→ COMPLETED │ │ ──→ Gemini generates │ (0 credits) │ │ ──→ results created │ POST /jobs/:id/results │ │ │ (you push tweets) │ └───────────────────┬──────────────┴───────────────┘ │ ┌──────────────────┼──────────────────┐ MANUAL_REVIEW AI_REVIEW AUTO_APPROVED (you approve) (AI approves) (all approved) │ │ │ └────────────────┴────────────────────┘ │ PATCH /results/:id ←── approve + edit │ ┌────────────────┴────────────────┐ /schedule /auto-schedule (you set datetime) (AI picks datetime) │ │ └──────────────┬───────────────┘ │ Scheduled entry created volunteer_magic_link generated │ At scheduled datetime: → email + webhook with magic link │ Volunteer posts tweet submits proof_url │ on_proof_webhook fires ✓ '''