API quickstart
The full contract is openapi.json (OpenAPI 3.1). This page
gets you from zero to a verified webhook in about ten minutes, using only
curl. Policy details (rate limits, errors, idempotency, delivery
guarantees, versioning) live in policies.md.
Base URL: https://api.feedigy.com/v1. Every request needs a bearer token.
Every response carries a request_id for support tracing — in the error
body on failures, in the X-Request-Id header on successes. Keep it;
support can trace any call from it.
1. Create a token
In the app, open Settings → API (/settings?tab=api), give the token a
name, tick the abilities it needs (feeds:read, posts:write, …), and copy
the bearer. It is shown once — only the last four characters are stored
for the Settings list. With nothing ticked the token gets the read abilities
explicitly; there is no wildcard.
export API=https://api.feedigy.com/v1export TOKEN=your-token-here2. Check the account
curl -s $API/me -H "Authorization: Bearer $TOKEN"{ "data": { "id": 7, "email": "you@example.com", "plan": "pro", "limits": { "api_requests_per_minute": 180 } } }limits.api_requests_per_minute always matches the X-RateLimit-Limit
response header (unlimited plans report the 3000 protective ceiling, not
null). A plan without API access answers 403 here, not 429.
3. List feeds
curl -s "$API/feeds?status=active&per_page=5" -H "Authorization: Bearer $TOKEN"{ "data": [ … ], "meta": { "per_page": 5, "next_cursor": "eyJ…", "has_more": true } }Pagination is cursor-based: pass meta.next_cursor as ?cursor= for the
next page. per_page defaults to 25 and caps at 100. Unknown query keys are
a 422, never silently ignored — if you mistype a filter you hear about it.
4. Create a post (idempotent)
Writes require Content-Type: application/json and, on POST/PATCH, an
Idempotency-Key header (1–255 printable ASCII characters, kept 24 h).
Replaying a key returns the original response without writing again;
automation platforms retry aggressively, so generate one key per logical
operation (a UUID works) and reuse it across retries:
export KEY=$(php -r 'echo Ramsey\Uuid\Uuid::uuid4()->toString();' 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())')curl -s -X POST $API/posts \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: $KEY" \ -d '{"feed_id": 12, "title": "Hello, API", "status": "draft"}' -D -A 201 answers with the post and a Location header. Send the exact same
request again and you get the same 201 with the same body — one post, not
two. Send a different body under the same key and you get a 422: a fresh
logical operation needs a fresh key.
status is draft, scheduled (with a scheduled_at timestamp carrying
an explicit offset), or published. Unknown body fields are a 422.
5. Receive a post.published webhook
In Settings → API, add an endpoint: your public HTTPS URL subscribed to
post.published, and copy the one-time secret. Publish the draft from step
4 (in the app, or PATCH /v1/posts/{id} with {"status":"published"}) and
your receiver gets:
POST /your-path Content-Type: application/jsonX-Feedigy-Event: post.publishedX-Feedigy-Delivery: <per-endpoint delivery id, an integer>X-Feedigy-Timestamp: <unix seconds>X-Feedigy-Signature: t=<ts>,v1=<hex HMAC-SHA256>{ "event": "post.published", "event_id": "<publication episode UUID>", "version": 1, "occurred_at": "2026-10-09T…Z", "data": { "post": { "id": 99, "title": "Hello, API", … }, "feed": { "id": 12, "title": "…", … } } }Verify before parsing: HMAC-SHA256 over <timestamp>.<exact raw body>
with your secret, lowercase hex, constant-time compare; reject timestamps
outside a 5-minute window; store X-Feedigy-Delivery ids — delivery is
at-least-once, so a retry repeats the id with the same body. During the
24 h after a secret rotation both the current and the previous signature
are sent (t=<ts>,v1=<current>,v1=<previous>); accept either.
$raw = file_get_contents('php://input');$timestamp = null;$signatures = [];foreach (explode(',', $_SERVER['HTTP_X_FEEDIGY_SIGNATURE'] ?? '') as $part) { [$k, $v] = array_pad(explode('=', $part, 2), 2, ''); if ($k === 't') { $timestamp = $v; } elseif ($k === 'v1' && $v !== '') { $signatures[] = $v; // after a rotation there are two; accept either }}$okAge = $timestamp !== null && abs(time() - (int) $timestamp) <= 300;$expected = hash_hmac('sha256', ($timestamp ?? '').'.'.$raw, $secret);$okSig = false;foreach ($signatures as $sig) { if (strlen($sig) === strlen($expected) && hash_equals($expected, $sig)) { $okSig = true; }}if (! $okAge || ! $okSig) { http_response_code(401); exit;}// …then dedupe on X-Feedigy-Delivery and process $raw…(Accept any v1= value in the header — after a rotation there are two.
The reference consumer in deploy/webhook-reference-consumer shows the
same check in Node.)
The other two events work identically: publisher.run.failed (a scheduled
auto-publisher run failed terminally) and channel.token_expired (a
connected account needs reconnecting). Payload shapes are
WebhookPostPublished, WebhookPublisherRunFailed and
WebhookChannelTokenExpired in openapi.json.
Next steps
- API Policies — rate limits and tiers, the error envelope, idempotency semantics, delivery guarantees, versioning.
- OpenAPI spec — the machine-readable contract.
- API Reference — every endpoint, field and webhook event.
Keep posting through busy weeks
Set sources, keywords and posting hours once. Feedigy posts to your channels while you work on other things.
No card required. The Free plan has no time limit.
