Summary and recommendation
ConvertKit's v4 API (base URL: `https://api.convertkit.com/v4`) supports Bearer token auth via API Secret or OAuth 2.0. OAuth is preferred for third-party integrations; access tokens do not expire by default but can be revoked, so a refresh token flow should be implemented for long-lived integrations.
The API covers subscriber lifecycle management, tag operations, custom fields, and webhook registration - but has no SCIM endpoint and no team-member provisioning surface whatsoever.
For identity graph use cases, subscriber state (`active`, `inactive`, `bounced`, `complained`, `cancelled`) combined with tags and custom fields is the closest available construct for modeling user identity and entitlements within ConvertKit.
API quick reference
| Has user API | Yes |
| Auth method | API Key (Bearer token) or OAuth 2.0 (v4) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | N/A |
Authentication
Auth method: API Key (Bearer token) or OAuth 2.0 (v4)
Setup steps
- For personal/server-side use: navigate to Settings > Advanced > API in your ConvertKit account and copy your API Secret.
- Pass the API Secret as a Bearer token in the Authorization header: 'Authorization: Bearer
'. - For OAuth 2.0 (third-party integrations): register an OAuth application at developers.convertkit.com, obtain client_id and client_secret.
- Redirect users to https://app.convertkit.com/oauth/authorize with response_type=code, client_id, redirect_uri, and scope parameters.
- Exchange the authorization code for an access token via POST to https://api.convertkit.com/oauth/token.
- Use the returned access_token as a Bearer token in subsequent API requests.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| public | Read-only access to public account data | Listing forms, sequences, tags |
| write_subscribers | Create, update, and unsubscribe subscribers | Subscriber create/update/delete operations |
| read_subscribers | Read subscriber data including email and custom fields | Listing and fetching subscriber details |
| write_forms | Manage form subscriptions | Adding subscribers to forms |
| write_tags | Create tags and tag/untag subscribers | Tag management operations |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Unique subscriber identifier | auto-generated | immutable | Used as path parameter for subscriber-specific endpoints |
| email_address | string | Subscriber's email address | required | updatable | Must be unique per account |
| first_name | string | Subscriber's first name | optional | updatable | |
| state | string | Subscription state: active, inactive, bounced, complained, cancelled | auto-set to active | updatable via unsubscribe endpoint | Cannot be set directly to 'active' via update; use re-subscribe flow |
| created_at | ISO 8601 datetime | Timestamp when subscriber was created | auto-generated | immutable | |
| fields | object | Key-value map of custom field values keyed by field slug | optional | updatable | Custom fields must be pre-created in the account before use |
| tagged_at | ISO 8601 datetime | Timestamp when a tag was applied (returned in tag-subscriber context) | auto-generated on tag | immutable | Only present in tag-subscriber list responses |
Core endpoints
List subscribers
- Method: GET
- URL:
https://api.convertkit.com/v4/subscribers - Watch out for: Default page size is 500; use 'after' cursor from pagination.end_cursor for subsequent pages.
Request example
GET /v4/subscribers?per_page=100&status=active
Authorization: Bearer <api_secret>
Response example
{
"subscribers": [{"id":1,"email_address":"user@example.com","state":"active","fields":{}}],
"pagination": {"has_previous_page":false,"has_next_page":true,"start_cursor":"abc","end_cursor":"xyz"}
}
Get subscriber
- Method: GET
- URL:
https://api.convertkit.com/v4/subscribers/{id} - Watch out for: Lookup by email is not supported on this endpoint; use GET /v4/subscribers?email_address= instead.
Request example
GET /v4/subscribers/123456
Authorization: Bearer <api_secret>
Response example
{
"subscriber": {
"id": 123456,
"email_address": "user@example.com",
"state": "active",
"fields": {"last_name": "Smith"}
}
}
Create/update subscriber (upsert)
- Method: POST
- URL:
https://api.convertkit.com/v4/subscribers - Watch out for: This endpoint upserts: if the email already exists, it updates the subscriber rather than creating a duplicate.
Request example
POST /v4/subscribers
Authorization: Bearer <api_secret>
{"email_address":"user@example.com","first_name":"Jane","fields":{"plan":"pro"}}
Response example
{
"subscriber": {
"id": 123456,
"email_address": "user@example.com",
"state": "active"
}
}
Update subscriber
- Method: PUT
- URL:
https://api.convertkit.com/v4/subscribers/{id} - Watch out for: Custom fields not included in the request are NOT cleared; only provided fields are updated.
Request example
PUT /v4/subscribers/123456
Authorization: Bearer <api_secret>
{"first_name":"Jane","fields":{"plan":"enterprise"}}
Response example
{
"subscriber": {
"id": 123456,
"email_address": "user@example.com",
"first_name": "Jane"
}
}
Unsubscribe subscriber
- Method: POST
- URL:
https://api.convertkit.com/v4/subscribers/{id}/unsubscribe - Watch out for: Unsubscribing sets state to 'inactive'. There is no API endpoint to reactivate an unsubscribed contact.
Request example
POST /v4/subscribers/123456/unsubscribe
Authorization: Bearer <api_secret>
Response example
{
"subscriber": {
"id": 123456,
"state": "inactive"
}
}
Tag a subscriber
- Method: POST
- URL:
https://api.convertkit.com/v4/tags/{tag_id}/subscribers - Watch out for: Tag must exist before tagging; use POST /v4/tags to create it first.
Request example
POST /v4/tags/789/subscribers
Authorization: Bearer <api_secret>
{"email_address":"user@example.com"}
Response example
{
"subscriber": {
"id": 123456,
"email_address": "user@example.com",
"state": "active"
}
}
Remove tag from subscriber
- Method: DELETE
- URL:
https://api.convertkit.com/v4/tags/{tag_id}/subscribers/{subscriber_id} - Watch out for: Returns 204 on success with no body. Returns 404 if the tag-subscriber relationship does not exist.
Request example
DELETE /v4/tags/789/subscribers/123456
Authorization: Bearer <api_secret>
Response example
HTTP 204 No Content
List tags
- Method: GET
- URL:
https://api.convertkit.com/v4/tags - Watch out for: Tags are account-level; all tags are returned without pagination (typically small sets).
Request example
GET /v4/tags
Authorization: Bearer <api_secret>
Response example
{
"tags": [
{"id": 789, "name": "customer", "created_at": "2024-01-01T00:00:00Z"}
]
}
Rate limits, pagination, and events
- Rate limits: ConvertKit enforces per-account rate limits on API v4. Requests exceeding the limit receive HTTP 429 responses.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: HTTP 429 is returned when the limit is exceeded. The Retry-After header indicates when to resume. Bulk operations (e.g., bulk tag) count as single requests.
- Pagination method: cursor
- Default page size: 500
- Max page size: 1000
- Pagination pointer: after (cursor-based, returned as next cursor in response metadata)
| Plan | Limit | Concurrent |
|---|---|---|
| All plans | 120 requests per minute | 0 |
- Webhooks available: Yes
- Webhook notes: ConvertKit supports webhooks (called 'Rules' or automation triggers) that POST event payloads to a configured URL when subscriber events occur.
- Alternative event strategy: Webhooks can also be created programmatically via POST /v4/webhooks endpoint with event and target_url parameters.
- Webhook events: subscriber.subscriber_activate, subscriber.subscriber_unsubscribe, subscriber.subscriber_bounce, subscriber.subscriber_complain, subscriber.form_subscribe, subscriber.course_subscribe, subscriber.course_complete, subscriber.link_click, subscriber.product_purchase, subscriber.tag_add, subscriber.tag_remove
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: N/A
- Endpoint: Not documented
Limitations:
- ConvertKit does not support SCIM provisioning on any plan.
- No SSO (SAML/OIDC) integration is available.
- Team member management is done via in-app invitations only.
- Creator Pro plan allows unlimited team members but no automated provisioning.
Common scenarios
Three integration patterns cover the majority of programmatic use cases.
First, subscriber provisioning: POST /v4/subscribers upserts on email address - if the record exists, it updates rather than duplicates; custom field slugs not matching account configuration are silently dropped, which is a common source of data loss.
Second, deprovisioning: there is no hard-delete; POST /v4/subscribers/{id}/unsubscribe sets state to inactive and cannot be reversed via API - treat this as a one-way operation. Third, bulk sync: use GET /v4/subscribers with per_page=1000 and cursor-based pagination (after param from `pagination.
end_cursor); for incremental syncs on large accounts (100k+ subscribers), scope requests with the updated_afterfilter to avoid full-list pagination latency. Supplement polling with webhooks registered viaPOST /v4/webhooks for real-time delta events (subscriber.
subscriber_activate, subscriber. subscriber_unsubscribe, subscriber.
tag_add, subscriber. tag_remove`).
Provision a new user (subscriber) with custom attributes
- POST /v4/subscribers with email_address, first_name, and fields object containing custom field slugs and values.
- If the email already exists, the endpoint upserts and returns the existing subscriber with updated fields.
- Optionally POST /v4/tags/{tag_id}/subscribers to apply a role or plan tag to the subscriber.
Watch out for: Custom field slugs must match exactly what is configured in the ConvertKit account; mismatched slugs are silently dropped.
Deprovision a user (unsubscribe and remove tags)
- GET /v4/subscribers?email_address=user@example.com to retrieve the subscriber ID.
- DELETE /v4/tags/{tag_id}/subscribers/{subscriber_id} for each tag to remove.
- POST /v4/subscribers/{id}/unsubscribe to set state to 'inactive'.
Watch out for: Unsubscribing via API triggers the same suppression as a manual unsubscribe; the subscriber cannot be reactivated via API.
Sync subscriber list with an external system
- GET /v4/subscribers with status=active and per_page=1000 to fetch the first page.
- Use pagination.end_cursor as the 'after' parameter in subsequent requests until has_next_page is false.
- Compare returned subscriber records against the external system and issue PUT /v4/subscribers/{id} for any field discrepancies.
- Register a webhook via POST /v4/webhooks for subscriber.subscriber_activate and subscriber.subscriber_unsubscribe to receive real-time delta updates.
Watch out for: Full list pagination can be slow for large accounts (100k+ subscribers); use the updated_after filter parameter to scope incremental syncs.
Why building this yourself is a trap
The most common integration traps in ConvertKit's API are version mixing, silent field drops, and irreversible state changes. API v3 and v4 coexist with different auth schemes - v3 uses query-param API keys, v4 uses Bearer tokens; mixing versions in a single integration will produce inconsistent auth failures.
Custom fields must be pre-created in the account before they can be set; unknown slugs are accepted without error but the data is discarded.
The 120 req/min rate limit is per account API key, not per IP - shared keys across multiple services aggregate toward this ceiling and will produce HTTP 429 responses under concurrent load.
Finally, the unsubscribe endpoint is a suppression action: once triggered, the subscriber record persists but cannot be reactivated programmatically, which has direct implications for any identity graph that models ConvertKit subscriber state as a reversible entitlement.
Automate ConvertKit workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.