Comidoc Developer API documentation
This reference covers authentication, quotas, filters, coupon availability, trends, and the Comidoc MCP surface.
- REST base URL
- https://comidoc.com/api/v1
- MCP endpoint
- https://comidoc.com/mcp
- Date and time format
- ISO 8601 with an explicit offset
Getting started
A verified email address is required to generate a key. The complete key is displayed only once, and Comidoc stores only its cryptographic digest. Send it as a Bearer token.
curl 'https://comidoc.com/api/v1/courses?q=python&rating_min=4.5&has_active_coupon=true' \
-H 'Authorization: Bearer cmd_live_your_key'Free: 250 credits/month. Pro: 100,000 shared credits/month, 10 requests/second, and up to 100 results/page. Responses include RateLimit-*, X-RateLimit-*, and X-Request-Id headers.
Prefer learning by example? The tutorial Build a Udemy Coupon Discord Bot in 5 Minutes goes from zero to a running integration in forty lines of Node.js on the free plan.
Coupon availability contract
Redemption capacity is public on every plan. redemptions.total is the initial capacity reported by Udemy, remaining is the capacity available at the latest successful observation, and redeemed is calculated when both values are known.
{
"data": [{
"coupon_code": "KEEPLEARNING",
"redemptions": {
"total": 100,
"remaining": 80,
"redeemed": 20,
"state": "known",
"observed_at": "2026-07-18T14:20:00.000Z"
},
"expires_at": "2026-07-21T23:59:59.000Z",
"created_at": "2026-07-17T09:05:00.000Z",
"last_verified_at": "2026-07-18T14:20:00.000Z",
"last_confirmed_active_at": "2026-07-18T14:20:00.000Z"
}],
"pagination": {
"total": 1,
"limit": 25,
"next_cursor": null
},
"meta": {
"request_id": "req_…",
"source": "direct",
"plan": "pro",
"coupon_codes_included": true,
"quota": {
"limit": 100000,
"used": 42,
"remaining": 99958,
"resets_at": "2026-08-01T00:00:00.000Z"
}
}
}created_at records when Comidoc first tracked the coupon. last_verified_at is the latest verification attempt, even if it failed, while last_confirmed_active_at is the latest successful active check. redemptions.observed_at timestamps the returned capacity values.
States: known for a fresh measurement, unknown when Udemy does not provide capacity, stale for an old measurement or inactive offer, and exhausted when no redemptions remain. On Free, coupon_code is null and meta.coupon_codes_included is false. Pro returns the coupon code, sets that metadata flag to true, and includes the complete Udemy URL.
Course filters
REST and MCP expose the same discovery surface as the website coupon-filter overlay. REST list values are comma-separated; MCP accepts one value or an array. In every public contract, course_id and a course object's id are Udemy's numeric course ID. Comidoc's internal identifier is not exposed.
Swipe horizontally to view every column.
| Parameter | Value | Effect |
|---|---|---|
| q | Text | Course title, description, or instructor |
| topic | Slug | Comidoc topic |
| course_id | Positive integer | Udemy course ID (not Comidoc's internal ID) |
| category / language / level | Comma-separated | Canonical values; matching ignores case and multiple values use OR |
| course_type | courses | tests | Include courses, practice tests, or both |
| duration | short | medium | long | Under 1 hour, 1–10 hours, or over 10 hours |
| rating | lt-4 | gte-4-lt-4_5 | gte-4_5 | Website rating bands, combinable with OR |
| rating_min / rating_max | 0–5 | Rating range |
| published_after | ISO 8601 | Udemy publication date |
| last_course_updated_after | ISO 8601 | Last course-content update reported by Udemy |
| coupon_status / status | Comma-separated | Active, recent expired, invalid, pending, or all |
| coupon_age | 24h | Coupon creation age, not course publication age |
| coupon_created_after | ISO 8601 | Custom coupon creation cutoff |
| has_active_coupon | Boolean | Requires a verified, unexpired offer with capacity |
| expires_before / expires_after | ISO 8601 | Offer expiry window |
| remaining_min | Integer | Minimum remaining redemptions |
| discount_type | free | percentage | One or more discount types |
| repeat_coupon_course | include | exclude | only | Uses the same recent frequent-coupon rule as the website |
| regular_issuer | include | exclude | only | Instructors who publish coupons regularly |
| sort | Depends on endpoint | Includes rating, published date, duration, remaining uses, price, and trend |
| unique_course | Boolean (/offers) | At most one offer per course, like the website grid |
| horizon | 24h | 7d | 30d | Time window used by trending results |
curl 'https://comidoc.com/api/v1/offers?course_type=courses&category=Development,IT%20%26%20Software&language=English,French&duration=short,medium&rating=gte-4-lt-4_5,gte-4_5&coupon_age=24h&repeat_coupon_course=exclude&sort=remaining&unique_course=true' \
-H 'Authorization: Bearer cmd_live_your_key'last_course_updated_after always targets Udemy's course-content update date and never uses Comidoc's technical database timestamp.
Use /api/v1/filter-options or the MCP tool get_filter_options instead of hard-coding categories or languages. Values are matched case-insensitively and returned in their canonical form; an unknown category, language, or level returns a 400 error instead of an empty success. All Levels is an explicit level value and can be selected on its own. Course responses expose exact offer_count and active_offer_count values, plus returned_offer_count and offers_truncated when the embedded offer array is capped.
REST endpoints
/api/v1/filter-optionsDiscover current categories, languages, levels, and filter enums/api/v1/coursesSearch courses with quality and offer filters/api/v1/courses/{id}Fetch a course by Udemy ID or slug/api/v1/offersSearch current or historical coupon offers/api/v1/topicsBrowse and search the topic catalog/api/v1/topics/{slug}Fetch topic details/api/v1/topics/{slug}/coursesSearch courses associated with a topic/api/v1/trending/coursesFetch time-based course trends/api/v1/trending/topicsFetch time-based topic trends/api/v1/changesRead the incremental change feed from a timestamp/api/v1/watchesFollow a course's new free coupons (Pro)/api/v1/watchesList your active course watches/api/v1/watches/{id}Stop a watch, without consuming a creditReceive new coupons without polling
API Pro accounts can follow courses without a watch-count limit. Configure one public HTTPS destination in your developer account’s Webhook tab, copy the signing secret, and send a free test. Then create watches using your API key. Watches belong to your account and survive key rotation.
Keys created before watches were introduced retain their existing scopes. Create a new key with watches:read and watches:write to use these endpoints. New portal keys include both scopes.
1. Follow a course
curl -X POST 'https://comidoc.com/api/v1/watches' \
-H 'Authorization: Bearer cmd_live_your_key' \
-H 'Content-Type: application/json' \
--data '{"course_id":3836244}'{
"data": {
"id": "watch_example",
"course_id": 3836244,
"course_title": "Python Automation",
"course_slug": "python-automation",
"created_at": "2026-10-03T12:00:00Z"
},
"meta": { "...": "standard API metadata and shared quota" }
}A new watch returns 201; creating the same active watch again returns 200 with the existing identifier. Existing coupons are not replayed. Only newly confirmed free coupons after registration are eligible; repeated verification of the same coupon does not produce additional notifications. Reactivation of the same coupon is not sent again to the same destination. Practice tests and inactive or opted-out courses are excluded. Notifications continue until you delete the watch.
2. Receive a notification
{
"id": "cf840882-07ec-4830-8e22-834a67d9a03b",
"type": "coupon.available",
"created_at": "2026-10-03T12:05:00Z",
"test": false,
"watch_id": "watch_example",
"data": {
"course": { "id": 3836244, "title": "Python Automation", "url": "https://comidoc.com/udemy/python-automation" },
"offer": {
"id": "offer_example",
"coupon_code": "KEEPLEARNING",
"udemy_url": "https://www.udemy.com/course/python-automation/?couponCode=KEEPLEARNING",
"expires_at": "2026-10-05T12:00:00Z",
"last_confirmed_active_at": "2026-10-03T12:04:58Z",
"remaining_uses": 80
}
}
}Tests use type: "webhook.test", test: true, and data.message instead of an offer. The offer can become unavailable after confirmation; notification does not reserve a redemption. Unavailable offers are discarded before retrying. Delivery order is not guaranteed.
3. Verify and acknowledge
Verify the signature against the exact raw request body before parsing JSON. The signing secret is separate from your API key. Headers are webhook-id, webhook-timestamp (Unix seconds), and webhook-signature. The HMAC-SHA256 input is id.timestamp.rawBody; the key is the Base64-decoded signing secret after removing whsec_.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhook(rawBody, headers, secret) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signature = headers['webhook-signature'];
if (typeof id !== 'string' || typeof timestamp !== 'string' ||
typeof signature !== 'string' || !/^\d+$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac('sha256', Buffer.from(secret.slice(6), 'base64'))
.update(id + '.' + timestamp + '.' + rawBody).digest();
return signature.split(' ').some(entry => {
if (!entry.startsWith('v1,')) return false;
const received = Buffer.from(entry.slice(3), 'base64');
return received.length === expected.length && timingSafeEqual(received, expected);
});
}Persist the webhook-id and enqueue your work atomically, then respond with 2xx promptly. Return 2xx for already accepted identifiers. Retain deduplication records beyond the retry window; timestamps alone do not prevent duplicate processing. Automatic retries reuse the same ID and body with a fresh timestamp and signature.
Shared credits and delivery failures
Creating or listing watches costs one API credit per call. One coupon notification accepted with 2xx costs one shared credit, regardless of retries. Tests and permanently failed deliveries cost zero. A credit is temporarily reserved before sending; it becomes charged after 2xx or is released after final failure or cancellation. The portal separates API, webhook, reserved, and available credits. API response quota fields include reservations so clients cannot spend held credits twice.
The optional webhook credit limit is a UTC calendar-month ceiling within the shared quota, not an additional allowance. Zero pauses new coupon deliveries; tests remain available. The monthly counter is stored in PostgreSQL and survives application and Redis restarts.
Normal delivery attempts are immediate, then approximately 1 minute, 5 minutes, 15 minutes, 1 hour, and 3 hours after the previous failure. Redirects are not followed. HTTP 410 ends the delivery immediately. Other non-2xx responses and connection failures are retried. An ambiguous acknowledgement after a crash may cause a duplicate delivery: use the stable ID to deduplicate.
Quota or webhook-limit exhaustion pauses new deliveries. They resume automatically once credits are available or the limit is raised, provided the offer remains eligible. Expired Pro access stops deliveries but retains watches; there is no replay of coupons missed during that period. Changing the URL or replacing the signing secret cancels queued messages for the old configuration. A request already in flight may complete. View recent results in the portal and refresh to check worker progress.
List or stop watches
curl 'https://comidoc.com/api/v1/watches' \
-H 'Authorization: Bearer cmd_live_your_key'
curl -X DELETE 'https://comidoc.com/api/v1/watches/watch_example' \
-H 'Authorization: Bearer cmd_live_your_key'Deletion is free and remains available after quota or Pro expiry. It returns {"deleted":true,"meta":{…}}. Stop watches through REST; this feature does not create an MCP notification subscription. Categories, topics, and instructors are outside this first version.
Listing watches is paginated. Send GET /api/v1/watches?limit=100, then pass pagination.next_cursor as cursor until it is null. The response also includes pagination.total and pagination.limit. Page size follows your account’s maximum; each page costs one API credit. Avoid modifying watches while iterating pages.
Pagination and synchronization
Pass pagination.next_cursor unchanged in the next request’s cursor parameter. Do not decode or construct cursor values. For incremental replication, call /changes?since=…, follow the cursor until it is exhausted, and advance your watermark only after processing succeeds. Course changes with is_active: false or is_opted_out: true act as tombstones and should remove the course and its offers from replicas.
Errors and retries
REST errors use application/problem+json with code, title, detail, instance, and request_id. Retry 429 and 503 responses with exponential backoff and jitter. Do not automatically retry 400, 401, or 403 responses without correcting the request or identity.
Each problem type URL opens its cause and resolution. Browse the complete error catalog.
MCP for agents
Configure an HTTP MCP server at https://comidoc.com/mcp. Anonymous access is intentionally limited and never reveals raw coupon codes; active offers include an opaque, short-lived, single-use Comidoc URL. Add the same Bearer token used for REST to apply the account quota. A Pro plan unlocks coupon codes.
Only tools/call consumes the anonymous hourly or account monthly business quota. Discovery, tools/list, notifications, and legacy 2025 initialization and ping remain protected by a separate per-IP transport rate limit. Every tool result exposes the same request, plan, coupon-code, and quota metadata as REST so an agent can regulate its own request rate.
search_courses and find_offers accept the same categories, languages, levels, course types, durations, rating bands, coupon age, Udemy update cutoff, frequent-coupon mode, and identifiers as their REST counterparts. Useunique_course: true with find_offers to reproduce the website grid.
search_coursesSearch the historical catalog with multiple filtersfetch_courseFetch a course and its active offersfind_offersSearch coupon offers and redemption capacityget_filter_optionsDiscover current categories, languages, and canonical filter valueslist_topicsDiscover available topicsfetch_topicFetch a topicget_trending_coursesCourse trends over 24 hours, 7 days, or 30 daysget_trending_topicsTopic trends over 24 hours, 7 days, or 30 days{
"mcpServers": {
"comidoc": {
"url": "https://comidoc.com/mcp",
"headers": { "Authorization": "Bearer cmd_live_your_key" }
}
}
}Set up in your client
Claude Code
claude mcp add --transport http comidoc https://comidoc.com/mcpClaude Desktop and claude.ai
Settings, then Connectors, then Add custom connector. Name it comidoc and paste https://comidoc.com/mcp as the URL.
Cursor
Add to Cursor in one click or declare the server in .cursor/mcp.json with the JSON above.
VS Code
code --add-mcp '{"name":"comidoc","type":"http","url":"https://comidoc.com/mcp"}'Every client works without a key at the anonymous quota. Add the Authorization header shown above to apply your account quota and, on the Pro plan, receive raw coupon codes.
RapidAPI
The optional RapidAPI adapter serves the same /api/v1 contract when enabled. The identity and plan supplied by the proxy are verified with a server-side shared secret. Only the explicit pro, ultra, and mega subscription labels unlock Pro; unknown labels fail closed to Free. Proxy headers must never be sent directly by a public client. RapidAPI and direct usage remain separately observable in the administration portal.
Versions and stability
Breaking changes receive a new URL version. Fields may be added to v1, so clients must ignore unknown fields. Trending scores expose score_version so algorithm changes remain detectable. The MCP server pins TypeScript SDK 2.0.0, implements the stable stateless 2026-07-28 protocol, and retains a stateless fallback for 2025 clients.