# Get Creator Profile Source: https://developers.kit.com/api-reference/accounts/get-creator-profile /api-reference/v4.json get /v4/account/creator_profile Returns the account's Creator Profile — the public-facing identity shown on their Kit profile page and in the Creator Network: display `name`, `byline`, `bio`, `image_url`, and the public `profile_url`. **Note:** returns `404` when the account hasn't set up a Creator Profile, so handle that case rather than treating it as an error in your integration. # Get current account Source: https://developers.kit.com/api-reference/accounts/get-current-account /api-reference/v4.json get /v4/account Returns the authenticated account and user: account `id`, `name`, `plan_type`, primary email address, timezone, and the account's sending addresses with their verification and DMARC status. For OAuth apps this is the standard way to identify which account authorized your app after the token exchange. For subscriber-growth metrics, see [Get growth stats](/api-reference/accounts/get-growth-stats); for account-level email performance, see [Get email stats](/api-reference/accounts/get-email-stats). # Get email stats Source: https://developers.kit.com/api-reference/accounts/get-email-stats /api-reference/v4.json get /v4/account/email_stats Returns account-level email performance for the last 90 days: `sent`, `opened`, and `clicked` counts plus open, click, unsubscribe, and bounce rates, with the exact reporting window in `starting`/`ending` (`email_stats_mode` is `last_90`). The `open_tracking_enabled` and `click_tracking_enabled` flags indicate whether tracking is switched on for the account — open and click rates are only meaningful while tracking is enabled. For subscriber-growth metrics over a custom date range, use [Get growth stats](/api-reference/accounts/get-growth-stats); for per-broadcast performance, use [Get stats for a broadcast](/api-reference/broadcasts/get-stats-for-a-broadcast). # Get growth stats Source: https://developers.kit.com/api-reference/accounts/get-growth-stats /api-reference/v4.json get /v4/account/growth_stats Get growth stats for a specific time period. Defaults to last 90 days.

NOTE: We return your stats in your sending time zone. This endpoint does not return timestamps in UTC. # List colors Source: https://developers.kit.com/api-reference/accounts/list-colors /api-reference/v4.json get /v4/account/colors Returns the account's saved brand color palette as an array of hex codes (up to 10). These are the colors available in Kit's editors for emails, forms, and landing pages. To change the palette, use [Update colors](/api-reference/accounts/update-colors). # Update colors Source: https://developers.kit.com/api-reference/accounts/update-colors /api-reference/v4.json put /v4/account/colors Replaces the account's brand color palette with the provided array of up to 10 hex codes and returns the updated list. **Note:** this overwrites the entire palette rather than appending — include every color you want to keep. Returns `422` when a color code is invalid. # API Authentication Source: https://developers.kit.com/api-reference/authentication Authenticating with the Kit API for apps and personal use We support two authentication mechanisms in the V4 API: * **OAuth 2.0** for apps available for all creators in the Kit App Store * **API keys** for automating simple tools and integrations for your own account ## OAuth We support the [Authorization Code Grant](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1) and depending on the type of app you're building we support two ways of issuing access tokens: * For web servers, you'll use the [refresh token flow](/api-reference/oauth-refresh-token-flow) * For Single Page Apps (SPA) or mobile apps, you'll use the [Proof Key for Code Exchange (PKCE) flow](/api-reference/oauth-proof-key-for-code-exchange-flow) OAuth app flow We've found these two resources to be helpful for learning more about how OAuth 2.0 works: ### Creating and configuring your OAuth powered app For apps and full API V4 support, you will need to authenticate via OAuth 2.0. In order to set this up in your Kit account, first you have to [create an app](/kit-app-store/building-apps#creating-your-app) and turn on [API access](/kit-app-store/building-apps#api-and-plugin-access). Once you have turned on API authentication for your app you will be offered 3 settings to configure: * `Authorization URL` * `Redirect URIs` * `Secure application` OAuth configuration ##### Authorization URL The `Authorization URL` should be a URL on your system that we will link to during app installation so you can initiate OAuth and store the returned access tokens. We will link the user to this URL with a `redirect` query param, e.g. `?redirect=https://app.kit.com/apps`. When the authorization flow is complete, you need to redirect the user back to that provided redirect URL so they can continue their session within the Kit app. ##### Redirect URI(s) Once a user has logged in or signed up with your service, you will redirect them back to the Kit OAuth server for the creator to grant access to the Kit API for your service. On this request you will specify the callback URI that Kit will reach out to with a temporary authorization code, that you will be able to exchange for an access and refresh token, once consent is given by the user. For security, the value in the `redirect_uri` property sent to the Kit OAuth server must match one of the Redirect URIs you have set up in the above app configuration screen. ##### Secure application If your app will be used in an insecure location where the client secret can't be kept confidential - such as mobile or single page apps, you will have to turn this setting off. When unchecked this will enforce use of the [Proof Key for Code Exchange (PKCE) flow found above](/api-reference/authentication#oauth-flows). ##### Post-installation redirect Your app may also include the option to alternatively send creators to your app, or an externally hosted onboarding flow, post signup. This can be configured using the `Redirect URL after install` field in your [app details setting page](/kit-app-store/app-details-page). An example of this flow can be seen below. example redirect flow example redirect flow ## API keys API key authentication is the simplest way to access V4 of the API, tailored for programmatic access to your own Kit account for simple account automation, or for pulling account data for deeper external analysis. We do not offer any official support for apps or public integrations that rely upon API keys for authentication - for apps, please follow the OAuth guide below. We also offer some restrictions when using API keys: * When using API Keys, no more than 120 requests over a rolling 60 second period for a given API Key (we offer limits of 600 requests using OAuth) * Some of our endpoints require OAuth authentication - for example, our bulk and purchase creation endpoints. Please check the endpoint specific documentation for authentication requirements ### Creating V4 API keys To use API Key authentication, you must first create a V4 API Key. To do this, visit the ["Developer" tab in your account settings](https://app.kit.com/account_settings/developer_settings). v4 api key settings Here: Please make sure to save your API key at this point and keep it somewhere safe, as you'll not be able to access it again after leaving the screen. V4 api key created ### Resetting & deleting V4 API keys If you have misplaced your API key, you will not be able to retrieve it again and will instead have to reset it from within your ["Developer" settings](https://app.kit.com/account_settings/developer_settings). To reset your key, first click on the "Edit" button on the key you want to update: edit v4 api key Then click on the "Reset" button to re-roll the key to a new value. reset v4 api key Click "Reset" once more to confirm your action. reset v4 api key confirmation Your V4 key is now reset. At this point, any script or process that was relying on the previous iteration of the key will fail to authenticate, so you will need to replace it with the new value provided here. Again, it is important that you save your API key at this point and keep it somewhere safe, as you'll not be able to access it again after leaving the screen. v4 api key reset ### Deleting V4 API keys If you ever no longer need an API Key, you can also delete it by editing the API Key, and click on "Delete API Key". delete v4 api key ### Using V4 API keys To use V4 API key authentication, pass the key alongside a `X-Kit-Api-Key` header when making requests. For example, the following request will return your account information: ```shell theme={null} curl --request GET \ --url https://api.kit.com/v4/account \ --header 'X-Kit-Api-Key: ' ``` # Create a broadcast Source: https://developers.kit.com/api-reference/broadcasts/create-a-broadcast /api-reference/v4.json post /v4/broadcasts Draft or schedule to send a broadcast to all or a subset of your subscribers.

To save a draft, set `send_at` to `null`.

To publish to the web, set `public` to `true`.

To schedule the broadcast for sending, provide a `send_at` timestamp. Scheduled broadcasts should contain a subject and your content, at a minimum.

