---
title: "Generate a batch of codes"
method: POST
path: "/v1/projects/{project}/plans/{plan}/access-codes/bulk-generate"
tags: ["Access Codes"]
---

# Generate a batch of codes

`POST /v1/projects/{project}/plans/{plan}/access-codes/bulk-generate`

## Request

- Base URL: `https://api.subscriby.net`
- URL: `https://api.subscriby.net/v1/projects/{project}/plans/{plan}/access-codes/bulk-generate`
- Auth: HTTP bearer

Queues a batch of codes for the plan and answers `202 Accepted` at once; the codes are written by a background job and delivered the way `export_type` names. Poll the list endpoint, or subscribe to `access_code.generated`, to know when the batch is ready.

```bash
curl -X POST https://api.subscriby.net/v1/projects/$PROJECT/plans/$PLAN/access-codes/bulk-generate \
  -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "quantity": 50,
    "export_type": "file",
    "expiry_preset": "1_month"
  }'
```

Answers `202` with the job's key, the plan, the quantity and the cost preview the pricing endpoint describes. Bulk generation is limited to **10 batches per minute per token** in its own bucket, on top of the token's general limits; the eleventh answers `429 RATE_LIMITED` with `Retry-After`.

The validation rules are identical to the creator dashboard's: `quantity` 1 to 1,000; `export_type` `file` or `messages`, with `consent: true` required for `messages` because one bot message per code can trip the connector's rate limits; `expiry_preset` one of `no_expiry`, `1_day`, `1_week`, `1_month`, `1_year` or `custom`, the last needing `custom_expiry_amount` and `custom_expiry_type` (`days`, `weeks`, `months` or `years`).

## Path parameters

- `project` string, uuid, required
- `plan` string, uuid, required

## Headers

- `Idempotency-Key` string, uuid, required

## Request body

- BulkGenerateAccessCodesRequest: A batch of access codes to generate: how many, how they are delivered to you, and when they expire.
  - `quantity` integer, required: How many codes to generate, 1 to 1,000 per batch.
  - `export_type` 'file' | 'messages', required: How a generated batch of access codes is handed to the creator. Both routes go through the creator's chat with the platform bot, because the batch is generated on a queue after the request returned and the bot is the one channel every creator already has open. | | |---| | `file` <br/> One CSV with every code, for spreadsheets and automation. | | `messages` <br/> One forwardable message per code, capped so a batch cannot flood the chat. |
  - `consent` unknown
  - `expiry_preset` 'no_expiry' | '1_day' | '1_week' | '1_month' | '1_year' | 'custom', required: When a generated access code stops being redeemable. The presets cover what creators pick nearly every time; `Custom` takes an amount and a unit for the rest. The instant is resolved when the batch is generated, so every code in a batch shares one expiry. | | |---| | `no_expiry` <br/> The code stays redeemable until it is used or revoked. | | `1_day` <br/> | | `1_week` <br/> | | `1_month` <br/> | | `1_year` <br/> | | `custom` <br/> An amount of a unit, supplied alongside the preset. |
  - `custom_expiry_amount` integer, nullable: How many units the codes live for; required when `expiry_preset` is `custom`, at least 1.
  - `custom_expiry_type` 'days' | 'weeks' | 'months' | 'years': The unit of a custom access-code expiry. Deliberately no minutes or hours: a code that dies within the hour it was generated is a support ticket, not a feature.

## Response `202`

202 with the job key, the plan, the quantity and the preview.

- object
  - `data` object, required: The response's payload.
    - `unique_key` string, required: The job's uniqueness key: a second batch for the same plan queued while one is running is folded into it.
    - `plan_id` string, required: The plan the codes activate.
    - `quantity` integer, required: How many codes were requested.
    - `status` 'generation_queued', required: Always `generation_queued`.
    - `preview` union, required: The worst-case cost breakdown, as the preview endpoint answers it.
      - object
        - `free_remaining` unknown, required
        - `chargeable_quantity` unknown, required
        - `cost` string, required: The worst-case charge for `chargeable_quantity`, as a decimal string in the billing currency.
        - `exceeds_threshold` boolean, required: Whether `cost` passes the confirmation threshold the dashboard warns at.
        - `threshold` union, required: That threshold, in the billing currency.
          - integer
          - number
      - object
        - `free_remaining` unknown, required
        - `chargeable_quantity` integer, required: How many of the batch would be metered if every one were redeemed.
        - `cost` '0', required: The worst-case charge for `chargeable_quantity`, as a decimal string in the billing currency.
        - `exceeds_threshold` boolean, required: Whether `cost` passes the confirmation threshold the dashboard warns at.
        - `threshold` union, required: That threshold, in the billing currency.
          - integer
          - number
      - object
        - `free_remaining` integer, required: Codes still covered by the tier's free allotment this cycle.
        - `chargeable_quantity` integer, required: How many of the batch would be metered if every one were redeemed.
        - `cost` '0', required: The worst-case charge for `chargeable_quantity`, as a decimal string in the billing currency.
        - `exceeds_threshold` boolean, required: Whether `cost` passes the confirmation threshold the dashboard warns at.
        - `threshold` number, required: That threshold, in the billing currency.

## Other responses

- `400`: `IDEMPOTENCY_KEY_MISSING`: every write needs an `Idempotency-Key` header. Send a fresh UUID per distinct operation.
- `401`: `AUTHENTICATION_REQUIRED`: the request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an `sbt_test_` token on production).
- `403`: `TOKEN_MISSING_ABILITY`: the token is valid but does not carry the ability this endpoint requires; `error.context.required_ability` names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers `FORBIDDEN`, `TEAM_TIER_REQUIRED` or `CONNECTOR_TIER_REQUIRED` with the same status, and says so in its own description.
- `404`: `RESOURCE_NOT_FOUND`: an id in the path names nothing the token can see. `TENANT_MISMATCH`: the project sits outside the token's `scope:project:` allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.
- `409`: `IDEMPOTENCY_KEY_REUSED`: the key was already used in the last 24 hours with a different request body.
- `422`: `VALIDATION_FAILED`: the payload broke a rule, and `error.fields` maps each offending key to its messages. A refusal from the domain, such as a plan that cannot go on sale or a member who cannot be removed, uses the same code with `error.message` saying why and no `fields`. On this endpoint: `VALIDATION_FAILED`: when a rule above is broken; `error.fields` names the key, and the `consent` and custom-expiry messages spell out what was missing.
- `425`: `IDEMPOTENCY_REPLAY_IN_PROGRESS`: the first request with this key is still running; retry in a few seconds and the original response is replayed.
- `429`: `RATE_LIMITED`: the token has spent its 300 requests a minute or 10,000 an hour; `Retry-After` says when the next one is accepted. On this endpoint: `RATE_LIMITED`: when the token has queued 10 batches within a minute.

---

[API](https://skmtc.dev/memberpass/apis/subscriby-rest-api.md) · [All operations](https://skmtc.dev/memberpass/apis/subscriby-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/memberpass/apis/subscriby-rest-api/revisions/59c4df91d764?raw)
