Work with this as data
Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/blueticked-com:blueticked-com-forms-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
OpenAPI Specification
openapi: 3.2.0
info:
title: Blueticked Forms API
version: 1.0.0
description: 'Multi-channel messaging (WhatsApp, SMS, email), templates, flows, campaigns, per-recipient documents and webhooks for South African businesses, plus contacts, flows, AI agents, journeys, published sites and a commerce layer with products, orders and checkout. One send endpoint (`POST /api/v1/messages`) covers all three channels. See the request examples for the WhatsApp template, SMS, and email shapes. Authenticate every request with `Authorization: Bearer blu_live_...` (or `blu_test_...` for the sandbox). Send an `Idempotency-Key` header on the POSTs that honour it (the ones whose OpenAPI operation declares the `Idempotency-Key` parameter), and a retry of the same key replays the original response instead of repeating the write. Everywhere else the header is IGNORED, so a blind retry of a 5xx can create a second record. 29 of 48 documented POST operations deduplicate; the rest do not. ## Test mode
Keys prefixed `blu_test_` are sandbox keys: sends never reach providers,
never bill, and have their delivery lifecycle simulated (queued → sent →
delivered → read, ~5s per step). Webhook deliveries fire with `"test": true`. Separately, a `test_mode: true` field on a send is a dry run that validates the
request and returns `status: "validated"` without creating or sending anything;
it works with any key, including a live one. Deterministic magic numbers:
| Number | Behaviour |
|---|---|
| +27800000001 | Always delivers (any other number behaves the same) |
| +27800000002 | Fails at the send step with `recipient_unreachable` |
| +27800000003 | Delivers, then fires a simulated `message.received` reply |
A test key''s writes are real workspace data: a contact it creates is stored like any other, where a live
campaign could reach it. A live key refuses to send to the sandbox numbers (+27800000000 to +27800000099)
from `POST /api/v1/messages` with 400 `validation_failed`, but a campaign or a flow does not check
the number, so delete test contacts when you go live. ## Error codes
| Code | Meaning |
|---|---|
| `invalid_field_map` | The inbound field map has an unknown or ambiguous target or repeats a contact identity target. |
| `unauthorized` | The credential was refused: a missing or malformed Authorization header, an unknown key or a wrong secret, a revoked or expired key, an expired or revoked OAuth token, or an inactive workspace. Once a key''s secret checks out, details.reason says which (revoked, expired, workspace_inactive). |
| `forbidden` | The credential is valid but not allowed here: a missing scope (details.required), an address outside the key''s IP allowlist, a business-restricted key on an endpoint it cannot use, or a blu_pub_ key off the agent chat endpoint or its allowed origins. |
| `not_found` | The requested contact, template, message or campaign does not exist. |
| `validation_failed` | The request was refused before anything happened. Either the body does not fit the schema (details is the list of schema issues, each with the field''s path), or a value breaks a rule (details names the field). |
| `rate_limited` | The workspace or key exceeded a rate limit, or the calling address made too many failed authentication attempts. Wait Retry-After seconds, then retry. |
| `idempotency_conflict` | The same Idempotency-Key was reused with a different body. |
| `idempotency_in_progress` | A request with the same Idempotency-Key is still being processed; retry once it finishes. |
| `resource_in_use` | The resource cannot be deleted because other live resources still depend on it (e.g. a group targeted by a scheduled campaign). `details` lists the dependents. |
| `conflict` | The other kind of 409: the request collides with what already exists (a code or name that is taken, or a resource in a state the call does not accept, as when only a paid order can book a courier).'
contact:
name: Blueticked Developer Support
url: https://blueticked.com/docs
email: developers@blueticked.com
servers:
- url: https://app.blueticked.com
description: Production
security:
- bearerAuth: []
tags:
- name: Forms
description: Operations for forms.
paths:
/api/v1/form-submissions:
get:
operationId: list-form-submissions
summary: List form submissions
description: 'What visitors submitted through site forms, newest first. Poll with unread=true as an intake queue.
`payload` is the form''s fields exactly as the visitor filled them; the shape follows the form on the page, so read it defensively.
`unread=true` returns only submissions not yet marked read. Poll it, ingest, then PATCH each one read for an at-least-once intake loop.
`site_id` narrows to one site; `business_id` to one business''s sites (one you can access, else 403); `limit` caps the page (default 50, max 200).'
tags:
- Forms
security:
- bearerAuth:
- forms:read
x-required-scopes:
- forms:read
parameters:
- name: site_id
in: query
required: false
description: Filter by `site_id`.
schema:
type: string
- name: unread
in: query
required: false
description: Filter by `unread`.
schema:
type: string
example: 'true'
- name: limit
in: query
required: false
description: Maximum number of items to return.
schema:
type: integer
minimum: 1
maximum: 200
default: 50
responses:
'200':
description: Success
headers:
x-ratelimit-limit:
description: Requests permitted in the current window.
schema:
type: integer
x-ratelimit-remaining:
description: Requests remaining in the current window.
schema:
type: integer
x-ratelimit-reset:
description: When the current window resets.
schema:
type: string
format: date-time
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
example: true
submissions:
type: array
items:
type: object
properties:
id:
type: string
example: 4b8c2d1e-5f6a-4b7c-8d9e-0f1a2b3c4d5e
format: uuid
site_id:
type: string
example: 1d3dd43a-1f0a-4e83-ae13-4494e0d967b3
format: uuid
page_slug:
type: string
example: contact
payload:
type: object
properties:
name:
type: string
example: Naledi Dlamini
phone:
type: string
example: '+27800000001'
pattern: ^\+[1-9]\d{6,14}$
message:
type: string
example: Do you deliver to Stellenbosch?
required:
- name
- phone
- message
is_read:
type: boolean
example: false
created_at:
type: string
example: '2026-08-05T09:12:00.000Z'
format: date-time
required:
- id
- site_id
- page_slug
- payload
- is_read
- created_at
required:
- ok
- submissions
examples:
list-form-submissions:
summary: List form submissions
value:
ok: true
submissions:
- id: 4b8c2d1e-5f6a-4b7c-8d9e-0f1a2b3c4d5e
site_id: 1d3dd43a-1f0a-4e83-ae13-4494e0d967b3
page_slug: contact
payload:
name: Naledi Dlamini
phone: '+27800000001'
message: Do you deliver to Stellenbosch?
is_read: false
created_at: '2026-08-05T09:12:00.000Z'
'400':
description: Validation failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: 'Unauthorized: missing or invalid API key'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'402':
description: Insufficient wallet balance
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: 'Forbidden: scope, plan limit, or suspension'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: Conflict (idempotency-key reuse or resource in use)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: 'Understood but refused: the request is well-formed, the action is not permitted for this resource'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
headers:
x-ratelimit-limit:
description: Requests permitted in the current window.
schema:
type: integer
x-ratelimit-remaining:
description: Requests remaining in the current window.
schema:
type: integer
x-ratelimit-reset:
description: When the current window resets.
schema:
type: string
format: date-time
Retry-After:
description: Whole seconds to wait before retrying (at least 1); the same moment as x-ratelimit-reset.
schema:
type: integer
minimum: 1
'500':
description: Internal error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'502':
description: An upstream provider refused or failed the request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: Channel or send queue unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/v1/form-submissions/{id}:
patch:
operationId: mark-form-submission-read
summary: Mark a submission read
description: 'Flip the read flag once an external system has ingested it.
Both directions are allowed: `false` puts one back in the unread queue.'
tags:
- Forms
security:
- bearerAuth:
- forms:write
x-required-scopes:
- forms:write
parameters:
- name: id
in: path
required: true
description: The resource's unique identifier.
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
is_read:
type: boolean
example: true
required:
- is_read
examples:
mark-form-submission-read:
summary: Mark a submission read
value:
is_read: true
responses:
'200':
description: Success
headers:
x-ratelimit-limit:
description: Requests permitted in the current window.
schema:
type: integer
x-ratelimit-remaining:
description: Requests remaining in the current window.
schema:
type: integer
x-ratelimit-reset:
description: When the current window resets.
schema:
type: string
format: date-time
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
example: true
is_read:
type: boolean
example: true
required:
- ok
- is_read
examples:
mark-form-submission-read:
summary: Mark a submission read
value:
ok: true
is_read: true
'400':
description: Validation failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: 'Unauthorized: missing or invalid API key'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'402':
description: Insufficient wallet balance
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: 'Forbidden: scope, plan limit, or suspension'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: Conflict (idempotency-key reuse or resource in use)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'413':
description: Request body above the endpoint's size limit
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: 'Understood but refused: the request is well-formed, the action is not permitted for this resource'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
headers:
x-ratelimit-limit:
description: Requests permitted in the current window.
schema:
type: integer
x-ratelimit-remaining:
description: Requests remaining in the current window.
schema:
type: integer
x-ratelimit-reset:
description: When the current window resets.
schema:
type: string
format: date-time
Retry-After:
description: Whole seconds to wait before retrying (at least 1); the same moment as x-ratelimit-reset.
schema:
type: integer
minimum: 1
'500':
description: Internal error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'502':
description: An upstream provider refused or failed the request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: Channel or send queue unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Error:
type: object
required:
- ok
- error
properties:
ok:
type: boolean
enum:
- false
example: false
error:
type: object
required:
- code
- message
- next_steps
properties:
code:
type: string
enum:
- invalid_field_map
- unauthorized
- forbidden
- not_found
- validation_failed
- rate_limited
- idempotency_conflict
- idempotency_in_progress
- resource_in_use
- conflict
- method_not_allowed
- gone
- channel_unavailable
- queue_unavailable
- insufficient_balance
- spend_cap_reached
- plan_limit_reached
- subscription_inactive
- variables_mismatch
- button_params_mismatch
- document_invalid
- document_required
- document_not_applicable
- document_source_invalid
- document_source_required
- document_source_not_applicable
- template_kind_unsupported
- internal_error
- payload_too_large
- service_unavailable
- provider_error
- enable_requires_dashboard
- activation_requires_dashboard
- journey_not_active
description: Stable machine-readable error code.
message:
type: string
description: Human-readable explanation.
details:
type: object
additionalProperties: true
description: Optional structured validation detail.
properties:
reason:
type: string
description: 'Which failure of this code it was, when the API says. Known values:
- promotions_not_ready (conflict)
- promotion_invalid (validation_failed)
- automatic_promotion_limit (validation_failed)
- promotion_products_limit (validation_failed)
- promotion_target_missing (conflict)
- promotion_version_conflict (conflict)
- promotion_not_found (not_found)
- discount_code_taken (conflict)
- promotion_read_failed (service_unavailable)
- promotion_save_failed (service_unavailable)
- invalid_form (validation_failed)
- missing_file (validation_failed)
- invalid_contact_id (validation_failed)
- file_too_large (validation_failed)
- not_a_pdf (validation_failed)
- document_not_found (not_found)
- document_not_for_contact (validation_failed)
- invalid_form (validation_failed)
- missing_file (validation_failed)
- file_too_large (payload_too_large)
- unsupported_mime (validation_failed)
- invalid_file (validation_failed)
- quota_exceeded (validation_failed)
- upload_failed (internal_error)
- media_upload_expired (conflict)
- media_upload_invalid (validation_failed)
- media_upload_mismatch (validation_failed)
- media_upload_unavailable (service_unavailable)
- media_upload_action_invalid (validation_failed)
- payload_too_large (payload_too_large)
- body_read_failed (validation_failed)
- invalid_json (validation_failed)
- register_failed (validation_failed)
- status_unconfirmed (validation_failed)
- status_mismatch (validation_failed)
- status_read_failed (validation_failed)
- managed_by_studio (conflict)
- rotation_grace_active (conflict)
- key_revoked (conflict)
- list_not_found (not_found)
- segment_not_found (not_found)
- setup_recheck_too_soon (rate_limited)
- chat_already_answered (conflict)
- consent_evidence_unavailable (service_unavailable)
- audience_unchecked (service_unavailable)
- erasure_retrying (service_unavailable)
- sms_channel_disconnected (channel_unavailable)
- sms_channel_check_unavailable (channel_unavailable)
- revoked (unauthorized)
- expired (unauthorized)
- workspace_inactive (unauthorized)
- no_draft (validation_failed)
- invalid_draft (validation_failed)
- live_address_unavailable (service_unavailable)
- publish_failed (internal_error)
- hosting_charge_required (plan_limit_reached)
- hosting_charge_unaffordable (insufficient_balance)
- not_published (validation_failed)
- unpublish_failed (internal_error)
- business_not_found (not_found)
- business_limit_reached (plan_limit_reached)
- business_charge_required (plan_limit_reached)
- business_charge_changed (plan_limit_reached)
- business_charge_live_key_required (plan_limit_reached)
- operator_agreement_unsigned (forbidden)
- no_organization (forbidden)
- unknown_template (validation_failed)
- slug_unavailable (conflict)
- insert_failed (internal_error)
- recipient_not_found (not_found)
- consent_required (validation_failed)
- custom_identity_required (validation_failed)
- dmarc_required (validation_failed)
- business_identity_required (validation_failed)
- physical_address_required (validation_failed)
- freemail_reply_to (validation_failed)
- reply_to_not_aligned (validation_failed)
- contacts_write_required (forbidden)
- domain_in_use_elsewhere (conflict)
- domain_in_use_by_business (conflict)
- domain_registered_unclaimed (conflict)
- domain_holder_unchecked (service_unavailable)
- receive_only (conflict)
- custom_field_creation_forbidden (forbidden)
- action_invalid (validation_failed)
- base_url_required (conflict)
- key_taken (conflict)
- too_many_actions (conflict)
- website_form_channel (conflict)
- invalid_contact_ids (validation_failed)
- business_id_required (validation_failed)
- automations_retired (gone)
- studio_flow_limit_reached (conflict)
- studio_flow_count_unavailable (service_unavailable)
- definition_required (validation_failed)
- invalid_business_id (validation_failed)
- invalid_definition (validation_failed)
- compile_failed (validation_failed)
- sms_type_required (validation_failed)
- edit_version_required (validation_failed)
- conflict (conflict)
- studio_flow_live (resource_in_use)
- studio_flow_shared_entry (resource_in_use)
- journey_read_failed (service_unavailable)
- field_read_failed (service_unavailable)
- sequence_requires_send_step (validation_failed)
- invalid_trigger (validation_failed)
- flow_name_taken (conflict)
- invalid_steps (validation_failed)
- invalid_segment (validation_failed)
- invalid_email_template (validation_failed)
- invalid_whatsapp_template (validation_failed)
- unsupported_whatsapp_template (validation_failed)
- whatsapp_template_not_approved (validation_failed)
- whatsapp_template_not_attached (validation_failed)
- not_found (not_found)
- forbidden (forbidden)
- reference_outside_business (validation_failed)
- meta_refused (provider_error)
- journeys_list_failed (internal_error)
- ai_agents_list_failed (internal_error)
- form_submissions_list_failed (internal_error)
- studio_flow_save_failed (internal_error)
- automation_save_failed (internal_error)
- save_failed (internal_error)
- table_not_found (not_found)
- table_has_no_people (validation_failed)
- table_status_unknown (validation_failed)
- table_column_unknown (validation_failed)
- row_field_needs_table (validation_failed)
- table_unchecked (service_unavailable)
- assignee_not_member (validation_failed)
- assignee_cannot_open (validation_failed)
- media_pending (conflict)
- media_unavailable (not_found)
- not_whatsapp_channel (validation_failed)
- whatsapp_token_missing (channel_unavailable)
- meta_unavailable (provider_error)
- meta_unavailable (service_unavailable)
- meta_pricing_permission_required (provider_error)
Treat any other value as the code''s general case.'
next_steps:
type: array
description: What to do about it, as data. `message` tells a person what happened; this tells a program what to do next. Always present and never empty. Branch on `action`; never parse `description`.
items:
type: object
required:
- action
- by
- description
properties:
action:
type: string
description: Stable verb identifying the remedy, e.g. connect_channel, retry_after, grant_missing_scope.
example: connect_channel
by:
type: string
enum:
- caller
- human
- waiting
description: 'WHO can clear it. `caller`: fix it in code and retry. `human`: a person must act in a browser; surface it and do NOT retry, because no rewrite of this request will succeed. `waiting`: outside anyone''s control (an upstream review or outage); poll or back off.'
example: human
description:
type: string
description: One sentence, for the human reading your logs.
endpoint:
type: string
description: The API call that fixes it, when the fix is a call.
example: POST /api/v1/channels/connect-sessions
url:
type: string
description: Where a person goes, when `by` is `human`.
request_id:
type: string
example: req_b2e3d610e10f1b07468721ee98f0e1d2
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: blu_live or blu_test
description: Your API key as a Bearer token. Use a `blu_test_` key for the sandbox.