We currently support targeting your subscribers based on segment or tag ids. # Delete a broadcast Source: https://developers.kit.com/api-reference/broadcasts/delete-a-broadcast /api-reference/v4.json delete /v4/broadcasts/{id} Permanently deletes a draft or scheduled broadcast and returns `204 No Content` on success. This is a hard delete and cannot be undone. **Note:** a broadcast that is currently sending or has already been sent cannot be deleted — the request returns a `422` with `Broadcast is already sending.` or `Broadcast has already been sent.` respectively. To keep a scheduled broadcast without sending it, unschedule it via [Update a broadcast](/api-reference/broadcasts/update-a-broadcast) instead. # Get a broadcast Source: https://developers.kit.com/api-reference/broadcasts/get-a-broadcast /api-reference/v4.json get /v4/broadcasts/{id} Returns a single broadcast's full record: subject, `content` (HTML), preview text, targeting (`subscriber_filter`), email template, sending schedule (`send_at`), and web-publishing details (`public`, `public_url`, `published_at`). Delivery and engagement stats are not included; use [Get stats for a broadcast](/api-reference/broadcasts/get-stats-for-a-broadcast) for opens, clicks, and unsubscribes, or [Get link clicks for a broadcast](/api-reference/broadcasts/get-link-clicks-for-a-broadcast) for per-link detail. A broadcast that is also published as a post shares its `publication_id` with the matching post from [List posts](/api-reference/posts/list-posts). # Get link clicks for a broadcast Source: https://developers.kit.com/api-reference/broadcasts/get-link-clicks-for-a-broadcast /api-reference/v4.json get /v4/broadcasts/{broadcast_id}/clicks Returns click performance for each link in a broadcast: the link `url`, its `unique_clicks`, and its click-to-delivery and click-to-open rates. Useful for seeing which calls to action actually drove engagement. **Note:** Pagination parameters control the list of links returned for the top-level broadcast; results are cursor-paginated. **Data retention:** Starting October 15, 2026, email stats (opens, clicks, sends, bounces, and unsubscribes) are available via the API for the last 5 years — click data for broadcasts older than that window is no longer available. Older data is archived and can no longer be retrieved via the API; creators can [contact Kit support](https://help.kit.com) to request an export of their archived data. See [Email data retention](/api-reference/email-data-retention) for the full policy. # Get stats for a broadcast Source: https://developers.kit.com/api-reference/broadcasts/get-stats-for-a-broadcast /api-reference/v4.json get /v4/broadcasts/{broadcast_id}/stats Returns delivery and engagement stats for a single broadcast: recipients, opens, clicks, unsubscribes, their corresponding rates, plus the sending `status` and `progress`. The `open_tracking_disabled` and `click_tracking_disabled` flags indicate whether tracking was enabled when the broadcast was sent — interpret open and click counts accordingly. To fetch stats for many broadcasts at once, use [Get stats for a list of broadcasts](/api-reference/broadcasts/get-stats-for-a-list-of-broadcasts); for per-link click detail, use [Get link clicks for a broadcast](/api-reference/broadcasts/get-link-clicks-for-a-broadcast). **Data retention:** Starting October 15, 2026, email stats (opens, clicks, sends, bounces, and unsubscribes) are available via the API for the last 5 years — requesting stats for a broadcast sent before that window returns a `400` error. Older data is archived and can no longer be retrieved via the API; creators can [contact Kit support](https://help.kit.com) to request an export of their archived data. See [Email data retention](/api-reference/email-data-retention) for the full policy. # Get stats for a list of broadcasts Source: https://developers.kit.com/api-reference/broadcasts/get-stats-for-a-list-of-broadcasts /api-reference/v4.json get /v4/broadcasts/stats Returns delivery and engagement stats — recipients, opens, clicks, unsubscribes, and their corresponding rates — for a page of broadcasts in a single request, avoiding a call per broadcast. Narrow the results with the `status` filter and the `sent_after`/`sent_before` timestamps; results are cursor-paginated with up to 1000 broadcasts per page (default 500). For a single broadcast, use [Get stats for a broadcast](/api-reference/broadcasts/get-stats-for-a-broadcast). **Data retention:** Starting October 15, 2026, email stats (opens, clicks, sends, bounces, and unsubscribes) are available via the API for the last 5 years — broadcasts sent before that window are omitted from this list. Older data is archived and can no longer be retrieved via the API; creators can [contact Kit support](https://help.kit.com) to request an export of their archived data. See [Email data retention](/api-reference/email-data-retention) for the full policy. # List broadcasts Source: https://developers.kit.com/api-reference/broadcasts/list-broadcasts /api-reference/v4.json get /v4/broadcasts Returns a cursor-paginated list of broadcasts in the account with their content, targeting (`subscriber_filter`), email template, and web-publishing settings. Filter by lifecycle `status` (`draft`, `scheduled`, `sending`, `completed`, `aborted`) and by the `sent_after`/`sent_before` timestamps. Default 500 results per page, maximum 1000. **Tip:** set `slim=true` to omit the expensive fields (`content`, `public_url`, `email_address`, `email_template`, `subscriber_filter`) for a faster, smaller response — useful when you only need identifiers and subjects. Delivery and engagement stats are not included in this response; use [Get stats for a list of broadcasts](/api-reference/broadcasts/get-stats-for-a-list-of-broadcasts) for those. Broadcasts that are also published as posts share a `publication_id` with the matching post from [List posts](/api-reference/posts/list-posts). # Update a broadcast Source: https://developers.kit.com/api-reference/broadcasts/update-a-broadcast /api-reference/v4.json put /v4/broadcasts/{id} Update an existing broadcast. Continue to draft or schedule to send a broadcast to all or a subset of your subscribers.

To save a draft, set `public` to false.

To schedule the broadcast for sending, set `public` to true and provide `send_at`. Scheduled broadcasts should contain a subject and your content, at a minimum.

We currently support targeting your subscribers based on segment or tag ids. # Bulk & async processing Source: https://developers.kit.com/api-reference/bulk-and-async-processing Working with our bulk endpoints We support bulk processing for some common use cases, e.g. create subscribers, requiring OAuth authentication. These endpoints exist in the bulk namespace, i.e. `https://api.kit.com/v4/bulk/`. In our bulk requests, we support synchronous processing for small batch sizes. The cut off size is clearly documented in each bulk request's documentation below. For large batch sizes, we use an asynchronous callback design. If you include a `callback_url` in your request body, we’ll `POST` to that URL when our processing has completed. Our `POST` request body will be the same response shape as our documented our synchronous `200 OK` use case for each endpoint. If you try to enqueue too many bulk requests at once, you'll receive an error response with a `413` status code, which your code should gracefully handle. Try again after a short period. We can receive up to 300MB of request data per app, per creator account, before we respond with a `413` status. This is shared across all bulk requests (e.g. bulk creation of subscribers and tags). # Bulk create custom fields Source: https://developers.kit.com/api-reference/custom-fields/bulk-create-custom-fields /api-reference/v4.json post /v4/bulk/custom_fields See "[Bulk & async processing](#bulk-amp-async-processing)" for more information. # Bulk update subscriber custom field values Source: https://developers.kit.com/api-reference/custom-fields/bulk-update-subscriber-custom-field-values /api-reference/v4.json post /v4/bulk/custom_fields/subscribers Creates or updates custom field values for many subscribers in one request. Each entry in `custom_field_values` pairs a `subscriber_id` with a `subscriber_custom_field_id` and the `value` to set. Requires OAuth authentication. Requests with 100 or fewer values are processed synchronously (`200`); larger requests are processed asynchronously (`202`) — provide a `callback_url` to be notified on completion, and see [Bulk & async processing](/api-reference/bulk-and-async-processing) for how async requests behave. Returns `413` when the request would exceed the account's data limit for enqueued bulk requests. The custom fields must already exist — entries referencing an unknown `subscriber_custom_field_id` are rejected individually with a `Custom field does not exist` error in the response's failures list, while the rest of the batch is still processed. See [List custom fields](/api-reference/custom-fields/list-custom-fields) and [Bulk create custom fields](/api-reference/custom-fields/bulk-create-custom-fields). # Create a custom field Source: https://developers.kit.com/api-reference/custom-fields/create-a-custom-field /api-reference/v4.json post /v4/custom_fields Create a custom field for your account. The label field must be unique to your account. Whitespace will be removed from the beginning and the end of your label.

Additionally, a key field and a name field will be generated for you. The key is an ASCII-only, lowercased, underscored representation of your label. This key must be unique to your account. Keys are used in personalization tags in sequences and broadcasts. Names are unique identifiers for use in the HTML of custom forms. They are made up of a combination of ID and the key of the custom field prefixed with "ck_field". # Delete custom field Source: https://developers.kit.com/api-reference/custom-fields/delete-custom-field /api-reference/v4.json delete /v4/custom_fields/{id} This will remove all data in this field from your subscribers. # List custom fields Source: https://developers.kit.com/api-reference/custom-fields/list-custom-fields /api-reference/v4.json get /v4/custom_fields A custom field allows you to collect subscriber information beyond the standard fields of first name and email address. An example would be a custom field called last name so you can get the full names of your subscribers.

