> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hoppier.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a program

> Creates a one-time program in the key's company and returns it. The permissions and account settings that apply in the dashboard apply here too.

The key's owner must be an admin who is allowed to create programs.


> [!WARNING]
>
> **Units.** `amount` is in **cents** (`2500` means \$25.00). `startDate` and `endDate` are Unix timestamps in **milliseconds**, sent as numbers.

> [!NOTE]
>
> There is no sandbox. This creates a real program in your account. Nobody is invited until you [add people](https://docs.hoppier.com/api-reference/participants/add.md).

* **Check before you retry.** This endpoint takes no `Idempotency-Key`. If a request times out or fails with a `5xx`, look for the program with [`GET /programs`](https://docs.hoppier.com/api-reference/programs/list) before you send it again, or you may create it twice.
* **Card type.** An account that isn't verified for Visa cards gets a `gift_card` program, whatever you send. The response's `programType` says which one you got.
* **Address.** Leave out `addressId` to use your account's default address (`isDefault` in [`GET /addresses`](https://docs.hoppier.com/api-reference/addresses/list)). An account that isn't verified for Visa cards and has no address yet can leave it out too: its programs give gift cards only, which don't use an address, so Hoppier fills in its own. An account verified for Visa cards needs an address of its own first: [save one](https://docs.hoppier.com/api-reference/addresses/create.md).
* **Merchants.** Look up merchant ids with [`GET /merchants`](https://docs.hoppier.com/api-reference/merchants/list). Leave out `merchantIds` to start from your company template's merchants, as the dashboard does. If the template locks its merchants, you can't choose them here. A program with no merchants allows every merchant.
* **Invite email.** The program gets Hoppier's standard invite email unless you send `inviteEmail`: `false` for none, or your own subject and body. Hoppier sends it only when you [add someone](https://docs.hoppier.com/api-reference/participants/add.md) with `sendEmailAsUsersAreAdded: true`. `inviteEmailCreated` in the response says whether it was saved.
* **Account settings.** A custom `endDate` needs custom end dates turned on for your account, and `isFixedAmount: false` needs individual amounts turned on. Without them, the request gets `403 feature_not_enabled`.
* **Not available here:** recurring programs, and a program's colours and logo. Those come from the dashboard and your company's program template.


## OpenAPI

````yaml /openapi.yaml post /programs
openapi: 3.1.0
info:
  title: Hoppier API
  version: 1.0.0-beta
  summary: Create and read your Hoppier programs, and add and list the people in them.
  description: >
    The Hoppier API is in **closed beta**. Everything in this reference runs in
    production today.


    - A full-access admin creates company API keys in the dashboard, under
    **Developers › API keys**. For a personal key, email
    [support@hoppier.com](mailto:support@hoppier.com).

    - There is no sandbox. Every key works against your live account.

    - We add fields and error codes without notice, so ignore anything you don't
    recognise. We don't rename or remove a documented field without announcing
    it on the [changelog](https://docs.hoppier.com/changelog) first.
  termsOfService: https://www.hoppier.com/terms
  contact:
    name: Hoppier support
    email: support@hoppier.com
servers:
  - url: https://app.hoppier.com/api/v1
    description: Production
security:
  - apiKey: []
tags:
  - name: Account
    description: Check an API key and see which account it belongs to.
  - name: Addresses
    description: Save and list your company's addresses, for new programs.
  - name: Merchants
    description: Look up the merchants and gift-card brands a program can allow.
  - name: Programs
    description: Read and create the reward programs in your account.
  - name: Participants
    description: Add people to a program and list them.
paths:
  /programs:
    post:
      tags:
        - Programs
      summary: Create a program
      description: >
        Creates a one-time program in the key's company and returns it. The
        permissions and account settings that apply in the dashboard apply here
        too.


        The key's owner must be an admin who is allowed to create programs.
      operationId: createProgram
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProgramRequest'
            examples:
              fixedAmount:
                summary: The same amount for everyone
                value:
                  name: Team lunch
                  programType: gift_card
                  amount: 2500
              individualAmounts:
                summary: Each person gets their own amount
                value:
                  name: Spot bonuses
                  programType: gift_card
                  isFixedAmount: false
              endsAfterJoining:
                summary: Each person's card ends 30 days after they're added
                value:
                  name: Welcome stipend
                  programType: hoppier_card
                  amount: 5000
                  dynamicEndDate: true
                  timeFrame: 30
              ownInviteEmail:
                summary: Chosen gift cards and your own invite message
                value:
                  name: Thank-you gifts
                  programType: gift_card
                  amount: 2500
                  merchantIds:
                    - 3755
                    - 4182
                  inviteEmail:
                    subject: 'A thank-you from Acme: __event_name__'
                    text: >-
                      Hi __first_name__,


                      Thanks for everything this year. Pick the gift card you
                      like best.
      responses:
        '200':
          description: The new program.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedProgram'
              example:
                id: prg_Z8q3NnW2xK
                object: program
                name: Team lunch
                amount: 2500
                currency: usd
                status: active
                startDate: '1793491200000'
                createdAt: '1793491200000'
                updatedAt: '1793491200000'
                addressId: ad_7Ym2p0Lx3Q
                colorHex: '#FF6161'
                colorHex2: '#FFFFFF'
                logoUrl: null
                isFixedAmount: true
                programType: gift_card
                inviteEmailCreated: true
        '400':
          description: >
            - `invalid_request`: a field is missing or invalid, or the body
            isn't valid JSON. `message` names the field and says what's wrong.
            For your own invite email, `parameter` names the part that was
            refused (`inviteEmail.subject`, `inviteEmail.html` or
            `inviteEmail.text`), and nothing is created.

            - `merchant_list_too_large`: too many merchants for a Visa card
            program. Use fewer merchants or a wildcard category.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingField:
                  summary: A required field is missing
                  value:
                    error: invalid_request
                    message: '"name" is required'
                amountTooLow:
                  summary: A fixed amount under 100 cents
                  value:
                    error: invalid_request
                    message: '"amount" must be greater than or equal to 100'
                noSavedAddress:
                  summary: >-
                    No addressId, and no saved address (an account verified for
                    Visa cards)
                  value:
                    error: invalid_request
                    message: >-
                      addressId is required: this account has no saved address.
                      Save one with POST /addresses.
                inviteEmailNoLink:
                  summary: Your own invite email without the claim link
                  value:
                    error: invalid_request
                    message: >-
                      inviteEmail.html must contain the __invite_url__
                      placeholder (each participant's claim link), such as <a
                      href="__invite_url__">Claim your card</a>.
                    parameter: inviteEmail.html
                tooManyMerchants:
                  summary: Too many merchants for a Visa card
                  value:
                    error: merchant_list_too_large
                    message: >-
                      This merchant list is too long for a Visa card (45
                      merchants). Use fewer merchants or a wildcard category.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >
            - `insufficient_permission`: the key's owner isn't an admin of the
            company, or their role doesn't allow creating programs.

            - `feature_not_enabled`: creating programs, custom end dates or
            individual amounts aren't turned on for your account.

            - `merchants_locked_by_template`: your company's program template
            sets the merchants, so you can't send `merchantIds`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                notAdmin:
                  summary: The key's owner isn't an admin
                  value:
                    error: insufficient_permission
                    message: This API key's user is not an admin of this account.
                notAllowed:
                  summary: The key's owner can't create programs
                  value:
                    error: insufficient_permission
                    message: This API key's user is not allowed to create programs.
                customEndDate:
                  summary: Custom end dates aren't turned on
                  value:
                    error: feature_not_enabled
                    message: Custom end dates are not enabled for this account.
                merchantsLocked:
                  summary: The template sets the merchants
                  value:
                    error: merchants_locked_by_template
                    message: >-
                      This account's program template sets the merchants, so
                      they cannot be chosen through the API.
        '404':
          description: >
            - `address_not_found`: `addressId` isn't one of your company's
            addresses. A malformed id, an id that doesn't exist and another
            company's address all get this answer.

            - `merchant_not_found`: a merchant in `merchantIds` isn't available
            to your company for this type of program.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                address:
                  summary: Not one of your addresses
                  value:
                    error: address_not_found
                    message: Address not found.
                merchant:
                  summary: A merchant you can't use
                  value:
                    error: merchant_not_found
                    message: Merchant id 4182 is not available for a gift_card program.
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          description: >
            `rate_limit_unavailable`: Hoppier couldn't check your rate limit, so
            nothing was created. Retry after the number of seconds in
            `Retry-After`.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: rate_limit_unavailable
                message: Please retry shortly.
components:
  schemas:
    CreateProgramRequest:
      type: object
      required:
        - name
        - programType
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
          description: The program's name, as admins and participants see it.
          examples:
            - Team lunch
        programType:
          type: string
          enum:
            - hoppier_card
            - gift_card
          description: >
            `hoppier_card`: a prepaid Visa card. `gift_card`: each person picks
            a gift card. An account that isn't verified for Visa cards gets
            `gift_card` whatever you send.
        amount:
          type: integer
          minimum: 0
          description: >
            The amount each person gets, in **cents** (`2500` means \$25.00).
            Required for a fixed-amount program, and at least `100`. Leave it
            out when `isFixedAmount` is `false`.
          examples:
            - 2500
        isFixedAmount:
          type: boolean
          default: true
          description: >
            `false` gives each person their own amount, which you set when you
            [add them](/api-reference/participants/add). Needs individual
            amounts turned on for your account.
        currency:
          type: string
          enum:
            - usd
          default: usd
        addressId:
          type: string
          description: >
            One of your company's addresses (`ad_…`), from [`GET
            /addresses`](/api-reference/addresses/list). Leave it out to use
            your account's default address. An account that isn't verified for
            Visa cards and has no address can leave it out too, and Hoppier
            fills in its own: that account's programs give gift cards only,
            which don't use an address.
          examples:
            - ad_7Ym2p0Lx3Q
        startDate:
          type: integer
          minimum: 1000000000000
          maximum: 4102444800000
          description: >-
            When the program starts, as a Unix timestamp in **milliseconds**.
            Defaults to now.
          examples:
            - 1793491200000
        endDate:
          type: integer
          minimum: 1000000000000
          maximum: 4102444800000
          description: >
            When the program ends, as a Unix timestamp in **milliseconds**,
            after `startDate`. Needs custom end dates turned on for your
            account. Not with `dynamicEndDate`.
          examples:
            - 1795996800000
        dynamicEndDate:
          type: boolean
          default: false
          description: >
            `true` ends each person's card `timeFrame` days (or hours) after
            they're added, instead of on one date for everyone.
        timeFrame:
          type: integer
          minimum: 1
          description: >-
            How long each person's card lasts, in `period` units. Required with
            `dynamicEndDate`, and only allowed with it.
          examples:
            - 30
        period:
          type: string
          enum:
            - days
            - hours
          default: days
          description: The unit of `timeFrame`.
        merchantIds:
          type: array
          minItems: 1
          maxItems: 100
          uniqueItems: true
          items:
            type: integer
            minimum: 1
          description: >
            The merchants the program allows: Visa merchants for a
            `hoppier_card` program, gift-card brands for a `gift_card` program.
            Look ids up with [`GET /merchants`](/api-reference/merchants/list).
            Leave it out to start from your company template's merchants.
        inviteEmail:
          description: >
            The program's invite email, sent when you [add
            someone](/api-reference/participants/add) with
            `sendEmailAsUsersAreAdded: true`. Leave it out (or send `true`) for
            Hoppier's standard invite email, send `false` for none, or send your
            own. You can change it later in the dashboard.
          oneOf:
            - type: boolean
            - $ref: '#/components/schemas/InviteEmail'
          default: true
    CreatedProgram:
      description: The new program, and whether its invite email was saved.
      allOf:
        - $ref: '#/components/schemas/Program'
        - type: object
          required:
            - inviteEmailCreated
          properties:
            inviteEmailCreated:
              type: boolean
              description: >
                `true` when the program's invite email was saved. `false` when
                you sent `inviteEmail: false`, or if saving it failed: then the
                program still exists, and the first add that asks for the email
                creates Hoppier's standard one.
    Error:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: >
            A machine-readable code, such as `unauthorized` or
            `program_not_found`. Act on it and the HTTP status. New codes can
            appear, so handle one you don't recognise by its status. [Every
            code](/guides/errors-and-rate-limits#error-codes).
          examples:
            - program_not_found
        message:
          type: string
          description: What went wrong, in plain words. Don't parse it; it can change.
          examples:
            - Program not found.
        parameter:
          type: string
          description: On some `invalid_request` errors, the field that was refused.
          examples:
            - email
    InviteEmail:
      type: object
      description: >
        Your own invite email: a subject, a body, or both. Send the body as
        `html` or as `text`, not both. A part you leave out is Hoppier's
        standard one.


        Placeholders, filled in for each person: `__first_name__`,
        `__company_name__`, `__event_name__` (the program's name),
        `__card_value__`, `__start_date__`, `__end_date__`, and `__invite_url__`
        (their claim link). Any other `__name__` is refused.
      minProperties: 1
      additionalProperties: false
      properties:
        subject:
          type: string
          minLength: 1
          maxLength: 200
          description: >
            One line. The only placeholder it can use is `__event_name__`. The
            standard subject is `You're invited: __event_name__`.
          examples:
            - 'A thank-you from Acme: __event_name__'
        html:
          type: string
          minLength: 1
          maxLength: 20000
          description: >
            The body as HTML. Hoppier removes anything unsafe before saving it:
            scripts, style blocks, frames, forms, event handlers such as
            `onclick`, and `javascript:` links. What's left must contain the
            claim link, such as `<a href="__invite_url__">Claim your card</a>`.
          examples:
            - >-
              <p>Hi __first_name__,</p><p>Thanks for a great year.</p><p><a
              href="__invite_url__">Claim your card</a></p>
        text:
          type: string
          minLength: 1
          maxLength: 5000
          description: >
            The body as plain text. A blank line starts a new paragraph, and a
            "Claim your card" link is added at the end, so leave out
            `__invite_url__`.
          examples:
            - |-
              Hi __first_name__,

              Thanks for a great year.
    Program:
      type: object
      description: A reward program in your account.
      required:
        - id
        - object
        - name
        - amount
        - currency
        - status
        - startDate
        - createdAt
        - updatedAt
        - isFixedAmount
        - programType
      properties:
        id:
          type: string
          description: The program's id. Starts with `prg_`.
          examples:
            - prg_Z8q3NnW2xK
        object:
          type: string
          const: program
        name:
          type: string
          examples:
            - Team lunch
        amount:
          type: integer
          description: >
            The amount each person gets, in **cents** (`2500` means \$25.00).
            `0` on a program where each person has their own amount.
          examples:
            - 2500
        currency:
          type: string
          description: Lowercase currency code. `usd` is the only value today.
          examples:
            - usd
        status:
          type: string
          enum:
            - active
            - inactive
            - pending
          description: |
            `active`: running. `inactive`: archived. `pending`: not active yet.
        startDate:
          type: string
          description: >
            When the program starts: a Unix timestamp in **milliseconds**, as a
            string. A program with no start date returns the string
            `"undefined"`, so treat anything that isn't 13 digits as "no start
            date".
          examples:
            - '1793491200000'
        endDate:
          type: string
          description: >-
            When the program ends (Unix milliseconds, as a string). Left out
            when the program has no end date.
          examples:
            - '1795996800000'
        createdAt:
          type: string
          description: Unix milliseconds, as a string.
          examples:
            - '1792504992000'
        updatedAt:
          type: string
          description: Unix milliseconds, as a string.
          examples:
            - '1792504992000'
        addressId:
          type: string
          description: >
            Id of the program's address, one of your [saved
            addresses](/api-reference/addresses/list). Starts with `ad_`. Left
            out when the program's address isn't one of yours, such as the
            address Hoppier fills in for a gift-card program in an account that
            hasn't saved one.
          examples:
            - ad_7Ym2p0Lx3Q
        colorHex:
          type:
            - string
            - 'null'
          description: The program's main colour in the Hoppier app.
          examples:
            - '#FF6161'
        colorHex2:
          type:
            - string
            - 'null'
          description: The program's second colour in the Hoppier app.
          examples:
            - '#FFFFFF'
        logoUrl:
          type:
            - string
            - 'null'
          description: URL of the program's logo, if it has one.
        isFixedAmount:
          type: boolean
          description: >
            `true`: everyone gets the program's `amount`. `false`: each person
            has their own amount, set when they're added.
        programType:
          type: string
          enum:
            - hoppier_card
            - gift_card
            - wire_transfer
          description: >
            `hoppier_card`: a prepaid Visa card. `gift_card`: each person picks
            a gift card. `wire_transfer`: a money transfer to each person's
            bank.
  responses:
    Unauthorized:
      description: >
        `unauthorized`: the API key is missing or malformed, or it's wrong,
        unknown or switched off.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missingKey:
              summary: No API key
              value:
                error: unauthorized
                message: 'API key is missing. Send it as "Authorization: Bearer <key>".'
            invalidKey:
              summary: Malformed, wrong or unknown key
              value:
                error: unauthorized
                message: API key is not valid.
            inactiveKey:
              summary: Key switched off
              value:
                error: unauthorized
                message: API key is inactive.
    RateLimited:
      description: >
        `rate_limited`: too many requests. Wait the number of seconds in
        `Retry-After`, then retry. See [Errors and rate
        limits](/guides/errors-and-rate-limits#rate-limits).
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            perAccount:
              summary: Per-account limit
              value:
                error: rate_limited
                message: >-
                  Too many requests for this account. Retry after the number of
                  seconds in Retry-After.
            perAddress:
              summary: Per-address limit
              value:
                error: rate_limited
                message: Too Many Requests
    ServerError:
      description: >
        `internal_error`: something went wrong on Hoppier's side. The message
        never contains internal details. Retry with backoff; if it keeps
        happening, email support@hoppier.com.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: internal_error
            message: An unexpected error occurred.
  headers:
    RetryAfter:
      description: Whole seconds to wait before retrying.
      schema:
        type: string
      example: '30'
    RateLimitRemaining:
      description: >-
        Per-address limit only. Requests left in the current 10-second window,
        so `0` on a 429.
      schema:
        type: string
      example: '0'
    RateLimitReset:
      description: >-
        Per-address limit only. When the window refills, as a Unix timestamp in
        seconds.
      schema:
        type: string
      example: '1793491210'
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: hk_live.<prefix>.<secret>
      description: >
        Send your key in the `Authorization` header: `Authorization: Bearer
        hk_live.…`. The word `Bearer` is case-sensitive.


        Keys are shown once. Treat them like passwords and never put one in code
        that runs in a browser or an app.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.