REST v1 · MCP 2026-07-28

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.

ParameterValueEffect
qTextCourse title, description, or instructor
topicSlugComidoc topic
course_idPositive integerUdemy course ID (not Comidoc's internal ID)
category / language / levelComma-separatedCanonical values; matching ignores case and multiple values use OR
course_typecourses | testsInclude courses, practice tests, or both
durationshort | medium | longUnder 1 hour, 1–10 hours, or over 10 hours
ratinglt-4 | gte-4-lt-4_5 | gte-4_5Website rating bands, combinable with OR
rating_min / rating_max0–5Rating range
published_afterISO 8601Udemy publication date
last_course_updated_afterISO 8601Last course-content update reported by Udemy
coupon_status / statusComma-separatedActive, recent expired, invalid, pending, or all
coupon_age24hCoupon creation age, not course publication age
coupon_created_afterISO 8601Custom coupon creation cutoff
has_active_couponBooleanRequires a verified, unexpired offer with capacity
expires_before / expires_afterISO 8601Offer expiry window
remaining_minIntegerMinimum remaining redemptions
discount_typefree | percentageOne or more discount types
repeat_coupon_courseinclude | exclude | onlyUses the same recent frequent-coupon rule as the website
regular_issuerinclude | exclude | onlyInstructors who publish coupons regularly
sortDepends on endpointIncludes rating, published date, duration, remaining uses, price, and trend
unique_courseBoolean (/offers)At most one offer per course, like the website grid
horizon24h | 7d | 30dTime 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

GET
/api/v1/filter-optionsDiscover current categories, languages, levels, and filter enums
GET
/api/v1/coursesSearch courses with quality and offer filters
GET
/api/v1/courses/{id}Fetch a course by Udemy ID or slug
GET
/api/v1/offersSearch current or historical coupon offers
GET
/api/v1/topicsBrowse and search the topic catalog
GET
/api/v1/topics/{slug}Fetch topic details
GET
/api/v1/topics/{slug}/coursesSearch courses associated with a topic
GET
/api/v1/trending/coursesFetch time-based course trends
GET
/api/v1/trending/topicsFetch time-based topic trends
GET
/api/v1/changesRead the incremental change feed from a timestamp
POST
/api/v1/watchesFollow a course's new free coupons (Pro)
GET
/api/v1/watchesList your active course watches
DELETE
/api/v1/watches/{id}Stop a watch, without consuming a credit

Receive 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 filters
fetch_courseFetch a course and its active offers
find_offersSearch coupon offers and redemption capacity
get_filter_optionsDiscover current categories, languages, and canonical filter values
list_topicsDiscover available topics
fetch_topicFetch a topic
get_trending_coursesCourse trends over 24 hours, 7 days, or 30 days
get_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/mcp

Claude 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.