bulk_generate_access_codes
Queue a bulk access-code generation batch. Returns a job_id immediately — poll get_job_status for completion.
Bulk-generate access codes for a plan. The underlying job can take minutes for large batches, so the tool returns a job_id immediately; clients poll get_job_status until status=completed. The access_code.generated event emits once per batch when the worker finishes.
Billing model: generation is free. The Stripe metered overage triggers on
redemption when the creator's tier free allotment is exhausted. The
response embeds a preview so callers can still surface the worst-case cost
("if all N get redeemed"). For a cost-only query, call
preview_access_code_cost.
Delivery goes through the creator's connector chat (the Telegram bot today),
never through this tool's response. export_type must be file (a CSV sent
to the creator) or messages (one forwardable message per code) — both
require the creator account to have a linked connector identity. Agents that
need programmatic access must also call
list_access_codes after the batch completes.
Requires ability
The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.
Runs the same action as
The REST endpoint and this tool share one action, so validation, permissions and events are identical.
Fires one event
Delivered to every endpoint subscribed to it once the change is made.
Annotations
A client that honours annotations asks a person before running it. It reaches beyond Subscriby: a connector, a provider or a member.
Arguments
plan_id*stringUUID of the plan to attach the generated codes to.
quantity*integerHow many access codes to generate (1-1000).
export_type*stringHow the job delivers the codes to the creator — file (CSV via bot) or messages (one per code via bot). Required.
expiry_preset*stringRequired. One of no_expiry, 1_day, 1_week, 1_month, 1_year, custom.
custom_expiry_amountintegeroptionalWhen expiry_preset=custom, the amount of custom_expiry_type units. Required when preset=custom.
custom_expiry_typestringoptionalWhen expiry_preset=custom, one of days/weeks/months/years. Required when preset=custom.
consentbooleanoptionalRequired and must be true when export_type=messages — acknowledges that one message per code will be sent to the creator's connector chat.
What it returns
{ "data": { "job_id": "0a4e7b96-c358-4d12-9f6b-25a8013ce74f", "status": "queued", "enqueued_at": "2026-05-18T10:05:00Z", "plan_id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b", "quantity": 50, "preview": { "free_remaining": 3, "chargeable_quantity": 47, "cost": "1.41" } }}Poll get_job_status with the returned job_id until status is completed or failed. It always reaches one of the two.
On completion the job's result carries the batch summary:
{
"plan_id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b",
"plan_name": "Premium Monthly",
"count": 50,
"export_type": "file",
"expires_at": null,
"delivered": true,
"delivery_error": null
}Generation and delivery succeed separately
The codes are written first, then handed to the creator over their connector.
If that delivery fails — the bot is disconnected, the platform is down — the
job still completes: the codes exist and are redeemable, and
access_code.generated
still fires. delivered goes false and delivery_error carries the reason,
so you can tell the creator their CSV never arrived without anyone concluding
the batch failed and generating it twice.
How it fails
VALIDATION_FAILEDquantity out of range, unknown export_type, missing consent when export_type=messages, missing custom_expiry_amount / custom_expiry_type when expiry_preset=custom, unknown expiry_preset, a batch for the same plan already in flight, or the underlying Action rejects the inputs.
RESOURCE_NOT_FOUNDunknown plan_id, or the plan belongs to a team outside the token's scope.
AUTHENTICATION_REQUIREDno authenticated user on the request.
TOKEN_MISSING_ABILITYtoken lacks project-access-code:create.
One batch per plan at a time
Queueing a second batch for a plan whose previous one has not reported an
outcome is refused with VALIDATION_FAILED, and the message names the live
job_id to poll instead.
The worker deduplicates by creator, project and plan, so a second batch inside that window would be dropped without a word — handing you a job id for work that was never going to run. Refusing up front is the honest version of the same constraint.
How is this guide?
Last updated on