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.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/4chems-com:4chems-com-onboarding-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: 3.2.0
info:
title: platform-api Onboarding API
description: 'Platform API: infrastructure, operational, and customer tiers.'
version: 0.12.1
tags:
- name: Onboarding
paths:
/v1/onboarding/route:
get:
tags:
- Onboarding
summary: Get Onboarding Route
description: 'Return the next onboarding step. If this doesn''t answer your question, call
`start_inquiry` to ask 4chems directly or request a call.
Anonymous call (no token): returns action=register with no URL and a
`next_step` naming `start_account_registration` and `start_device_login`
(UC-091).
A presented but invalid/expired token returns 401 (no silent fallback).
Authenticated call: existing routing logic (email_verified gate, domain
lookup, etc.) — behaviour is unchanged from pre-UC-053 (UC-014).
The email domain is derived from the token''s ``email`` claim only.
Zero DB reads are performed before the email_verified gate (NFR-006 pt 3).'
operationId: get_onboarding_route
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/RoutingOut'
security: []
x-anonymous: true
/v1/onboarding/access-requests:
post:
tags:
- Onboarding
summary: Create Access Request Endpoint
description: 'Request access to the tenant that owns the caller''s verified email domain.
Idempotent: a repeat POST while a pending request exists returns the same
operation envelope without creating a duplicate row or notification.
A dismissed request allows a fresh re-ask (rate-limited to 3/day, NFR-002).'
operationId: create_access_request
responses:
'202':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/OperationOut'
security:
- HTTPBearer: []
x-destructive: false
x-idempotent: true
/v1/onboarding/access-requests/{access_request_id}:approve:
post:
tags:
- Onboarding
summary: Approve Access Request Endpoint
description: 'Approve a pending access request: issue a `member` invitation to the
requester and resolve the request (level `admin` or `owner` of the
request''s tenant). The response carries the single-use invitation token
to hand to the requester, who accepts via `accept_invitation`. 409
`access_request.not_pending` unless the request is pending.'
operationId: approve_access_request
security:
- HTTPBearer: []
parameters:
- name: access_request_id
in: path
required: true
schema:
type: string
format: uuid
title: Access Request Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AccessRequestApprovedOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
x-destructive: false
x-idempotent: false
/v1/onboarding/access-requests/{access_request_id}:dismiss:
post:
tags:
- Onboarding
summary: Dismiss Access Request Endpoint
description: 'Dismiss a pending access request (level `admin` or `owner` of the
request''s tenant). The requester is mailed — naming the tenant, never the
dismissing admin — and may ask again. 409 `access_request.not_pending`
unless the request is pending.'
operationId: dismiss_access_request
security:
- HTTPBearer: []
parameters:
- name: access_request_id
in: path
required: true
schema:
type: string
format: uuid
title: Access Request Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AccessRequestOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
x-destructive: true
x-idempotent: false
/v1/onboarding/access-requests/{access_request_id}:withdraw:
post:
tags:
- Onboarding
summary: Withdraw Access Request Endpoint
description: 'Withdraw the caller''s own pending access request; no notification is
sent. Any other caller gets 404 `access_request.not_found`, the same as
for an unknown id. 409 `access_request.not_pending` unless pending.'
operationId: withdraw_access_request
security:
- HTTPBearer: []
parameters:
- name: access_request_id
in: path
required: true
schema:
type: string
format: uuid
title: Access Request Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AccessRequestOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
x-destructive: true
x-idempotent: false
/v1/onboarding/tenants:
post:
tags:
- Onboarding
summary: Create Self Service Tenant
description: 'Create a 7-day trial tenant for the caller''s business email domain (UC-052).
Needs a verified email on a business domain and `attestation: true`; answers
`auth.email_not_verified`, `tenant.attestation_required`,
`tenant.free_mail_domain` (free-mail address), `tenant.domain_exists`
(a live trial tenant on the domain) or `trial.exists_for_domain` (the
domain already had its one trial; call `start_inquiry`). The trial lasts
exactly 7 days; `ttl` / `expires_at` in the body are ignored.
Domain is derived from the verified token email claim (NFR-006 pt 4).
The caller becomes trial admin; the tenant is trial by construction
(UC-044 flavour, ≤7-day expiry, guaranteed teardown) and cannot be
made permanent via this endpoint.
Guard order (nothing persisted on any failure):
1. email_verified gate (NFR-006 pt 3)
2. attestation=true (NFR-006 §8a)
3. per-principal rate limit (NFR-002 pt 2, ≤10/min)
4. free-mail domain block (NFR-006 pt 5)
5. live-tenant guard, then trial-once (UC-099) — no re-trial after teardown'
operationId: create_self_service_tenant
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SelfServiceTenantCreateIn'
required: true
responses:
'202':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/OperationOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- HTTPBearer: []
x-destructive: false
x-idempotent: false
/v1/onboarding/entry:
post:
tags:
- Onboarding
summary: Check Entry
description: 'Retired (UC-091): always answers 410 ``onboarding.entry_retired``.
The entry check revealed whether an address has an account (UC-085 D2).
No Keycloak request is made, with or without a token. Removed in the next
minor release.'
operationId: check_entry
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EntryCheckIn'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
deprecated: true
security: []
x-anonymous: true
x-no-mcp: true
/v1/onboarding/enrichment:
post:
tags:
- Onboarding
summary: Enrich Domain Endpoint
description: 'Fetch the caller''s domain imprint and suggest legal-entity details (UC-057).
Empty body (domain comes from the token email claim only — NFR-006 pt 4).
Suggest-only: always 200, never errors the wizard. All-null on any failure.
Free-mail domains return all-null without any outbound fetch (AC4).
Per-principal rate limit: RATE_LIMIT_ENRICHMENT_PER_MINUTE (default 5/min).'
operationId: enrich_domain
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/EnrichmentOut'
security:
- HTTPBearer: []
x-destructive: false
x-idempotent: true
x-open-world: true
/v1/onboarding/login/device:
post:
tags:
- Onboarding
summary: Start Device Login
description: 'Sign in to an existing account by device login (UC-091, RFC 8628). Anonymous,
empty body; the account must exist (start_account_registration, then set the
password through the emailed link). Show `verification_uri` (or
`verification_uri_complete`) and `user_code` to the human, who signs in there,
then call `verify_device_login` with `challenge_id`, waiting `interval` seconds
between calls. The response never contains the Keycloak device_code or the
agent client secret.'
operationId: start_device_login
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceChallengeOut'
security: []
x-destructive: false
x-idempotent: false
x-anonymous: true
/v1/onboarding/login/device/verify:
post:
tags:
- Onboarding
summary: Verify Device Login
description: 'Poll a device login to token issuance (UC-091, RFC 8628). Anonymous. While
the human has not signed in yet it answers `{"status": "pending",
"retry_after": }`: wait `retry_after` seconds and call again with the
same `challenge_id`. On approval it returns Keycloak''s token set
(`access_token`, `refresh_token`, `expires_in`, ...) exactly once; pass the
`access_token` as `authorization` (''Bearer '') to every other tool.'
operationId: verify_device_login
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceVerifyIn'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security: []
x-destructive: false
x-idempotent: false
x-anonymous: true
/v1/onboarding/registrations/device:
post:
tags:
- Onboarding
summary: Start Registration
description: 'Deprecated — use start_device_login (same behaviour).
Start a Keycloak device-authorization grant (UC-075, RFC 8628).
Anonymous, empty body. Show the returned `verification_uri` (or
`verification_uri_complete`) and `user_code` to a human on any device;
they register (self-registration) or log in there. Then call
`verify_registration` with `challenge_id`, sleeping `interval` seconds
between calls while it answers 202. The response never contains the
Keycloak device_code or the agent client secret.'
operationId: start_registration
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceChallengeOut'
deprecated: true
security: []
x-destructive: false
x-idempotent: false
x-replaced-by: start_device_login
x-anonymous: true
/v1/onboarding/registrations/device/verify:
post:
tags:
- Onboarding
summary: Verify Registration
description: 'Deprecated — use verify_device_login (same behaviour).
Poll a device-authorization challenge to token issuance (UC-075, RFC 8628).
Anonymous. While the human has not yet acted, answers 202
`{"status": "pending", "retry_after": }` — sleep that many
seconds and call again with the same `challenge_id`. On approval,
answers 200 with Keycloak''s token set (`access_token`, `refresh_token`,
`expires_in`, ...) handed over exactly once; every existing token-gated
tool then works unchanged with that `access_token`.'
operationId: verify_registration
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceVerifyIn'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
deprecated: true
security: []
x-destructive: false
x-idempotent: false
x-replaced-by: verify_device_login
x-anonymous: true
/v1/onboarding/registrations/account:
post:
tags:
- Onboarding
summary: Start Account Registration
description: 'Start registering a 4chems account with a business email address (`email`,
`name`, optional `company`). Anonymous. Emails a 6-digit code to the address;
no account exists yet. Ask the user for the code, then call
verify_account_registration with the returned challenge_id and code. Free-mail
addresses (gmail.com, ...) are refused: use the company address, or call
start_inquiry. The answer is the same whether or not the address already has
an account.'
operationId: start_account_registration
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AccountRegistrationIn'
required: true
responses:
'201':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AccountChallengeOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security: []
x-destructive: false
x-idempotent: false
x-anonymous: true
/v1/onboarding/registrations/account/verify:
post:
tags:
- Onboarding
summary: Verify Account Registration
description: 'Verify the emailed 6-digit code (`challenge_id`, `code`) and create the
4chems account. Never guess or retry codes, and never ask the user for a
password.
Anonymous. On a matching code, creates the user (email already verified)
and Keycloak mails a link where the user sets their own password; no
password passes through this API or the agent. Then call
start_device_login to sign in. Answers 409 registration.exists when the
address already has an account, and 503 registration.idp_unavailable on
a Keycloak failure — retry with the same challenge_id and code.'
operationId: verify_account_registration
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AccountVerifyIn'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AccountCreatedOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security: []
x-destructive: false
x-idempotent: false
x-anonymous: true
components:
schemas:
AccountChallengeOut:
properties:
challenge_id:
type: string
format: uuid
title: Challenge Id
expires_in_seconds:
type: integer
title: Expires In Seconds
next_step:
type: string
title: Next Step
type: object
required:
- challenge_id
- expires_in_seconds
- next_step
title: AccountChallengeOut
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
input:
title: Input
ctx:
type: object
title: Context
type: object
required:
- loc
- msg
- type
title: ValidationError
EnrichmentOut:
properties:
legal_entity_name:
anyOf:
- type: string
- type: 'null'
title: Legal Entity Name
entity_type:
anyOf:
- type: string
- type: 'null'
title: Entity Type
country:
anyOf:
- type: string
- type: 'null'
title: Country
confidence:
type: number
title: Confidence
default: 0.0
source_url:
anyOf:
- type: string
- type: 'null'
title: Source Url
type: object
title: EnrichmentOut
description: 'Response for POST /v1/onboarding/enrichment (UC-057).
Suggest-only: all fields are nullable; confidence=0 means no suggestion.
The UI must fall back to manual entry when confidence is low.'
AccountVerifyIn:
properties:
challenge_id:
type: string
format: uuid
title: Challenge Id
code:
type: string
maxLength: 32
minLength: 1
title: Code
type: object
required:
- challenge_id
- code
title: AccountVerifyIn
description: Request body for POST /v1/onboarding/registrations/account/verify (UC-089).
AccountCreatedOut:
properties:
account:
type: string
const: created
title: Account
next_step:
type: string
title: Next Step
type: object
required:
- account
- next_step
title: AccountCreatedOut
EntryCheckIn:
properties:
email:
type: string
format: email
title: Email
type: object
required:
- email
title: EntryCheckIn
description: Request body for POST /v1/onboarding/entry (UC-055).
DeviceChallengeOut:
properties:
challenge_id:
type: string
format: uuid
title: Challenge Id
verification_uri:
type: string
title: Verification Uri
verification_uri_complete:
type: string
title: Verification Uri Complete
user_code:
type: string
title: User Code
expires_in:
type: integer
title: Expires In
interval:
type: integer
title: Interval
type: object
required:
- challenge_id
- verification_uri
- verification_uri_complete
- user_code
- expires_in
- interval
title: DeviceChallengeOut
description: 'Response for POST /v1/onboarding/registrations/device (UC-075).
Never carries the Keycloak device_code or the agent client secret.'
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
BotSignalsIn:
properties:
honeypot:
anyOf:
- type: string
- type: 'null'
title: Honeypot
submit_timing_ms:
anyOf:
- type: integer
- type: 'null'
title: Submit Timing Ms
type: object
title: BotSignalsIn
description: 'Advisory bot-detection signals from the onboarding wizard (UC-052).
Recorded and fed to scoring; never used as a hard gate.'
NextStepOut:
properties:
operation:
type: string
title: Operation
documents:
items:
additionalProperties:
anyOf:
- type: string
- type: 'null'
type: object
type: array
title: Documents
type: object
required:
- operation
- documents
title: NextStepOut
description: 'What a `member` must do next: record consents to the listed documents (UC-093).'
ErrorDetail:
properties:
code:
type: string
title: Code
message:
type: string
title: Message
type: object
required:
- code
- message
title: ErrorDetail
SelfServiceTenantCreateIn:
properties:
legal_entity:
$ref: '#/components/schemas/LegalEntityIn'
attestation:
type: boolean
title: Attestation
bot_signals:
anyOf:
- $ref: '#/components/schemas/BotSignalsIn'
- type: 'null'
type: object
required:
- legal_entity
- attestation
title: SelfServiceTenantCreateIn
description: 'Request body for POST /v1/onboarding/tenants (UC-052).
The domain is derived from the caller''s verified token email claim
(NFR-006 pt 4) — it is not a request parameter.
attestation must be true; the endpoint raises 422 if false (NFR-006 §8a).'
DeviceVerifyIn:
properties:
challenge_id:
type: string
format: uuid
title: Challenge Id
type: object
required:
- challenge_id
title: DeviceVerifyIn
description: Request body for POST /v1/onboarding/registrations/device/verify (UC-075).
SubscriptionState:
type: string
enum:
- trialing
- expired
- erased
- active
title: SubscriptionState
description: Commercial state (UC-096, UC-086 D7); `TrialStatus` is provisioning only.
RoutingOut:
properties:
action:
type: string
title: Action
x-extensible-enum:
- wizard
- wizard_no_tenant_domain
- request_access
- trial_used
- member
- register
next_step:
anyOf:
- type: string
- $ref: '#/components/schemas/NextStepOut'
- type: 'null'
title: Next Step
tenant_name:
anyOf:
- type: string
- type: 'null'
title: Tenant Name
registration_url:
anyOf:
- type: string
- type: 'null'
title: Registration Url
registration_methods:
anyOf:
- items:
type: string
type: array
- type: 'null'
title: Registration Methods
subscription:
anyOf:
- $ref: '#/components/schemas/SubscriptionOut'
- type: 'null'
type: object
required:
- action
title: RoutingOut
AccessRequestApprovedOut:
properties:
id:
type: string
format: uuid
title: Id
tenant_id:
type: string
format: uuid
title: Tenant Id
status:
type: string
title: Status
created_at:
type: string
format: date-time
title: Created At
updated_at:
type: string
format: date-time
title: Updated At
invitation_id:
type: string
format: uuid
title: Invitation Id
token:
type: string
title: Token
type: object
required:
- id
- tenant_id
- status
- created_at
- updated_at
- invitation_id
- token
title: AccessRequestApprovedOut
description: '`approve_access_request` response: the issued invitation, with its
single-use accept token for the admin to hand to the requester.'
SubscriptionOut:
properties:
state:
$ref: '#/components/schemas/SubscriptionState'
expires_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Expires At
grace_ends_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Grace Ends At
type: object
required:
- state
- expires_at
- grace_ends_at
title: SubscriptionOut
AccessRequestOut:
properties:
id:
type: string
format: uuid
title: Id
tenant_id:
type: string
format: uuid
title: Tenant Id
status:
type: string
title: Status
created_at:
type: string
format: date-time
title: Created At
updated_at:
type: string
format: date-time
title: Updated At
type: object
required:
- id
- tenant_id
- status
- created_at
- updated_at
title: AccessRequestOut
description: An access request after approve / dismiss / withdraw (UC-094).
AccountRegistrationIn:
properties:
email:
type: string
title: Email
name:
type: string
maxLength: 120
minLength: 1
title: Name
company:
anyOf:
- type: string
maxLength: 120
- type: 'null'
title: Company
type: object
required:
- email
- name
title: AccountRegistrationIn
description: Request body for POST /v1/onboarding/registrations/account (UC-088).
OperationOut:
properties:
operation_id:
type: string
format: uuid
title: Operation Id
status:
type: string
title: Status
type:
type: string
title: Type
target_type:
anyOf:
- type: string
- type: 'null'
title: Target Type
target_id:
anyOf:
- type: string
- type: 'null'
title: Target Id
result:
anyOf:
- {}
- type: 'null'
title: Result
error:
anyOf:
- $ref: '#/components/schemas/ErrorDetail'
- type: 'null'
created_at:
type: string
format: date-time
title: Created At
finished_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Finished At
type: object
required:
- operation_id
- status
- type
- created_at
title: OperationOut
LegalEntityIn:
properties:
country:
type: string
pattern: ^[A-Z]{2}$
title: Country
legal_entity_type:
type: string
pattern: ^[A-Z]{2,3}_[A-Z0-9][A-Z0-9_]*$
title: Legal Entity Type
other_label:
anyOf:
- type: string
maxLength: 80
minLength: 2
- type: 'null'
title: Other Label
name:
type: string
maxLength: 200
minLength: 1
title: Name
type: object
required:
- country
- legal_entity_type
- name
title: LegalEntityIn
description: "Composite legal-entity input validated against the country catalog.\n\nRules:\n- *country* must be a supported market (validated at field level via SupportedCountry,\n caught in the model validator against the loaded catalog).\n- *legal_entity_type* must appear in that country's catalog file.\n- When *legal_entity_type* is ``<CC>_OTHER``, *other_label* is required (2-80 chars).\n- For any other type, *other_label* must be absent."
securitySchemes:
HTTPBearer:
type: http
scheme: bearer