You create a custom field, and then you're able to use that in your forms or emails. # Update a custom field Source: https://developers.kit.com/api-reference/custom-fields/update-a-custom-field /api-reference/v4.json put /v4/custom_fields/{id} Updates a custom field label (see [Create a custom field](/api-reference/custom-fields/create-a-custom-field) for more information on labels). Note that the key will change but the name remains the same when the label is updated.

Warning: An update to a custom field will break all of the liquid personalization tags in emails that reference it - e.g. if you update a `Zip_Code` custom field to `Post_Code`, all liquid tags referencing `{{ subscriber.Zip_Code }}` would no longer work and need to be replaced with `{{ subscriber.Post_Code }}`. # Dates Source: https://developers.kit.com/api-reference/dates Working with dates We return date values throughout our API. These are all returned in UTC, ISO8601 format such as: `"2023-07-17T16:48:20Z"`. In order to make this data more user friendly, we recommend using the `timezone.utc_offset` found on the [Get current account endpoint](/api-reference/accounts/get-current-account) to convert the date to the timezone set on the Kit account level of the creator. # Email data retention Source: https://developers.kit.com/api-reference/email-data-retention How long email stats are available via the Kit API, and what to do when you need older data Starting **October 15, 2026**, Kit is changing how far back email stats — opens, clicks, sends, bounces, and unsubscribes — are directly accessible across broadcasts and sequences. We're investing in a faster, more modern way to store and serve this data, which lets us build more robust reporting tools going forward. This applies to email **stats only**. Subscriber records, tags, custom fields, purchases, and broadcast content are not affected. Nothing is deleted — a creator's data remains theirs; what changes is how older stats are accessed. ## What the API returns The API returns email stats for the **last 5 years (60 months)**: | Data age | Kit app (UI) | API & Kit MCP | CSV export | | --------- | --------------- | --------------- | --------------- | | 0–3 years | ✅ Available | ✅ Available | ✅ Available | | 3–5 years | ❌ Not shown | ✅ Available | ✅ Available | | 5+ years | ❌ Not available | ❌ Not available | ❌ Not available | Note that the Kit app UI shows the last 3 years, so the API, [Kit MCP](/mcp/kit-mcp), and exports give you access to a *longer* history (5 years) than creators see in the app. How the 5-year window surfaces varies by endpoint: * Requesting stats for a specific broadcast sent before the window returns a `400` error. * Explicit date-range parameters that fall outside the window return a `400` error indicating the earliest available date. * List and filter endpoints omit out-of-window broadcasts from results, or clamp date ranges to the window. ## Affected endpoints * [Get stats for a broadcast](/api-reference/broadcasts/get-stats-for-a-broadcast) — returns a `400` error for broadcasts sent before the window * [Get stats for a list of broadcasts](/api-reference/broadcasts/get-stats-for-a-list-of-broadcasts) — broadcasts sent before the window are omitted from results * [Get link clicks for a broadcast](/api-reference/broadcasts/get-link-clicks-for-a-broadcast) — click data for broadcasts older than the window is no longer available * [List stats for a subscriber](/api-reference/subscribers/list-stats-for-a-subscriber) — date params are clamped to the window; dates outside it return a `400` error * [Filter subscribers by engagement](/api-reference/subscribers/filter-subscribers-by-engagement-sign-up-date-state-and-tags) — engagement-condition date bounds are clamped to the window; a `stats` include range outside it returns a `400` error ## Need data older than 5 years? Stats older than 5 years will be archived and will no longer be accessible. If a creator needs their email stats that are older than 5 years, you have until October 15th, 2026 to export them directly from Kit or [contact Kit support](https://help.kit.com/). Requests need to come from the account owner, so apps should direct creators to support rather than contacting support on their behalf. If your app stores or analyses long-term engagement history, sync any stats older than 5 years before October 15, 2026. Going forward, sync data before it ages out of the 5-year window. # List email templates Source: https://developers.kit.com/api-reference/email-templates/list-email-templates /api-reference/v4.json get /v4/email_templates Returns a cursor-paginated list of the account's email templates with each template's `id`, `name`, `is_default` flag, and `category` (for example `HTML` or `Starting point`). Use a template's `id` as the `email_template_id` when creating or updating a broadcast. **Note:** templates in the `Starting point` category are generally not supported when creating broadcasts via the API — using one returns a `Starting point email template is not supported` error unless your app has been enabled for it. See [Create a broadcast](/api-reference/broadcasts/create-a-broadcast). # Eventual consistency Source: https://developers.kit.com/api-reference/eventual-consistency Understanding data freshness and propagation delays in the Kit API. Some Kit API endpoints are backed by a distributed data system that syncs asynchronously. This means that after you write data — creating a subscriber, updating a field, applying a tag — a subsequent read may not immediately reflect that change. This is called **eventual consistency**, and it's an intentional property of how our infrastructure is built. By separating our read and write systems, we're able to support richer queries, advanced filters, and better performance at scale. ## What to expect You may see temporary differences such as: * A newly created subscriber not appearing in a list right away. * A list `total` count that is slightly off after a write. * Filter results (by tag, segment, engagement, etc.) that don't yet include a just-written change. In all cases, these differences resolve automatically once the system catches up. Eventual consistency applies to **list and reporting endpoints** — for example, listing subscribers or filtering by tags. Direct lookups by ID (e.g., `GET /v4/subscribers/:id`) are not affected and return strongly consistent results. ## How long is the delay? Under normal conditions: | Percentile | Propagation delay | | ------------ | ----------------- | | P50 (median) | \~30 seconds | | P99 (tail) | Up to 5 minutes | For most requests, data will be consistent within about 30 seconds. In rare cases — typically under higher load — it may take up to 5 minutes before reads converge to the latest written state. For context, this is well within the range of delays common across the industry. Stripe's reporting API, for example, can reflect updates with up to a 3-hour delay. ## Which endpoints are affected? Eventual consistency primarily affects endpoints that query or aggregate across subscribers: * `GET /v4/subscribers` — listing and filtering subscribers * Endpoints that return subscriber counts or totals * Endpoints that filter by tags, segments, custom fields, or engagement data Endpoints that write data and return the resource in the response (e.g., `POST /v4/subscribers`, `PUT /v4/subscribers/:id`) are **not** affected — the response you receive from a write reflects the committed state of that resource. ## How to build around eventual consistency ### Don't rely on read-after-write for confirmation If you write subscriber data and immediately query a list to confirm it's there, you may see stale or incomplete results. This is especially common in bulk import workflows, where job processing time compounds the propagation delay. **Trust the write response** — a successful `201 Created` or `202 Accepted` means the data has been saved or queued. You don't need to verify by reading the list right away. ```bash theme={null} # ✅ Accepted: trust that the bulk job will complete POST /v4/bulk/subscribers → 202 Accepted # ⚠️ Avoid querying lists immediately after a bulk write GET /v4/subscribers?created_after=2026-01-01T00:00:00Z → May return incomplete results while the job processes and data syncs (~30s–5min) ``` ### Use the ID returned in write responses Write responses return the full resource object, including the `id`. Use that ID directly for subsequent operations rather than re-querying a list to look it up. ```bash theme={null} # Capture the ID from the write response POST /v4/subscribers → { "subscriber": { "id": "abc123" } } # Use it directly for subsequent operations POST /v4/tags/:tag_id/subscribers { "subscriber_id": "abc123" } GET /v4/subscribers/abc123 ``` ### Retry reads with backoff If you need to confirm a resource is visible in a list, retry the read using exponential backoff rather than polling aggressively: * Start with a short delay (250–500ms). * Increase the delay on each retry. * Cap the maximum delay (e.g., 5 seconds). * Stop after a reasonable timeout (30–60 seconds) and surface a helpful error to the user. ### Bulk workflows Bulk endpoints process asynchronously in the background, adding processing time on top of the propagation delay. See [Bulk & async processing](/api-reference/bulk-and-async-processing) for details on polling job status before reading back results. ### Two-way sync integrations If your app syncs subscriber data bidirectionally — writing to Kit and reading back to confirm state — the propagation delay can cause your sync to appear stale for a short window. We recommend: * Storing the `id` and known state locally at write time rather than re-fetching to verify. * Waiting at least 30–60 seconds before reading back, or using exponential backoff if you must poll. * Treating a successful write response as the source of truth, not the immediate read-back result. ### Design your UI for a "syncing" state If your app has a UI, acknowledge the reality of eventual consistency to your users: * Show a "Syncing…" or "Saving…" state after writes. * Offer a manual "Refresh" action so users can check for updated data. * Avoid flows that make hard decisions based on list results immediately after a write. ## Troubleshooting If you suspect eventual consistency is affecting your integration: * Confirm whether the workflow is read-after-write (a create or update followed by an immediate list or count read). * Add logging around write timestamps and subsequent read attempts to measure the actual delta. * Implement retries with backoff for reads that return missing or stale data. * If the issue persists beyond a 5-minute window, contact support with request IDs and timestamps — that's outside the expected propagation range and may indicate a separate issue. ## FAQ **Does this affect all endpoints?** No. Write endpoints and direct ID lookups are strongly consistent. Eventual consistency applies to list, filter, and reporting endpoints that aggregate across subscribers. **Can I force a fresh read?** Not currently. Design your integration to tolerate short delays using the ID-based patterns, retry with backoff, and user-facing syncing states described above. **Why does this happen at all?** Our subscriber list and reporting data is backed by a separate analytics system that offers richer query capabilities than our transactional database. The trade-off is a short sync delay. The two systems converge reliably — unlike some caching layers that can return stale data indefinitely. *** Have questions about how eventual consistency affects a specific use case? Reach out at [support@kit.com](mailto:support@kit.com) or join the conversation in the [Kit developer community](https://kit.typeform.com/to/f8urvmPe). # Add subscriber to form Source: https://developers.kit.com/api-reference/forms/add-subscriber-to-form /api-reference/v4.json post /v4/forms/{form_id}/subscribers/{id} The subscriber being added to the form must already exist. Subscribers can be created using the "[Create a subscriber](#create-a-subscriber)" endpoint. # Add subscriber to form by email address Source: https://developers.kit.com/api-reference/forms/add-subscriber-to-form-by-email-address /api-reference/v4.json post /v4/forms/{form_id}/subscribers The subscriber being added to the form must already exist. Subscribers can be created using the "[Create a subscriber](#create-a-subscriber)" endpoint. # Bulk add subscribers to forms Source: https://developers.kit.com/api-reference/forms/bulk-add-subscribers-to-forms /api-reference/v4.json post /v4/bulk/forms/subscribers Adding subscribers to double opt-in forms will trigger sending an Incentive Email. Subscribers already added to the specified form will not receive the Incentive Email again. For more information about double opt-in see "[Double opt-in](#double-opt-in)".

