Account
Methods in this section: 4. All methods
GET /me Available
#Who am I
Returns the tenant, the integration, the key with its effective scopes, the rate limits and the wallet state. No scope is required: a valid key is enough. Handy for a connectivity check and for picking the cell.
- Scopes
- none required, a valid key is enough
- Rate limit
- read
- Idempotency
- not required
- Successful response
200 The key and account description.
Response body
| Field | Type | Description |
|---|
tenant | object required | The tenant that owns the key. |
tenant.id | string required | |
tenant.name | string required | |
cell | string required | Platform cell: ru, en or id. Values: ru en id |
base_url | string required | Base URL of this cell API. |
integration | object required | The integration (service account) that owns the key. |
integration.id | string required | |
integration.name | string required | |
channel_id | string required, may be null | The integration "api" channel; null — the channel is not created yet. |
key | object required | |
key.key_id | string required | Public key id (not a secret). |
key.mode | string required | Values: live test |
key.scopes | array<string> required | Effective scopes: integration scopes ∩ the owner current permissions ∩ the licence. Values: conversations:read conversations:write ai_seller:invoke leads:read leads:write leads:score analytics:read analytics:write dashboards:read articles:read articles:write articles:generate strategist:read strategist:write strategist:chat webhooks:manage channels:read channels:write cards:read cards:write |
key.expires_at | string required | Key expiry, ISO 8601 UTC. |
key.ip_allowlist | array<string> required, may be null | Allowed addresses and networks (CIDR); null — any address. |
limits | object required | Request rate and paid-operation limits. |
limits.read_per_min | integer required | |
limits.write_per_min | integer required | |
limits.paid_per_min | integer required | |
limits.paid_concurrency | integer required | Concurrent paid AI operations per integration; beyond it — 429 concurrency_limit. |
limits.max_operation_cost | Money required, may be null | The per-operation cost cap in the wallet currency (a higher estimate — 402 spend_limit_reached); null — could not be read. |
wallet | object required | The tenant wallet (AI operations). |
wallet.balance | Money required, may be null | The wallet balance in roubles; null — the state is unknown or the tenant is not billed. |
wallet.state | string required | ok — fine; low — running low; negative — the balance is below zero; blocked — the wallet is blocked; unknown — the state could not be obtained. Details: GET /wallet. Values: ok low negative blocked unknown |
wallet.exchange | string required | suspended for negative and blocked: every route except /me and /wallet answers 402 wallet_suspended. Values: active suspended |
wallet.topup_url | string required | The dashboard address where the wallet is topped up. |
wallet.daily_spend | object required, may be null | The integration daily spend; null — could not be computed. |
wallet.daily_spend.limit | Money required, may be null | The integration daily spend limit; null — not set. |
wallet.daily_spend.used | Money required | Charged for the integration paid operations since the start of the cell local day. |
crm_mode | string required | The tenant CRM mode: "internal" (our CRM) or an external system. |
pii_encryption | PiiEncryption optional, may be null | EN and ID cells only: the JWE format of personal-data fields; null — the setting could not be read. Absent on the RU cell. |
Examples
Request
curl -X GET 'https://wfacademy.id/api/public/v1/me' \
-H 'Authorization: Bearer '"$API_KEY"
Response 200
{
"tenant": {
"id": "EXAMPLE_ID",
"name": "string"
},
"cell": "ru",
"base_url": "https://example.com/hooks/sapport",
"integration": {
"id": "EXAMPLE_ID",
"name": "string"
},
"channel_id": "EXAMPLE_ID",
"key": {
"key_id": "EXAMPLE_ID",
"mode": "live",
"scopes": [
"conversations:read"
],
"expires_at": "2026-10-11T09:00:00Z",
"ip_allowlist": [
"string"
]
},
"limits": {
"read_per_min": 0,
"write_per_min": 0,
"paid_per_min": 0,
"paid_concurrency": 0,
"max_operation_cost": {
"amount": null,
"currency": null
}
},
"wallet": {
"balance": {
"amount": null,
"currency": null
},
"state": "ok",
"exchange": "active",
"topup_url": "https://example.com/hooks/sapport",
"daily_spend": {
"limit": null,
"used": null
}
},
"crm_mode": "string",
"pii_encryption": {
"mode": "off",
"key": null,
"previous_key": null,
"accepted": null
}
}
Error example
{
"type": "https://wfacademy.id/developers/errors#invalid_api_key",
"title": "Invalid API key",
"status": 401,
"code": "invalid_api_key",
"detail": "EXAMPLE",
"request_id": "req_EXAMPLE"
}
Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.
Possible errors
invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited service_unavailable internal_error
PUT /me/encryption-key Available
#Public key for the JWE format
EN and ID cells only; 404 on RU. Registers or replaces the recipient public key (JWK) used to build personal-data field values in JWE format (RFC 7516). You create the key pair and keep the private key; private JWK members are rejected. EN: ECDH-ES+A256KW (P-256, P-384, X25519) or RSA-OAEP-256 (2048 bits or more). ID: ECDH-ES+A256KW (P-384, X25519) or RSA-OAEP-256 / RSA-OAEP-384 (3072 bits or more). enc is A256GCM. The same key again — changed: false. A new key applies at once; the previous one can still be requested with the Encryption-Key-Id header for 7 days. Registration turns mode off into optional; mode required is changed in the dashboard only.
- Scopes
webhooks:manage
- Rate limit
- write
- Idempotency
- not required
- Successful response
200 The setting after registration.
Request body The recipient public key.
| Field | Type | Description |
|---|
jwk | object required | The recipient public key (JWK) with an alg member. Private members (d, p, q, dp, dq, qi, k) are rejected. |
Response body
| Field | Type | Description |
|---|
mode | string required | off — personal-data fields are returned as is; optional — in JWE format when a key is registered; required — only in JWE format (no key — 409 encryption_key_required). The mode is set in the dashboard. Values: off optional required |
key | object required, may be null | The active recipient public key; null — none registered. |
key.kid | string required | Key id (from the JWK or the RFC 7638 thumbprint). |
key.alg | string required | Key management algorithm: ECDH-ES+A256KW, RSA-OAEP-256 or RSA-OAEP-384. |
key.kty | string required | Values: EC OKP RSA |
key.crv | string required, may be null | Curve (EC/OKP); null for RSA. |
key.rsa_bits | integer required, may be null | RSA modulus length, bits; null for EC/OKP. |
key.thumbprint | string required | Public key thumbprint (RFC 7638, SHA-256). |
key.created_at | string required | When the key was registered, ISO 8601 UTC. |
previous_key | object required, may be null | The previous key after a change: it can still be requested with the Encryption-Key-Id header until expires_at (7 days). |
previous_key.kid | string required | Key id (from the JWK or the RFC 7638 thumbprint). |
previous_key.alg | string required | Key management algorithm: ECDH-ES+A256KW, RSA-OAEP-256 or RSA-OAEP-384. |
previous_key.kty | string required | Values: EC OKP RSA |
previous_key.crv | string required, may be null | Curve (EC/OKP); null for RSA. |
previous_key.rsa_bits | integer required, may be null | RSA modulus length, bits; null for EC/OKP. |
previous_key.thumbprint | string required | Public key thumbprint (RFC 7638, SHA-256). |
previous_key.created_at | string required | When the key was registered, ISO 8601 UTC. |
previous_key.expires_at | string required, may be null | |
accepted | object required, may be null | Which keys this cell accepts. |
accepted.alg | array<string> required | |
accepted.crv | array<string> required | |
accepted.min_rsa_bits | integer required | |
accepted.enc | string required | |
changed | boolean required | false — the same key was already registered. |
Examples
Request
curl -X PUT 'https://wfacademy.id/api/public/v1/me/encryption-key' \
-H 'Authorization: Bearer '"$API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"jwk": {}
}'
Response 200
{
"mode": "off",
"key": {
"kid": "string",
"alg": "string",
"kty": "EC",
"crv": "string",
"rsa_bits": 0,
"thumbprint": "string",
"created_at": "2026-10-11T09:00:00Z"
},
"previous_key": {
"kid": "string",
"alg": "string",
"kty": "EC",
"crv": "string",
"rsa_bits": 0,
"thumbprint": "string",
"created_at": "2026-10-11T09:00:00Z",
"expires_at": "2026-10-11T09:00:00Z"
},
"accepted": {
"alg": [
"string"
],
"crv": [
"string"
],
"min_rsa_bits": 0,
"enc": "string"
},
"changed": false
}
Error example
{
"type": "https://wfacademy.id/developers/errors#scope_missing",
"title": "Scope is missing",
"status": 403,
"code": "scope_missing",
"detail": "EXAMPLE",
"request_id": "req_EXAMPLE"
}
Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.
Possible errors
invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited wallet_suspended service_unavailable internal_error scope_missing not_found bad_request validation_failed payload_too_large invalid_state
GET /wallet Available
#Wallet and exchange state
Returns the tenant wallet balance, its state (ok, low, negative, blocked, unknown), whether data exchange is suspended and the top-up address. No scope is required and it works even while exchange is suspended: this is where you see why the other routes answer 402 wallet_suspended. A balance below zero or a blocked wallet suspends exchange; after a top-up it resumes by itself within 30 seconds.
- Scopes
- none required, a valid key is enough
- Rate limit
- read
- Idempotency
- not required
- Successful response
200 The wallet state.
Response body
| Field | Type | Description |
|---|
balance | Money required, may be null | The wallet balance in roubles (the wallet is kept in RUB on every cell); null — the state is unknown or the tenant is not billed. |
state | string required | ok — fine; low — running low (exchange continues); negative — the balance is below zero; blocked — the wallet is blocked; unknown — the state could not be obtained. Values: ok low negative blocked unknown |
exchange | string required | suspended for negative and blocked: every route except /me and /wallet answers 402 wallet_suspended and webhooks are postponed. With unknown it stays active: free reads proceed with the Wallet-State: unknown header, paid operations are closed (503). Values: active suspended |
topup_url | string required | The dashboard address where the wallet is topped up (a staff member with the top-up permission must sign in). |
daily_spend | object required, may be null | The integration daily spend: charges for its paid operations since the start of the cell local day; null — could not be computed. |
daily_spend.limit | Money required, may be null | The integration daily spend limit; null — not set. |
daily_spend.used | Money required | Charged since the start of the cell local day. |
Examples
Request
curl -X GET 'https://wfacademy.id/api/public/v1/wallet' \
-H 'Authorization: Bearer '"$API_KEY"
Response 200
{
"balance": {
"amount": "123.45",
"currency": "IDR"
},
"state": "ok",
"exchange": "active",
"topup_url": "https://example.com/hooks/sapport",
"daily_spend": {
"limit": null,
"used": {
"amount": null,
"currency": null
}
}
}
Error example
{
"type": "https://wfacademy.id/developers/errors#invalid_api_key",
"title": "Invalid API key",
"status": 401,
"code": "invalid_api_key",
"detail": "EXAMPLE",
"request_id": "req_EXAMPLE"
}
Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.
Possible errors
invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited service_unavailable internal_error
GET /operations/{id} Available
#Get an operation
Returns an asynchronous operation created by your integration (a 202 response with the operation envelope): the queued, running, succeeded, failed or canceled state, the result resource and the cost of paid operations. The route needs no scope of its own: it requires the same scope as the request that created the operation (for example, ai_seller:invoke for an AI seller reply). An operation of another integration or tenant is a 404. Instead of frequent polling, subscribe to the operation.completed event.
- Scopes
- none required, a valid key is enough
- Rate limit
- read
- Idempotency
- not required
- Successful response
200 The operation.
Parameters
| Field | Type | Description |
|---|
id | string required | In: path. Operation id (uuid) from the operation envelope. |
Response body
| Field | Type | Description |
|---|
operation | Operation required | |
Examples
Request
curl -X GET 'https://wfacademy.id/api/public/v1/operations/EXAMPLE_ID' \
-H 'Authorization: Bearer '"$API_KEY"
Response 200
{
"operation": {
"id": "EXAMPLE_ID",
"type": "string",
"status": "queued",
"created_at": "2026-10-11T09:00:00Z",
"completed_at": "2026-10-11T09:00:00Z",
"resource": {
"type": "string",
"id": "EXAMPLE_ID"
},
"error": {
"code": "string",
"message": "string"
},
"usage": {
"cost": null,
"model": null
}
}
}
Error example
{
"type": "https://wfacademy.id/developers/errors#invalid_api_key",
"title": "Invalid API key",
"status": 401,
"code": "invalid_api_key",
"detail": "EXAMPLE",
"request_id": "req_EXAMPLE"
}
Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.
Possible errors
invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited wallet_suspended service_unavailable internal_error scope_missing pii_transfer_not_allowed not_found