The subscribers being added to the form must already exist. Subscribers can be created in bulk using the "[Bulk create subscriber](#bulk-create-subscribers)" endpoint.

See "[Bulk & async processing](#bulk-amp-async-processing)" for more information. # List forms Source: https://developers.kit.com/api-reference/forms/list-forms /api-reference/v4.json get /v4/forms Returns a cursor-paginated list of forms and landing pages in the account. Both live on this endpoint — filter with `type=embed` for embedded forms or `type=hosted` for landing pages. By default only `active` forms are returned; use `status` (`active`, `archived`, `trashed`, or `all`) to widen the search. Add `include=subscriber_count` to embed each form's subscriber count. Each form includes its `uid`, `embed_js`, and `embed_url` for embedding on your site. To see who joined via a form, use [List subscribers for a form](/api-reference/forms/list-subscribers-for-a-form); to add an existing subscriber, use [Add subscriber to form](/api-reference/forms/add-subscriber-to-form). # List subscribers for a form Source: https://developers.kit.com/api-reference/forms/list-subscribers-for-a-form /api-reference/v4.json get /v4/forms/{form_id}/subscribers Returns a cursor-paginated list of subscribers who joined through (or were added to) the form. By default only `active` subscribers are returned — use `status` (`active`, `inactive`, `bounced`, `complained`, `cancelled`, or `all`) to widen the search. Filter by when they were added to the form with `added_after`/`added_before`, or by when the subscriber record was created with `created_after`/`created_before`. Set `slim=true` to omit expensive optional fields for a faster, smaller response. # Proof Key for Code Exchange (PKCE) flow Source: https://developers.kit.com/api-reference/oauth-proof-key-for-code-exchange-flow This guide will help you understand how to set up a PKCE OAuth flow for your Kit app. For more details on which flow to use or how to set up your OAuth app within Kit, please refer to the [more general "Authentication" guide](/api-reference/authentication). When a user installs your app from the Kit App Store, Kit redirects them to the `Authorization URL` you've configured. ``` https://example.com/kit/oauth?redirect=https://app.kit.com/apps/1?success=true ``` From here, your app should present the user a screen to sign in (or sign up). Kit will append a redirect query parameter to your Authorization URL that you will need to save in order to complete the flow. Before redirecting the user to the authorization server, the app must first generate a secret code verifier and challenge.

The code verifier is a cryptographically random string using the characters A-Z, a-z, 0-9, and the punctuation characters -.\_\~ (hyphen, period, underscore, and tilde), between 43 and 128 characters long.

Once the app has generated the code verifier, it uses that to create the code challenge. The code challenge is a BASE64-URL-encoded string of the SHA256 hash of the code verifier.

The app will need to store the `code_verifier` for later use.

After the user successfully authenticates with your app and the code verifier and challenge have been generated, redirect them to Kit's OAuth server to request their identity. The value supplied to redirect\_uri must be one of the Redirect URIs configured in your app's settings, found on the *Distribution* tab. ```http theme={null} https://api.kit.com/v4/oauth/authorize? client_id=YOUR_CLIENT_ID& response_type=code& redirect_uri=https://oauth2.example.com/callback&code_challenge=N2U2ZjNiNDEzZDE4NzkwYzYyYTM5ZjEwMzM1NDUzY2IwYTNlNWM5ODQ2NWQyNGU5ZTdiMjZiY2E4Njc5ZjY3Zg& code_challenge_method=S256& state=DEF456 ``` Your app's Client ID `code` URI to redirect to A BASE64-URL-encoded string of the SHA256 hash of the code\_verifier `S256` Default scope is `public`. Fine-grained access control via scopes coming soon. Custom state to pass to the `redirect_uri` and/or to protect from XSRF Unique, human-readable identifier for a tenant of a multi-tenant app. Found on the "Authentication" tab in your app settings: Example Kit app configuration Kit will present a consent screen that asks the user to grant or refuse your app access to their account. Kit OAuth page If the user grants access, Kit redirects the user back to the `redirect_uri` you provided when requesting the user's identity in step 2.

Kit appends a `code` query param with a temporary authorization code.

Kit also appends a state query param with the same value sent in the authorization request. This check helps verify that the user, not a malicious script, is making the request and reduces the risk of CSRF attacks. ``` https://oauth2.example.com/callback? code=mrApixZzMPnYO28KoeIZxn2mvom1Tx48S9iyrQVYVE8& state=DEF456 ```
Your app uses the authorization code provided to obtain a refresh and access token. ``` POST https://api.kit.com/v4/oauth/token ``` With a body like so: ```json theme={null} { "client_id": "YOUR_CLIENT_ID", "code_verifier": "add75a87509bca16dead084e7908824c8373cdeeb28341ae44713a6879f47be8f8fe6edfe9b8fa6917535e", "grant_type": "authorization_code", "code": "abc123", "redirect_uri": "https://oauth2.example.com/callback" } ``` Your app's Client ID A cryptographically random string using the characters A-Z, a-z, 0-9, and the punctuation characters -.\_\~ (hyphen, period, underscore, and tilde), between 43 and 128 characters long. `authorization_code` The code received via the redirect uri query params The redirect URI the request is coming from (must be one of your app's redirect URIs) ```shell shell theme={null} curl -X POST https://api.kit.com/v4/oauth/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'Accept: application/json' \ -d '{ "client_id": "YOUR_CLIENT_ID", "code_verifier": "add75a87509bca16dead084e7908824c8373cdeeb28341ae44713a6879f47be8f8fe6edfe9b8fa6917535e", "grant_type": "authorization_code", "code": "abc123", "redirect_uri": "https://oauth2.example.com/callback" }' ``` ```javascript Javascript theme={null} const headers = { 'Content-Type':'application/x-www-form-urlencoded', 'Accept':'application/json' }; const inputBody = '{ "client_id": "YOUR_CLIENT_ID", "code_verifier": "add75a87509bca16dead084e7908824c8373cdeeb28341ae44713a6879f47be8f8fe6edfe9b8fa6917535e", "grant_type": "authorization_code", "code": "abc123", "redirect_uri": "https://oauth2.example.com/callback" }'; fetch('https://api.kit.com/v4/oauth/token', { method: 'POST', body: inputBody, headers: headers }) .then(function(res) { return res.json(); }).then(function(body) { console.log(body); }); ``` ```ruby Ruby theme={null} require 'rest-client' require 'json' headers = { 'Content-Type' => 'application/x-www-form-urlencoded', 'Accept' => 'application/json' } payload = { "client_id" => "YOUR_CLIENT_ID", "code_verifier" => "add75a87509bca16dead084e7908824c8373cdeeb28341ae44713a6879f47be8f8fe6edfe9b8fa6917535e", "grant_type" => "authorization_code", "code" => "abc123", "redirect_uri" => "https://oauth2.example.com/callback" } result = RestClient.post 'https://api.kit.com/v4/oauth/token', payload, params: { }, headers: headers p JSON.parse(result) ``` ```python Python theme={null} import requests headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' } json = { "client_id": "YOUR_CLIENT_ID", "code_verifier": "add75a87509bca16dead084e7908824c8373cdeeb28341ae44713a6879f47be8f8fe6edfe9b8fa6917535e", "grant_type": "authorization_code", "code": "abc123", "redirect_uri": "https://oauth2.example.com/callback" } r = requests.post('https://api.kit.com/v4/oauth/token', headers = headers, json = json) print(r.json()) ``` **200**: Returns a token ```json theme={null} { "access_token": "YOUR_ACCESS_TOKEN_HERE", "token_type": "Bearer", "expires_in": 172800, "refresh_token": "YOUR_REFRESH_TOKEN_HERE", "scope": "public", "created_at": 1710270147 } ```

Response schema: *application/json*

Access token that can be used to make API requests on behalf of the authenticated user `Bearer` When the access token expire in seconds Refresh token that can be used to generate a new access token once this one expires The scopes available for the access token When the access token was created
Now that the user has completed the OAuth flow, your app must send the the user back to Kit using the `redirect` parameter provided at the beginning of the flow.

This will ensure the user properly navigates back to your app inside of Kit and registers that the app has been installed.

If you have set up the `Redirect URL after install` field in your app's settings, a modal prompting creators to continue their journey on your configured site will appear at this point. See this section in the [app details page guide](/kit-app-store/app-details-page#how-to-configure) for more details. example redirect flow
Your app can now make calls to Kit's API on behalf of the user by passing a `Authorization` header with the token as a `Bearer` value. ```shell shell theme={null} curl -X GET https://api.kit.com/v4/account \ -H 'Accept: application/json' \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN_HERE' ``` ```javascript Javascript theme={null} const headers = { 'Accept':'application/json', 'Authorization':'Bearer YOUR_ACCESS_TOKEN_HERE' }; fetch('https://api.kit.com/v4/account', { method: 'GET', headers: headers }) .then(function(res) { return res.json(); }).then(function(body) { console.log(body); }); ``` ```ruby Ruby theme={null} require 'rest-client' require 'json' headers = { 'Accept' => 'application/json', 'Authorization' => 'Bearer YOUR_ACCESS_TOKEN_HERE' } result = RestClient.get 'https://api.kit.com/v4/account', params: { }, headers: headers p JSON.parse(result) ``` ```python Python theme={null} import requests headers = { 'Accept': 'application/json', 'Authorization': 'Bearer YOUR_ACCESS_TOKEN_HERE' } r = requests.get('https://api.kit.com/v4/account', headers = headers) print(r.json()) ``` The access token will eventually expire and a new one must be obtained using the refresh token obtained earlier. To do this, make a `POST` call to `https://api.kit.com/v4/oauth/token`, with the following body: ```json theme={null} { "client_id": "YOUR_CLIENT_ID", "grant_type": "refresh_token", "refresh_token": "abc123" } ``` Your app's Client ID `refresh_token` The refresh token ```shell shell theme={null} curl -X POST https://api.kit.com/v4/oauth/token \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "client_id": "YOUR_CLIENT_ID", "grant_type": "refresh_token", "code": "abc123" }' ``` ```javascript Javascript theme={null} const headers = { 'Content-Type':'application/json', 'Accept':'application/json' }; const inputBody = '{ "client_id": "YOUR_CLIENT_ID", "grant_type": "refresh_token", "code": "abc123" }'; fetch('https://api.kit.com/v4/oauth/token', { method: 'POST', body: inputBody, headers: headers }) .then(function(res) { return res.json(); }).then(function(body) { console.log(body); }); ``` ```ruby Ruby theme={null} require 'rest-client' require 'json' headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' } payload = { "client_id" => "YOUR_CLIENT_ID", "grant_type" => "refresh_token", "code" => "abc123" } result = RestClient.post 'https://api.kit.com/v4/oauth/token', payload, params: { }, headers: headers p JSON.parse(result) ``` ```python Python theme={null} import requests headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' } json = { "client_id": "YOUR_CLIENT_ID", "grant_type": "refresh_token", "code": "abc123" } r = requests.post('https://api.kit.com/v4/oauth/token', headers = headers, json = json) print(r.json()) ``` **200**: Returns a token ```json theme={null} { "access_token": "YOUR_NEW_ACCESS_TOKEN_HERE", "token_type": "Bearer", "expires_in": 7200, "refresh_token": "YOUR_NEW_REFRESH_TOKEN_HERE", "scope": "public", "created_at": 1710271006 } ```

Response schema: *application/json*

Access token that can be used to make API requests on behalf of the authenticated user `Bearer` When the access token expire in seconds Refresh token that can be used to generate a new access token once this one expires The scopes available for the access token When the access token was created
# OAuth refresh token flow Source: https://developers.kit.com/api-reference/oauth-refresh-token-flow This guide will help you understand how to set up a standard refresh token flow for an OAuth app. For more details on which flow to use or how to set up your OAuth app within Kit, please refer to the [more general "Authentication" guide](/api-reference/authentication). We also offer example OAuth implementations: For Node.js, see the [`oauth-express` example](https://github.com/Kit/app-examples/tree/e627873f4a37dffcb3796b3a5ff25d4f108944c4/oauth-express) in our examples repository. ## OAuth refresh token flow When a user installs your app from the Kit App Store, Kit redirects them to the `Authorization URL` you've configured. ``` https://example.com/kit/oauth?redirect=https://app.kit.com/apps/1?success=true ``` From here, your app should present the user a screen to sign in (or sign up). Kit will append a redirect query parameter to your Authorization URL that you will need to save in order to complete the flow. If you're [externally initiating the installation](/kit-app-store/authentication#externally-initiating-installations) from your own site, you can also pass a `return_to` parameter to redirect creators back to a specific page after install. After the user successfully authenticates with your app, redirect them to Kit's OAuth server to request their identity. The value supplied to redirect\_uri must be one of the Redirect URIs configured in your app's settings, found on the *Distribution* tab. ```http theme={null} https://api.kit.com/v4/oauth/authorize? client_id=YOUR_CLIENT_ID& response_type=code& redirect_uri=https://oauth2.example.com/callback& state=DEF456 ``` Your app's Client ID `code` URI to redirect to Default scope is `public`. Fine-grained access control via scopes coming soon. Custom state to pass to the `redirect_uri` and/or to protect from XSRF Unique, human-readable identifier for a tenant of a multi-tenant app. Found on the "Authentication" tab in your app settings: Example Kit app configuration Kit will present a consent screen that asks the user to grant or refuse your app access to their account. Kit OAuth page If the user grants access, Kit redirects the user back to the `redirect_uri` you provided when requesting the user's identity in step 2.

Kit appends a `code` query param with a temporary authorization code.

Kit also appends a state query param with the same value sent in the authorization request. This check helps verify that the user, not a malicious script, is making the request and reduces the risk of CSRF attacks. ``` https://oauth2.example.com/callback? code=mrApixZzMPnYO28KoeIZxn2mvom1Tx48S9iyrQVYVE8& state=DEF456 ```
Your app uses the authorization code provided to obtain a refresh and access token. ``` POST https://api.kit.com/v4/oauth/token ``` With a body like so: ```json theme={null} { "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "grant_type": "authorization_code", "code": "abc123", "redirect_uri": "https://oauth2.example.com/callback" } ``` Your app's Client ID Your app's Client Secret `authorization_code` The code received via the redirect uri query params The redirect URI the request is coming from (must be one of your app's redirect URIs) ```shell shell theme={null} curl -X POST https://api.kit.com/v4/oauth/token \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "grant_type": "authorization_code", "code": "abc123", "redirect_uri": "https://oauth2.example.com/callback" }' ``` ```javascript Javascript theme={null} const headers = { 'Content-Type':'application/json', 'Accept':'application/json' }; const inputBody = '{ "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "grant_type": "authorization_code", "code": "abc123", "redirect_uri": "https://oauth2.example.com/callback" }'; fetch('https://api.kit.com/v4/oauth/token', { method: 'POST', body: inputBody, headers: headers }) .then(function(res) { return res.json(); }).then(function(body) { console.log(body); }); ``` ```ruby Ruby theme={null} require 'rest-client' require 'json' headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' } payload = { "client_id" => "YOUR_CLIENT_ID", "client_secret" => "YOUR_CLIENT_SECRET", "grant_type" => "authorization_code", "code" => "abc123", "redirect_uri" => "https://oauth2.example.com/callback" } result = RestClient.post 'https://api.kit.com/v4/oauth/token', payload, params: { }, headers: headers p JSON.parse(result) ``` ```python Python theme={null} import requests headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' } json = { "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "grant_type": "authorization_code", "code": "abc123", "redirect_uri": "https://oauth2.example.com/callback" } r = requests.post('https://api.kit.com/v4/oauth/token', headers = headers, json = json) print(r.json()) ``` **200**: Returns a token ```json theme={null} { "access_token": "YOUR_ACCESS_TOKEN_HERE", "token_type": "Bearer", "expires_in": 172800, "refresh_token": "YOUR_REFRESH_TOKEN_HERE", "scope": "public", "created_at": 1710270147 } ```

Response schema: *application/json*

Access token that can be used to make API requests on behalf of the authenticated user `Bearer` When the access token expire in seconds Refresh token that can be used to generate a new access token once this one expires The scopes available for the access token When the access token was created
Now that the user has completed the OAuth flow, your app must send the the user back to Kit using the `redirect` parameter provided at the beginning of the flow.

This will ensure the user properly navigates back to your app inside of Kit and registers that the app has been installed.

If you have set up the `Redirect URL after install` field in your app's settings, a modal prompting creators to continue their journey on your configured site will appear at this point. See this section in the [app details page guide](/kit-app-store/app-details-page#how-to-configure) for more details. If the installation was [externally initiated](/kit-app-store/authentication#externally-initiating-installations) with a `return_to` parameter, the creator is redirected directly to that URL instead. example redirect flow
Your app can now make calls to Kit's API on behalf of the user by passing a `Authorization` header with the token as a `Bearer` value. ```shell shell theme={null} curl -X GET https://api.kit.com/v4/account \ -H 'Accept: application/json' \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN_HERE' ``` ```javascript Javascript theme={null} const headers = { 'Accept':'application/json', 'Authorization':'Bearer YOUR_ACCESS_TOKEN_HERE' }; fetch('https://api.kit.com/v4/account', { method: 'GET', headers: headers }) .then(function(res) { return res.json(); }).then(function(body) { console.log(body); }); ``` ```ruby Ruby theme={null} require 'rest-client' require 'json' headers = { 'Accept' => 'application/json', 'Authorization' => 'Bearer YOUR_ACCESS_TOKEN_HERE' } result = RestClient.get 'https://api.kit.com/v4/account', params: { }, headers: headers p JSON.parse(result) ``` ```python Python theme={null} import requests headers = { 'Accept': 'application/json', 'Authorization': 'Bearer YOUR_ACCESS_TOKEN_HERE' } r = requests.get('https://api.kit.com/v4/account', headers = headers) print(r.json()) ``` The access token will eventually expire and a new one must be obtained using the refresh token obtained earlier. To do this, make a `POST` call to `https://api.kit.com/v4/oauth/token`, with the following body: Refresh tokens are single-use. The response below returns a new `refresh_token` — store it and use it on the next refresh. The refresh token you just submitted is now revoked; reusing it returns an `invalid_grant` error. ```json theme={null} { "client_id": "YOUR_CLIENT_ID", "grant_type": "refresh_token", "refresh_token": "abc123" } ``` Your app's Client ID `refresh_token` The refresh token ```shell shell theme={null} curl -X POST https://api.kit.com/v4/oauth/token \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "client_id": "YOUR_CLIENT_ID", "grant_type": "refresh_token", "code": "abc123" }' ``` ```javascript Javascript theme={null} const headers = { 'Content-Type':'application/json', 'Accept':'application/json' }; const inputBody = '{ "client_id": "YOUR_CLIENT_ID", "grant_type": "refresh_token", "code": "abc123" }'; fetch('https://api.kit.com/v4/oauth/token', { method: 'POST', body: inputBody, headers: headers }) .then(function(res) { return res.json(); }).then(function(body) { console.log(body); }); ``` ```ruby Ruby theme={null} require 'rest-client' require 'json' headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' } payload = { "client_id" => "YOUR_CLIENT_ID", "grant_type" => "refresh_token", "code" => "abc123" } result = RestClient.post 'https://api.kit.com/v4/oauth/token', payload, params: { }, headers: headers p JSON.parse(result) ``` ```python Python theme={null} import requests headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' } json = { "client_id": "YOUR_CLIENT_ID", "grant_type": "refresh_token", "code": "abc123" } r = requests.post('https://api.kit.com/v4/oauth/token', headers = headers, json = json) print(r.json()) ``` **200**: Returns a token ```json theme={null} { "access_token": "YOUR_NEW_ACCESS_TOKEN_HERE", "token_type": "Bearer", "expires_in": 7200, "refresh_token": "YOUR_NEW_REFRESH_TOKEN_HERE", "scope": "public", "created_at": 1710271006 } ```

Response schema: *application/json*

Access token that can be used to make API requests on behalf of the authenticated user `Bearer` When the access token expire in seconds Refresh token that can be used to generate a new access token once this one expires The scopes available for the access token When the access token was created
# OAuth token revocation Source: https://developers.kit.com/api-reference/oauth-token-revocation Call Kit's OAuth token revocation endpoint when a creator disconnects your app from your side, or when you otherwise need to invalidate the credentials Kit issued you. This endpoint follows [RFC 7009 (OAuth 2.0 Token Revocation)](https://datatracker.ietf.org/doc/html/rfc7009). After a successful revoke, Kit will: * Mark the access token (and any associated refresh token) as revoked. * Disable the matching plugin authorization for that creator on Kit's side. * Asynchronously clear any partner credentials Kit holds for that creator on your behalf. This keeps the connection state in sync on both ends, so your app doesn't end up with stale tokens after a creator disconnects from your side. ## Endpoint ``` POST https://api.kit.com/v4/oauth/revoke ``` ## Request `Content-Type: application/x-www-form-urlencoded` The Kit-issued token to revoke. Both access tokens and refresh tokens are accepted. Your app's Client ID. Your app's Client Secret. Optional hint at the token type, one of `access_token` or `refresh_token`. Speeds up the lookup. If the hint is wrong, Kit falls back to checking the other type. ```shell shell theme={null} curl -X POST https://api.kit.com/v4/oauth/revoke \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'Accept: application/json' \ -d 'token=YOUR_ACCESS_TOKEN_HERE&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET' ``` ```javascript Javascript theme={null} const headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' }; const inputBody = new URLSearchParams({ token: 'YOUR_ACCESS_TOKEN_HERE', client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' }).toString(); fetch('https://api.kit.com/v4/oauth/revoke', { method: 'POST', body: inputBody, headers: headers }) .then(function(res) { console.log(res.status); }); ``` ```ruby Ruby theme={null} require 'rest-client' headers = { 'Content-Type' => 'application/x-www-form-urlencoded', 'Accept' => 'application/json' } payload = { 'token' => 'YOUR_ACCESS_TOKEN_HERE', 'client_id' => 'YOUR_CLIENT_ID', 'client_secret' => 'YOUR_CLIENT_SECRET' } RestClient.post 'https://api.kit.com/v4/oauth/revoke', payload, headers ``` ```python Python theme={null} import requests headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' } data = { 'token': 'YOUR_ACCESS_TOKEN_HERE', 'client_id': 'YOUR_CLIENT_ID', 'client_secret': 'YOUR_CLIENT_SECRET' } r = requests.post('https://api.kit.com/v4/oauth/revoke', headers=headers, data=data) print(r.status_code) ``` ## Response **200 OK** on success, with an empty response body. Per [RFC 7009 §2.2](https://datatracker.ietf.org/doc/html/rfc7009#section-2.2), the endpoint also responds with `200 OK` if the token is unknown, already revoked, or expired. This is intentional and prevents token enumeration. Treat a `200` response as "the token is no longer valid", regardless of whether it was valid before the call. # API Overview Source: https://developers.kit.com/api-reference/overview Our API offers a host of functionality to help our creators automate a bunch of tasks to help make their lives as easy as possible and developers build apps for our ever-growing Kit App Store. As long as the authenticated account's plan is [eligible for use with the API, apps and legacy integrations](https://kit.com/pricing), you will be able to create and send broadcasts to your subscribers, manage your email list through tags and custom fields, import purchase data from your favourite e-commerce platforms and much, much more. If you are a third party developer, you can use the Kit API to link together or build on top of the Kit platform. Then, once your app is complete, you will be able to publish it to the thousands of creators using our platform today. ## New to Kit API V4 Kit API V4 introduces many new features and improved functionality from our previous versions such as: * Improved performance * Cursor-based pagination * Bulk requests and async processing * New functionality for broadcasts, including access to subscriber filters and improved HTML support * Better access to subscriber stats * Bug fixes * and much, much more. See our guide on [upgrading from V3 to V4](/api-reference/upgrading-to-v4) for more details. Kit API V4 is the latest version of our API. [API V3](/api-reference/v3/overview) is still available for use but is deprecated and will be sunset in the future. We recommend using API V4 for all new projects. ## Quick Access Learn everything you need to know to build for the Kit App Store. Using V3 of the Kit API? Find out about breaking changes between versions, to get you up and running on V4 in no time. # Pagination Source: https://developers.kit.com/api-reference/pagination Working with paginated responses ```json theme={null} { "broadcasts": [...], "pagination": { "has_previous_page": false, "has_next_page": true, "start_cursor": "WzEzXQ==", "end_cursor": "WzE0XQ==", "per_page": 100 } } ``` All of our list endpoints are paginated unless noted otherwise, using cursor based pagination. Each one will return a `pagination` object in the JSON response, with an example shown on the right. In order to navigate the results, follow these steps: * The default page size is 500 results. To change the page size, use the `per_page` query parameter. The maximum page size allowed is 1000. * To request the next page of results, use the `after` query param with the `end_cursor` value of the response. * To request the previous page of results, use the `before` query param with the `start_cursor` value of the response. * To request the total count of the collection, use the `include_total_count` query param with a value of `true`. This will complete another data query to return the total count. Expect a slightly slower response when using this option. # List posts Source: https://developers.kit.com/api-reference/posts/list-posts /api-reference/v4.json get /v4/posts Returns a cursor-paginated list of the account's posts — content published to the creator's Kit site and/or sent by email — with each post's `status` (e.g. `draft`, `scheduled`, `published`), `slug`, `public_url`, `is_paid` flag, and timestamps for publishing and sending. Post bodies are omitted by default; set `include_content=true` to embed the `content` HTML on each post (this significantly increases response size and latency for large collections — prefer [Get a post](/api-reference/posts/get-a-post) for a single body). A post that was also sent as a broadcast shares its `publication_id` with the matching broadcast from [List broadcasts](/api-reference/broadcasts/list-broadcasts). # API response codes Source: https://developers.kit.com/api-reference/response-codes Key response codes you may encounter while using the Kit API ## 401 | Unauthorized We return the Unauthorized error in a variety of situations, including: * the authentication method is configured incorrectly or not included on the call * the incorrect authentication method is used (some endpoints require OAuth) * an account no longer has access to apps (due to their trial lapsing, being on a free account, failed account payment etc.) In order to troubleshoot this yourself, please check the error message, which will help you understand why access is not being granted. If this issue persists, please reach out to support. ## 413 | Too many bulk requests If you try to enqueue too many bulk requests at once, you'll receive an error response with a `413` status code, which your code should gracefully handle. Details on [handling bulk processing can be found here](/api-reference/bulk-and-async-processing). ## 422 | Bad data When you create or update a field, you may receive an error response with status code `422` if any fields contain bad data or required fields are missing. ## 429 | Rate limiting We have different rate limits depending on the authentication strategy used: * When using OAuth, no more than 600 requests over a rolling 60 second period for given access token. * When using API Keys, no more than 120 requests over a rolling 60 second period for a given API Key. If your request rate exceeds our limits, you will receive an error response with status code `429`, which your code should gracefully handle. We recommend spacing out your requests and performing an [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) to keep within the limit. ## 500 | Internal server errors If the server is overloaded or you encounter a bug, you will receive a response with status code `500`. Try again after a short period, and if you continue to encounter an error, please raise the issue with support. # Upgrading to V4 Source: https://developers.kit.com/api-reference/upgrading-to-v4 Helping you upgrade from V3 of the API to V4 ## General updates * The URLs for the API endpoints are now `api.kit.com/v4/...` instead of `api.convertkit.com/v3/...`. They are otherwise unchanged unless called out specifically below. * V4 supports OAuth for applications and API Keys for automating simple tools and integrations for your personal account. V4 API Keys are not compatible with V3 * Our pagination mechanism has changed. We no longer support page or offset based pagination. All of [our pagination is now cursor based](/api-reference/pagination). This improves performance. * All errors are now returned with a consistent response shape. The response is a JSON object with a single attribute `errors`, an array of strings. * See below for a list of endpoints changed ## Endpoint specific updates ### Accounts * Get current account * [The response shape has changed](/api-reference/accounts/get-current-account). User and account information is now nested under `user` and `account` objects, respectively. ### Broadcasts * List broadcasts * The `page` parameter is no longer supported. To request next or previous pages, [use the `before` or `after` cursor](/api-reference/pagination) * Create a broadcast * The `email_layout_template` param is no longer supported. To specify the email template, use the `email_template_id` param. * [Query your email templates](/api-reference/email-templates/list-email-templates) to get the correct id * [The response shape has changed](/api-reference/broadcasts/create-a-broadcast). We no longer return `email_layout_template` and return an object for `email_template`. * The error response shape has changed. * Get a broadcast * [The response shape has changed](/api-reference/broadcasts/get-a-broadcast). We no longer return `email_layout_template` and return an object for `email_template`. * The error response shape has changed. * Delete a broadcast * [The response shape has changed](/api-reference/broadcasts/delete-a-broadcast). We return a 204 empty response. * The error response shape has changed. * Update a broadcast * The `email_layout_template` param is no longer supported. To specify the email template, use the `email_template_id` param. * [Query your email templates](/api-reference/email-templates/list-email-templates) to get the correct id * [The response shape has changed](/api-reference/broadcasts/update-a-broadcast). We no longer return `email_layout_template` and return an object for `email_template`. * The error response shape has changed. * Get stats * The error response shape has changed. ### Subscribers * List subscribers * The `page` parameter is no longer supported. To request next or previous pages, [use the `before` or `after` cursor](/api-reference/pagination) * The `from` parameter is no longer supported. It has been replaced with `created_after`. * The `to` parameter is no longer supported. It has been replaced with `created_before`. * The `updated_from` parameter is no longer supported. It has been replaced with `updated_after`. * The `updated_to` parameter is no longer supported. It has been replaced with `updated_before`. * Get a subscriber * The error response shape has changed. * Update a subscriber * The error response shape has changed. * Unsubscribe a subscriber * The URL path has changed. `/v3/unsubscribe` -> `/v4/subscribers/:id/unsubscribe` * We now require you to unsubscribe the subscriber via their id * If you need to find their id by email address, you can query with [List subscribers](/api-reference/subscribers/list-subscribers), `/v4/subscribers?email_address=` * [The response shape has changed](/api-reference/subscribers/unsubscribe-subscriber). It returns a 204 empty response instead of the subscriber. * The error response shape has changed. * List tags for a subscriber * [The response shape has changed](/api-reference/subscribers/list-tags-for-a-subscriber). `created_at` has been replaced with `tagged_at`. * The error response shape has changed. ### Custom Fields * Create a custom field * [The response shape has changed](/api-reference/custom-fields/create-a-custom-field). The created custom field is now returned nested under a `custom_field` attribute. * This endpoint no longer allows creating multiple custom fields. Use [Bulk create custom fields](/api-reference/custom-fields/bulk-create-custom-fields) instead. * The error response shape has changed. * Update a custom field * [The response shape has changed](/api-reference/custom-fields/update-a-custom-field). The updated custom field is returned nested under a `custom_field` attribute. * The error response shape has changed. ### Forms * List forms * [The response shape has changed](/api-reference/forms/list-forms). * Add subscriber to a form by email address * The `email` parameter is no longer supported. To add a subscriber by email address, use the `email_address` parameter. * List subscribers to a form * The URL path has changed. `/v3/forms/:id/subscriptions` -> `/v4/forms/:id/subscribers` * [The response shape has changed](/api-reference/forms/list-subscribers-for-a-form). Subscriber information is no longer nested under `subscription`. ### Purchases * List purchases * The `page` parameter is no longer supported. To request next or previous pages, [use the `before` or `after` cursor](/api-reference/pagination) * Create a purchase * The error response shape has changed. ### Sequences * List sequences * [The response shape has changed](/api-reference/sequences/list-sequences). Sequences are nested under a `sequences` attributes (instead of a `courses` attribute). * Add subscriber to a sequence by email address * The `email` parameter is no longer supported. To add a subscriber by email address, use the `email_address` parameter. * List subscribers to a sequence * The URL path has changed. `/v3/sequences/:id/subscriptions` -> `/v4/sequences/:id/subscribers` * [The response shape has changed](/api-reference/sequences/list-subscribers-for-a-sequence). Subscriber information is no longer nested under `subscription`. ### Tags * Create a tag * [The request shape has changed](/api-reference/tags/create-a-tag). Root `tag` attribute no longer required * [The response shape has changed](/api-reference/tags/create-a-tag). The returned tag is nested under a `tag` attribute. * The error response shape has changed. * This endpoint no longer allows creating multiple tags. Use [Bulk create tags](/api-reference/tags/bulk-create-tags) instead. * List subscribers for a tag * The URL path has changed. `/v3/tags/:id/subscriptions` -> `/v4/tags/:id/subscribers` * The `page` parameter is no longer supported. To request next or previous pages, [use the `before` or `after` cursor](/api-reference/pagination) * The error response shape has changed. * [The response shape has changed](/api-reference/tags/list-subscribers-for-a-tag). The root object is `subscribers` instead of `subscriptions` along with other smaller changes. * Tag a subscriber * The URL path has changed. `/v3/tags/:id/subscribe` -> `/v4/tags/:tag_id/subscribers/:id` * [The response shape has changed](/api-reference/tags/tag-a-subscriber). The root object is `subscriber` instead of `subscription` along with other smaller changes. * The error response shape has changed. * None of the optional params from V3 are supported in V4 * Tag a subscriber by email address * The URL path has changed. `/v3/tags/:id/subscribe` -> `/v4/tags/:tag_id/subscribers` * The `email` parameter is no longer supported. To add a subscriber by email address, use the `email_address` parameter. * [The response shape has changed](/api-reference/tags/tag-a-subscriber). The root object is `subscriber` instead of `subscription` along with other smaller changes. * The error response shape has changed. * None of the optional request params from V3 are supported in V4 * Remove tag from subscriber * The URL path and HTTP verb has changed. `POST /v3/tags/:id/unsubscribe` -> `DELETE /v4/tags/:tag_id/subscribers/:id` * [The response shape has changed](/api-reference/tags/remove-tag-from-subscriber). We return a 204 empty response * The error response shape has changed. * Remove tag from subscriber by email address * The URL path and HTTP verb has changed. `POST /v3/tags/:id/unsubscribe` -> `DELETE /v4/tags/:tag_id/subscribers/:id` * [The response shape has changed](/api-reference/tags/remove-tag-from-subscriber-by-email-address). We return a 204 empty response. * The error response shape has changed. ### Webhooks * The URL paths for webhooks have changed from `/automations/hooks` to `/webhooks`. * Create a webhook * [The response shape has changed](/api-reference/webhooks/create-a-webhook). The root object is `webhook` instead of `rule`. * The error response shape has changed. * Delete a webhook * [The response shape has changed](/api-reference/webhooks/delete-a-webhook). We return a 204 empty response. * The error response shape has changed. # App details page Source: https://developers.kit.com/kit-app-store/app-details-page Help creators get the most from your app by setting up a comprehensive app details page. The app details page is your app's storefront and is also where creators land after installing your app, so make it count. When you publish **Visual Automation templates** for your app, Kit may surface those templates on your app details page (for example preview and adopt actions, depending on the creator’s plan and install state). Write your description and support links so creators know how to use adopted automations—see [Visual Automation templates](/kit-app-store/va-templates#creator-facing-behavior). app details page ## What to include Your app details page needs: * A clear description of what your app does * Setup instructions * Links to documentation * Link to where any settings for your app are hosted * Support information * Images to help your app stand out A **demo video** is optional but highly recommended—it's one of the best ways to showcase your app and drive installations. It's not required to publish. See [Best practices](#best-practices) for guidance on adding one. It may also include the option to alternatively send creators to your app, or an externally hosted onboarding flow, post signup. This can be configured using the `Redirect URL after install` field. An example of this flow can be seen below. example redirect flow example redirect flow ## How to configure Go to the top navigation menu ["Automate" > "Apps" > "Build"](https://app.kit.com/apps?is=created). From there, click your app's "Edit" button to display a form with fields for the app details page. Then, update these key fields: | **Field** | **What it's for** | **Restriction** | **Required for publishing** | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------- | | App name | The name the creators see for your app across the entire Kit platform | 50 characters | ✅ | | Icon | Your app icon, which will be shown in all places where your app can be installed and used. | Under 1 MB and 1,000 x1,000 pixels. Only JPEG and PNG are accepted. | ✅ | | Summary | A short description that helps market your app, primarily shown in the card for your app on the Kit App Store main page | 90 characters | ✅ | | Description | A longer description with markdown support that helps creators understand the value your app brings them, the functionality, and how to use it. It's your opportunity to sell yourself to our massive creator base. | 5,000 characters | ✅ | | Images | Images of your app to give creators a visual representation of what it looks like and the functionality available | Up to 10 images, 4,000 x 4,000 pixels. Only JPEG and PNG are accepted. | ✅ | | Demo video URL | A video that demonstrates your app in action, to drive installations. If available, the video will appear before your app images. | YouTube video URLs only | | | Categories | The category (or categories) your app belongs to | | ✅ | | Requires a paid account | A toggle that identifies whether a paid account is required to use your app | | ✅ | | Redirect URL after install | Send users to your app, or an external onboarding flow, after they install it on Kit. Use this if setup continues on your platform. Kit will still handle authentication and track the installation before redirecting. | valid https URL | | | Allowlisted domains (optional) | Domains that are permitted as `return_to` destinations when [externally initiating an installation](/kit-app-store/authentication#externally-initiating-installations) from your site. Required if you want to redirect creators back to your site dynamically at install time. | Comma-separated list of domains (e.g. `yoursite.com`) | | | Support URL | Tell your users where to reach out to if they encounter issues using your app | valid https URL | ✅ | | Help article URL | A knowledge base/help center article for working with your app | valid https URL | ✅ | | Home page URL | Your app website URL | valid https URL | ✅ | | App settings URL | A URL that takes creators to a page in your app that allows them to manage the functionality of your app - from tag/custom field mapping, sync preferences and much more | valid https URL | | | Privacy Policy URL | A link to your privacy policy | valid https URL | ✅ | ## Markdown support We currently offer the following markdown support for the app description: * Headings * `# Heading 1` and `## Heading 2` * Deeper heading levels (`###` to `######`) are accepted, but render identically to `## Heading 2` * Formatting * `**bold**` * `*italic*` * Lists * Unordered lists (`- item`) and ordered lists (`1. Item`), including nesting * Links * `[link text](https://example.com)` — `http` and `https` URLs only * Blockquotes * `> quoted text` * Code * Inline code: `` `code` `` * Code blocks: indent lines with 4 spaces, or fence them with `~~~` (backtick ` ``` ` fences are not supported and will render as broken inline text) ### Using HTML You can also write these same elements as plain HTML — `

`, `

`, ``, ``, `
    `, `