byo-sms countries
Read the currently routable country catalog for BYO-SMS coverage planning.
When to use
- Before integration country configuration.
How it works
- Reads routable country source; cold catalog may be temporarily unavailable.
Prerequisites
- Entitlement and granular pricing.
Expected outcome
Country catalog.
Next steps
- Use only offered codes; retry catalog outage later.
Usage
akedly byo-sms countries
Inspect this command offline before preparing an operation:
akedly describe "byo-sms countries" --output json
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:read.
Server request: GET /byo-sms/countries.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
idempotent: Idempotent requests retry network errors and HTTP 429/502/503/504, at most 3 tries, waiting for cooldownSeconds, retryAfter or the Retry-After header (otherwise 1 second) between tries, up to 30 seconds; a longer requested wait is returned as the error.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
List the routable country catalog. A cold catalog returns HTTP 503 COUNTRY_CATALOG_UNAVAILABLE; retry later. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are supported.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Before integration country configuration.
akedly byo-sms countries
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": [
{
"isoCode": "EG",
"name": "Egypt"
},
{
"isoCode": "US",
"name": "United States"
}
]
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms countries
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
}
],
"files": [],
"wire": {
"method": "GET",
"route": "/byo-sms/countries",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
The endpoint returns data as an array of country objects with isoCode and name. Schema permits API extensions per item.
Examples are synthetic and the routable country list can change.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"isoCode": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"isoCode",
"name"
],
"additionalProperties": true
},
"required": [
"countries"
]
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": [
{
"isoCode": "EG",
"name": "Egypt"
},
{
"isoCode": "US",
"name": "United States"
}
]
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms list
Review account BYO-SMS routes, provider, coverage and routing status.
When to use
- Choose an id or recover uncertain write.
How it works
- Lists newest-first, strips stored secrets; visible step fields can still contain literal credentials. Header/query serialize as {} even if configured.
- Stored countries [] (and a sole ALL sentinel) means catch-all; a non-empty country list limits the route to those codes.
Prerequisites
- Entitlement and granular pricing.
Expected outcome
Integration array; visibility can hide steps/fees; active does not imply effective traffic routing.
Next steps
- Inspect with get, and review pipeline/account routing independently.
Usage
akedly byo-sms list
Inspect this command offline before preparing an operation:
akedly describe "byo-sms list" --output json
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:read.
Server request: GET /byo-sms/integrations.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
idempotent: Idempotent requests retry network errors and HTTP 429/502/503/504, at most 3 tries, waiting for cooldownSeconds, retryAfter or the Retry-After header (otherwise 1 second) between tries, up to 30 seconds; a longer requested wait is returned as the error.
Output
Sensitive output: echo-scrub. echo-scrub masks secret-shaped text, credential fields, secret query parameters and JSON secret fields inside strings in the response, such as a webhook receiver echo or BYO-SMS steps; keep the output private anyway.
Full command details
List all your integrations, newest first, without pagination or filters. The server strips secrets and vodafoneSetup, exposing secretNames instead of secret values, but visible steps are returned verbatim and may contain literal credentials (for example in auth, url or bodyTemplate); keep output private. Each step's headers and query currently come back as {} even when values are saved, so an empty headers or query object does not show the saved configuration. Member permissions can hide steps or fees (stepsVisible/feeVisible). Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are supported. active does not mean sends use this integration: routing follows each pipeline setting (akedly pipelines get <id> -> data.byoSms: inherit|on|off); inherit uses the account switch (akedly whoami -> user.byoSmsEnabled, set by Akedly), while on/off override it. Vodafone rows in byo-sms list also report routingEnabled.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Choose an id or recover uncertain write.
akedly byo-sms list
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": [
{
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
],
"resolvedFeeEGP": 0.25
}
]
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms list
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
}
],
"files": [],
"wire": {
"method": "GET",
"route": "/byo-sms/integrations",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Sanitizer removes secret values and Vodafone private setup. steps/feeOverride may be omitted by member permission; stepsVisible and feeVisible state visibility. Step headers/query serialize as empty objects even if configured; do not rebuild replacement config from that view.
A Vodafone integration carries structured readiness fields; list includes routingEnabled only for Vodafone rows and can include resolvedFeeEGP when fee visibility is granted. API may append extension fields.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"userID": {
"type": "string"
},
"pipelineID": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"uid": {
"type": "string"
},
"providerKey": {
"enum": [
"generic",
"vodafone-elen"
]
},
"adapterVersion": {
"type": [
"string",
"null"
]
},
"providerConfig": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"senderName": {
"type": [
"string",
"null"
]
}
},
"required": [
"senderName"
],
"additionalProperties": true
},
"countries": {
"type": "array",
"items": {
"type": "string"
}
},
"steps": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"format": "uri"
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"query": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"bodyType": {
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"additionalProperties": true
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "integer"
}
},
"jsonPathEquals": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
},
"extract": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"required": [
"url"
],
"additionalProperties": true
}
},
"onFailure": {
"enum": [
"strict",
"fallbackToAkedly"
]
},
"feeOverride": {
"type": [
"number",
"null"
]
},
"active": {
"type": "boolean"
},
"liveAnnouncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"testPhone": {
"type": [
"string",
"null"
]
},
"testStatus": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"passed": {
"type": "boolean"
},
"lastTestedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastResultSummary": {
"type": [
"string",
"null"
]
},
"providerSmsId": {
"type": [
"string",
"null"
]
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"contractFingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"passed",
"lastTestedAt",
"lastResultSummary",
"providerSmsId",
"certainty",
"failureCategory",
"lastTestKey",
"contractFingerprint"
],
"additionalProperties": true
},
"configHash": {
"type": [
"string",
"null"
]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"stepsVisible": {
"type": "boolean"
},
"feeVisible": {
"type": "boolean"
},
"secretNames": {
"type": "array",
"items": {
"type": "string"
}
},
"vodafoneReadiness": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"providerKey": {
"const": "vodafone-elen"
},
"adapterVersion": {
"type": "string"
},
"supportedCountries": {
"type": "array",
"items": {
"const": "EG"
}
},
"credentials": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"required": {
"type": "array",
"items": {
"type": "string"
}
},
"configured": {
"type": "boolean"
},
"missing": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"required"
],
"additionalProperties": true
},
"sender": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"configured": {
"type": "boolean"
}
},
"required": [
"configured"
],
"additionalProperties": true
},
"pilotEnabled": {
"type": "boolean"
},
"pilotAccountAllowed": {
"type": [
"boolean",
"null"
]
},
"pilotAccountIdsConfigured": {
"type": "boolean"
},
"adapterAvailable": {
"type": "boolean"
},
"quotaDebitLedgerReady": {
"type": "boolean"
},
"contractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractMatches": {
"type": "boolean"
},
"deploymentBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"egress": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"ips": {
"type": "array",
"items": {
"type": "string",
"format": "ipv4"
}
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
},
"fingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"environment",
"environmentLabel",
"ips",
"status",
"verified",
"fingerprint"
],
"additionalProperties": true
},
"externalActivationRequired": {
"type": "boolean"
},
"testEligible": {
"type": "boolean"
},
"activationEligible": {
"type": "boolean"
},
"setupBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"testBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"activationBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"blocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"whitelistAcknowledged": {
"type": "boolean"
}
},
"required": [
"providerKey",
"adapterVersion",
"supportedCountries",
"pilotEnabled",
"pilotAccountAllowed",
"pilotAccountIdsConfigured",
"adapterAvailable",
"quotaDebitLedgerReady",
"contractFingerprint",
"deploymentBlocker",
"egress",
"externalActivationRequired",
"testEligible",
"activationEligible",
"setupBlocker",
"testBlocker",
"activationBlocker",
"blocker"
],
"additionalProperties": true
},
"routingEnabled": {
"type": "boolean"
},
"routingHint": {
"type": "string"
},
"resolvedFeeEGP": {
"type": "number",
"minimum": 0
}
},
"required": [
"_id",
"userID",
"name",
"uid",
"providerKey",
"countries",
"onFailure",
"active",
"testStatus",
"stepsVisible",
"feeVisible",
"secretNames"
],
"additionalProperties": true
}
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": "The CLI scrubs recognized secret values (prefixed secrets, credential fields, secret query parameters and JSON secret fields inside strings) from the response before projection; keep the output private anyway."
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": [
{
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
],
"resolvedFeeEGP": 0.25
}
]
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms get
Inspect one owned route’s provider setup, test state, coverage, fee and routing readiness.
When to use
- Before editing/testing/activating or retry recovery.
How it works
- Reads sanitized route; visible step text may leak inline credentials and headers/query are not faithfully echoed.
- Stored countries [] (and a sole ALL sentinel) means catch-all; a non-empty country list limits the route to those codes.
Prerequisites
- Owned id; entitlement.
Expected outcome
One decorated integration.
Next steps
- Never reconstruct a replacement step file from redacted/incomplete response.
Usage
akedly byo-sms get <id>
Inspect this command offline before preparing an operation:
akedly describe "byo-sms get" --output json
| Positional | Grammar | Usage guidance |
|---|---|---|
| <id> | ^[a-fA-F0-9]{24}$ | Use the id from akedly byo-sms list. |
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:read.
Server request: GET /byo-sms/integrations/:id.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
idempotent: Idempotent requests retry network errors and HTTP 429/502/503/504, at most 3 tries, waiting for cooldownSeconds, retryAfter or the Retry-After header (otherwise 1 second) between tries, up to 30 seconds; a longer requested wait is returned as the error.
Output
Sensitive output: echo-scrub. echo-scrub masks secret-shaped text, credential fields, secret query parameters and JSON secret fields inside strings in the response, such as a webhook receiver echo or BYO-SMS steps; keep the output private anyway.
Full command details
Read one of your integrations. The server strips secrets and vodafoneSetup, exposing secretNames instead of secret values, but visible steps are returned verbatim and may contain literal credentials (for example in auth, url or bodyTemplate); keep output private. Each step's headers and query currently come back as {} even when values are saved, so an empty headers or query object does not show the saved configuration. Member permissions can hide steps or fees (stepsVisible/feeVisible). Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are supported. active does not mean sends use this integration: routing follows each pipeline setting (akedly pipelines get <id> -> data.byoSms: inherit|on|off); inherit uses the account switch (akedly whoami -> user.byoSmsEnabled, set by Akedly), while on/off override it. Vodafone rows in byo-sms list also report routingEnabled.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Before editing/testing/activating or retry recovery.
akedly byo-sms get 64b000000000000000000001
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms get <id>
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [
{
"name": "id",
"type": "string",
"required": true,
"pattern": "^[a-fA-F0-9]{24}$",
"validation": "Use the id from akedly byo-sms list.",
"mapsToRouteParameter": "id",
"routeParameterValidation": "A route parameter cannot be . or .., including percent-encoded forms."
}
],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
}
],
"files": [],
"wire": {
"method": "GET",
"route": "/byo-sms/integrations/:id",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Sanitizer removes secret values and Vodafone private setup. steps/feeOverride may be omitted by member permission; stepsVisible and feeVisible state visibility. Step headers/query serialize as empty objects even if configured; do not rebuild replacement config from that view.
A Vodafone integration carries structured readiness fields; list includes routingEnabled only for Vodafone rows and can include resolvedFeeEGP when fee visibility is granted. API may append extension fields.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"userID": {
"type": "string"
},
"pipelineID": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"uid": {
"type": "string"
},
"providerKey": {
"enum": [
"generic",
"vodafone-elen"
]
},
"adapterVersion": {
"type": [
"string",
"null"
]
},
"providerConfig": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"senderName": {
"type": [
"string",
"null"
]
}
},
"required": [
"senderName"
],
"additionalProperties": true
},
"countries": {
"type": "array",
"items": {
"type": "string"
}
},
"steps": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"format": "uri"
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"query": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"bodyType": {
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"additionalProperties": true
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "integer"
}
},
"jsonPathEquals": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
},
"extract": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"required": [
"url"
],
"additionalProperties": true
}
},
"onFailure": {
"enum": [
"strict",
"fallbackToAkedly"
]
},
"feeOverride": {
"type": [
"number",
"null"
]
},
"active": {
"type": "boolean"
},
"liveAnnouncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"testPhone": {
"type": [
"string",
"null"
]
},
"testStatus": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"passed": {
"type": "boolean"
},
"lastTestedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastResultSummary": {
"type": [
"string",
"null"
]
},
"providerSmsId": {
"type": [
"string",
"null"
]
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"contractFingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"passed",
"lastTestedAt",
"lastResultSummary",
"providerSmsId",
"certainty",
"failureCategory",
"lastTestKey",
"contractFingerprint"
],
"additionalProperties": true
},
"configHash": {
"type": [
"string",
"null"
]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"stepsVisible": {
"type": "boolean"
},
"feeVisible": {
"type": "boolean"
},
"secretNames": {
"type": "array",
"items": {
"type": "string"
}
},
"vodafoneReadiness": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"providerKey": {
"const": "vodafone-elen"
},
"adapterVersion": {
"type": "string"
},
"supportedCountries": {
"type": "array",
"items": {
"const": "EG"
}
},
"credentials": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"required": {
"type": "array",
"items": {
"type": "string"
}
},
"configured": {
"type": "boolean"
},
"missing": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"required"
],
"additionalProperties": true
},
"sender": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"configured": {
"type": "boolean"
}
},
"required": [
"configured"
],
"additionalProperties": true
},
"pilotEnabled": {
"type": "boolean"
},
"pilotAccountAllowed": {
"type": [
"boolean",
"null"
]
},
"pilotAccountIdsConfigured": {
"type": "boolean"
},
"adapterAvailable": {
"type": "boolean"
},
"quotaDebitLedgerReady": {
"type": "boolean"
},
"contractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractMatches": {
"type": "boolean"
},
"deploymentBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"egress": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"ips": {
"type": "array",
"items": {
"type": "string",
"format": "ipv4"
}
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
},
"fingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"environment",
"environmentLabel",
"ips",
"status",
"verified",
"fingerprint"
],
"additionalProperties": true
},
"externalActivationRequired": {
"type": "boolean"
},
"testEligible": {
"type": "boolean"
},
"activationEligible": {
"type": "boolean"
},
"setupBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"testBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"activationBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"blocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"whitelistAcknowledged": {
"type": "boolean"
}
},
"required": [
"providerKey",
"adapterVersion",
"supportedCountries",
"pilotEnabled",
"pilotAccountAllowed",
"pilotAccountIdsConfigured",
"adapterAvailable",
"quotaDebitLedgerReady",
"contractFingerprint",
"deploymentBlocker",
"egress",
"externalActivationRequired",
"testEligible",
"activationEligible",
"setupBlocker",
"testBlocker",
"activationBlocker",
"blocker"
],
"additionalProperties": true
},
"routingEnabled": {
"type": "boolean"
},
"routingHint": {
"type": "string"
},
"resolvedFeeEGP": {
"type": "number",
"minimum": 0
}
},
"required": [
"_id",
"userID",
"name",
"uid",
"providerKey",
"countries",
"onFailure",
"active",
"testStatus",
"stepsVisible",
"feeVisible",
"secretNames"
],
"additionalProperties": true
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": "The CLI scrubs recognized secret values (prefixed secrets, credential fields, secret query parameters and JSON secret fields inside strings) from the response before projection; keep the output private anyway."
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms fee
Quote account registry resolved utility-SMS fee; no send or charge.
When to use
- Explain account pricing; mixed fees yield null singular fee and min/max range.
How it works
- Uses shared server resolver across active tested account integrations.
Prerequisites
- Entitlement, granular pricing, ana_rates_explorer read.
Expected outcome
feeEGP is the resolved account price when one price applies; when active tested account-registry routes have different prices, feeEGP is null and feeMinEGP/feeMaxEGP show the range. varies and source explain the result. Currency is EGP.
Next steps
- Use integration-fee for a route-specific quote.
Usage
akedly byo-sms fee
Inspect this command offline before preparing an operation:
akedly describe "byo-sms fee" --output json
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:read.
Server request: GET /byo-sms/fee.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
idempotent: Idempotent requests retry network errors and HTTP 429/502/503/504, at most 3 tries, waiting for cooldownSeconds, retryAfter or the Retry-After header (otherwise 1 second) between tries, up to 30 seconds; a longer requested wait is returned as the error.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
Preview the account-level resolved BYO-SMS fee in EGP, including the account registry of active tested integrations; feeEGP is null when those integrations resolve to different fees; feeMinEGP and feeMaxEGP then give the range; this reads a price and does not charge or send. Also requires ana_rates_explorer:read. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are supported.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Explain account pricing; mixed fees yield null singular fee and min/max range.
akedly byo-sms fee
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"feeEGP": 0.25,
"feeMinEGP": 0.25,
"feeMaxEGP": 0.25,
"varies": false,
"source": "default",
"currency": "EGP"
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms fee
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
}
],
"files": [],
"wire": {
"method": "GET",
"route": "/byo-sms/fee",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"type": "object",
"properties": {
"feeEGP": {
"type": [
"number",
"null"
]
},
"feeMinEGP": {
"type": "number"
},
"feeMaxEGP": {
"type": "number"
},
"currency": {
"const": "EGP"
},
"varies": {
"type": "boolean"
},
"source": {
"enum": [
"integration",
"account",
"default"
]
}
},
"additionalProperties": true,
"required": [
"feeEGP",
"feeMinEGP",
"feeMaxEGP",
"varies",
"source",
"currency"
]
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"feeEGP": 0.25,
"feeMinEGP": 0.25,
"feeMaxEGP": 0.25,
"varies": false,
"source": "default",
"currency": "EGP"
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms integration-fee
Quote one integration’s resolved fee; no send or charge.
When to use
- Compare configured route cost.
How it works
- Server resolves integration/account/default fee chain.
Prerequisites
- Owned integration and rate-read permission.
Expected outcome
feeEGP and EGP currency.
Next steps
- Compare account-level quote as needed.
Usage
akedly byo-sms integration-fee <id>
Inspect this command offline before preparing an operation:
akedly describe "byo-sms integration-fee" --output json
| Positional | Grammar | Usage guidance |
|---|---|---|
| <id> | ^[a-fA-F0-9]{24}$ | Use the id from akedly byo-sms list. |
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:read.
Server request: GET /byo-sms/integrations/:id/fee.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
idempotent: Idempotent requests retry network errors and HTTP 429/502/503/504, at most 3 tries, waiting for cooldownSeconds, retryAfter or the Retry-After header (otherwise 1 second) between tries, up to 30 seconds; a longer requested wait is returned as the error.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
Preview the resolved feeEGP and currency EGP for one owned integration; this reads a price and does not charge or send. Also requires ana_rates_explorer:read. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are supported.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Compare configured route cost.
akedly byo-sms integration-fee 64b000000000000000000001
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"feeEGP": 0.25,
"currency": "EGP"
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms integration-fee <id>
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [
{
"name": "id",
"type": "string",
"required": true,
"pattern": "^[a-fA-F0-9]{24}$",
"validation": "Use the id from akedly byo-sms list.",
"mapsToRouteParameter": "id",
"routeParameterValidation": "A route parameter cannot be . or .., including percent-encoded forms."
}
],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
}
],
"files": [],
"wire": {
"method": "GET",
"route": "/byo-sms/integrations/:id/fee",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"type": "object",
"properties": {
"feeEGP": {
"type": "number"
},
"currency": {
"const": "EGP"
}
},
"additionalProperties": true,
"required": [
"feeEGP",
"currency"
]
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"feeEGP": 0.25,
"currency": "EGP"
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms egress-ips
Show Akedly outbound IP allowlist for this environment.
When to use
- Give provider addresses during setup.
How it works
- Reads published, validated egress configuration only.
Prerequisites
- Entitlement.
Expected outcome
Sorted addresses, environment and ready/missing/invalid state.
Next steps
- Provider allowlisting is separate; ask support if missing/invalid.
Usage
akedly byo-sms egress-ips
Inspect this command offline before preparing an operation:
akedly describe "byo-sms egress-ips" --output json
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:read.
Server request: GET /byo-sms/egress-ips.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
idempotent: Idempotent requests retry network errors and HTTP 429/502/503/504, at most 3 tries, waiting for cooldownSeconds, retryAfter or the Retry-After header (otherwise 1 second) between tries, up to 30 seconds; a longer requested wait is returned as the error.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
Show the outbound IPv4 addresses Akedly sends BYO-SMS messages from in this environment. Give data.ips to your SMS provider so it can allow-list them. data.ips is a sorted list; data.environment and data.environmentLabel name the environment the list belongs to. data.status is ready, missing or invalid: missing means Akedly has not published addresses for this environment yet, and invalid means the published value was rejected; in both cases data.ips is empty, so ask Akedly support. data.verified is true only when the status is ready; it means Akedly accepted the published list, not that your provider has allow-listed it. This command sends nothing and changes nothing; it does not acknowledge or whitelist an address. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are supported.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Give provider addresses during setup.
akedly byo-sms egress-ips
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"ips": [],
"environment": "example",
"environmentLabel": "EXAMPLE",
"status": "missing",
"verified": false
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms egress-ips
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
}
],
"files": [],
"wire": {
"method": "GET",
"route": "/byo-sms/egress-ips",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"type": "object",
"properties": {
"ips": {
"type": "array",
"items": {
"type": "string"
}
},
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
}
},
"additionalProperties": true,
"required": [
"ips",
"environment",
"environmentLabel",
"status",
"verified"
]
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"ips": [],
"environment": "example",
"environmentLabel": "EXAMPLE",
"status": "missing",
"verified": false
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms vodafone readiness
Review account/environment Vodafone pilot and operational blockers without sending.
When to use
- Before Vodafone provisioning.
How it works
- Reads account-level setup readiness; test/activation eligibility remain false here. Per-integration eligibility is on get.
Prerequisites
- Entitlement.
Expected outcome
Readiness and egress fingerprint.
Next steps
- Resolve blockers with Akedly, then inspect Vodafone route readiness.
Usage
akedly byo-sms vodafone readiness
Inspect this command offline before preparing an operation:
akedly describe "byo-sms vodafone readiness" --output json
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:read.
Server request: GET /byo-sms/vodafone/readiness.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
idempotent: Idempotent requests retry network errors and HTTP 429/502/503/504, at most 3 tries, waiting for cooldownSeconds, retryAfter or the Retry-After header (otherwise 1 second) between tries, up to 30 seconds; a longer requested wait is returned as the error.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
Read Vodafone environment, pilot, adapter, settlement and setup readiness without testing or sending. data.egress.fingerprint identifies the current environment and outbound-address configuration and is null until Akedly has verified those addresses; it is distinct from contractFingerprint. Readiness does not acknowledge or whitelist an address and does not enable sending. This account-level readiness always reports testEligible and activationEligible as false; for a Vodafone integration, byo-sms get shows its own vodafoneReadiness, including testEligible, activationEligible and whitelistAcknowledged; the stored acknowledgement timestamp and fingerprint are hidden. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are supported. Read blocker and deploymentBlocker for the reason codes and messages.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Before Vodafone provisioning.
akedly byo-sms vodafone readiness
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"providerKey": "vodafone-elen",
"adapterVersion": "1",
"supportedCountries": [
"EG"
],
"credentials": "[REDACTED — synthetic secret omitted]",
"sender": {
"configured": false
},
"pilotEnabled": false,
"pilotAccountAllowed": false,
"pilotAccountIdsConfigured": false,
"adapterAvailable": false,
"quotaDebitLedgerReady": false,
"contractFingerprint": null,
"testContractFingerprint": null,
"testContractMatches": false,
"deploymentBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"egress": {
"environment": "example",
"environmentLabel": "EXAMPLE",
"ips": [],
"status": "missing",
"verified": false,
"fingerprint": null
},
"externalActivationRequired": true,
"testEligible": false,
"activationEligible": false,
"setupBlocker": null,
"testBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"activationBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"blocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
}
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms vodafone readiness
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
}
],
"files": [],
"wire": {
"method": "GET",
"route": "/byo-sms/vodafone/readiness",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"providerKey": {
"const": "vodafone-elen"
},
"adapterVersion": {
"type": "string"
},
"supportedCountries": {
"type": "array",
"items": {
"const": "EG"
}
},
"credentials": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"required": {
"type": "array",
"items": {
"type": "string"
}
},
"configured": {
"type": "boolean"
},
"missing": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"required"
],
"additionalProperties": true
},
"sender": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"configured": {
"type": "boolean"
}
},
"required": [
"configured"
],
"additionalProperties": true
},
"pilotEnabled": {
"type": "boolean"
},
"pilotAccountAllowed": {
"type": [
"boolean",
"null"
]
},
"pilotAccountIdsConfigured": {
"type": "boolean"
},
"adapterAvailable": {
"type": "boolean"
},
"quotaDebitLedgerReady": {
"type": "boolean"
},
"contractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractMatches": {
"type": "boolean"
},
"deploymentBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"egress": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"ips": {
"type": "array",
"items": {
"type": "string",
"format": "ipv4"
}
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
},
"fingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"environment",
"environmentLabel",
"ips",
"status",
"verified",
"fingerprint"
],
"additionalProperties": true
},
"externalActivationRequired": {
"type": "boolean"
},
"testEligible": {
"type": "boolean"
},
"activationEligible": {
"type": "boolean"
},
"setupBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"testBlocker": {
"oneOf": [
{
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"activationBlocker": {
"oneOf": [
{
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"blocker": {
"oneOf": [
{
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"whitelistAcknowledged": {
"type": "boolean"
}
},
"required": [
"providerKey",
"adapterVersion",
"supportedCountries",
"pilotEnabled",
"pilotAccountAllowed",
"pilotAccountIdsConfigured",
"adapterAvailable",
"quotaDebitLedgerReady",
"contractFingerprint",
"deploymentBlocker",
"egress",
"externalActivationRequired",
"testEligible",
"activationEligible",
"setupBlocker",
"testBlocker",
"activationBlocker",
"blocker"
],
"additionalProperties": true
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"providerKey": "vodafone-elen",
"adapterVersion": "1",
"supportedCountries": [
"EG"
],
"credentials": "[REDACTED — synthetic secret omitted]",
"sender": {
"configured": false
},
"pilotEnabled": false,
"pilotAccountAllowed": false,
"pilotAccountIdsConfigured": false,
"adapterAvailable": false,
"quotaDebitLedgerReady": false,
"contractFingerprint": null,
"testContractFingerprint": null,
"testContractMatches": false,
"deploymentBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"egress": {
"environment": "example",
"environmentLabel": "EXAMPLE",
"ips": [],
"status": "missing",
"verified": false,
"fingerprint": null
},
"externalActivationRequired": true,
"testEligible": false,
"activationEligible": false,
"setupBlocker": null,
"testBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"activationBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"blocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
}
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms create
Create inactive account-level SMS route using generic provider steps or supported Vodafone managed configuration.
When to use
- Connect a provider route before test and activation.
How it works
- Generic stores ordered HTTP steps, coverage, fail policy and secrets; Vodafone requires managed sender/credentials and its own provisioned setup. Creation can alert staff. Omitted
--countriesor ALL means catch-all coverage; the worked example passes EG and therefore shows Egypt-only coverage.
Prerequisites
- Entitlement, granular pricing and routable countries; generic steps or Vodafone pilot/region approval.
- Coverage is an intentional scope decision: omitted countries and a sole ALL value mean catch-all; include explicit country codes to restrict routing.
Expected outcome
Inactive sanitized integration; visible URL/templates may contain inline credentials.
Next steps
- Review get, test with reviewed recipient and activate only on passing current configuration.
Usage
akedly byo-sms create
Inspect this command offline before preparing an operation:
akedly describe "byo-sms create" --output json
Flags
| Flag | Type | Enum / range | Required | Description |
|---|---|---|---|---|
--ksa | boolean switch | — | no | Select the KSA deployment. |
--dry-run | boolean switch | — | no | Preview without sending the write or creating output files; planning reads may still occur. Bypass consent. |
--name | text | — | yes | Command-specific option; see the full command details below. Manifest body field: name. |
--description | text | — | no | Command-specific option; see the full command details below. Manifest body field: description. |
--on-failure | enum | strict | fall-back-to-akedly | no | Command-specific option; see the full command details below. Manifest body field: onFailure. Tip: For OTP sends, fall-back-to-akedly is recommended: with it, if your provider fails, Akedly sends the SMS through its own providers at its normal SMS rates, not the flat Own SMS fee. With strict, the SMS fails and the pipeline's normal channel fallback continues. Utility SMS never uses the Akedly fallback, and on a managed Vodafone integration Akedly sets the failure behavior, so you cannot choose it. |
--provider | enum | custom-http | vodafone-elen | no | Command-specific option; see the full command details below. Manifest body field: providerKey. |
--sender-name | text | — | no | Command-specific option; see the full command details below. Manifest body field: providerConfig.senderName. |
--countries | list | — | no | Command-specific option; see the full command details below. Manifest body field: countries. |
--steps-file | text | — | no | Command-specific option; see the full command details below. Manifest body field: steps. Tip: By default Akedly judges each step only by its HTTP status, so a provider error returned with an accepted status counts as success; add successAssertion jsonPathEquals or bodyMatches to the step to catch it, or prefer an endpoint that reports failure with an HTTP error status, and confirm real delivery when you test the integration. |
--secrets-file | text | — | no | Command-specific option; see the full command details below. Manifest body field: secrets. |
--help | boolean switch | — | no | Show offline command help. |
--output | enum | json | human | no | Select JSON or human output. |
--fields | dot-paths | — | no | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:create.
Server request: POST /byo-sms/integrations.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
write: Non-idempotent writes are not automatically retried. guarded-write: when an update carries the revision read from the server, its write is not automatically retried after network errors or HTTP 429/502/503/504. It may already be saved; inspect the resource before rerunning. A rerun reads current settings again. The explicit CLI_RATE_LIMITED refusal exception below still applies. recommendation-read: the recommendation request made while planning --recommended-limits is not automatically retried after network errors or HTTP 429/502/503/504. If planning fails, no update is sent; rerun the command to request a new recommendation. The explicit CLI_RATE_LIMITED refusal exception below still applies. This label does not change the retry policy of the standalone rate-recommendations command.
Output
Sensitive output: echo-scrub. echo-scrub masks secret-shaped text, credential fields, secret query parameters and JSON secret fields inside strings in the response, such as a webhook receiver echo or BYO-SMS steps; keep the output private anyway.
Full command details
Create an inactive account-registry integration. --name is required; generic integrations need a non-empty JSON steps array (at most 1 MiB). --secrets-file is a JSON object (at most 64 KiB); never pass credentials in argv. Steps run in order. Step format: required url; method GET|POST|PUT|PATCH|DELETE (default POST); headers/query string maps (default {}); bodyType json|form|raw|none (default json); bodyTemplate (default empty string); auth with type none|bearer|basic|apiKeyHeader (default none), using token for bearer, username/password for basic or headerName/headerValue for apiKeyHeader. Optional successAssertion has statusIn (default [200,201,202]), jsonPathEquals {path,value} and bodyMatches regex; extract is an array of {name,from:status|header|jsonPath|regex,expr}. timeoutMs defaults to 3000; retryCount is 0-3 (default 0), only for the first step when it is not the send step; isSendStep defaults to false, and the first marked step or otherwise the last step is the send step. Templates support {{otp}}/{{message}}, {{otpMessage}}, {{phone}}, {{phoneLocal}}, {{secrets.<name>}} and {{vars.<name>}} from an earlier extract.name; for example {{secrets.apiKey}} reads the apiKey key from --secrets-file instead of embedding a literal credential. Secret and extraction names use letters, digits and underscore; a json bodyTemplate must form valid JSON after placeholder substitution. Countries are comma-separated; ALL alone means catch-all, omitted countries also mean catch-all; the server validates the routable catalog and rejects unknown or duplicate codes. onFailure defaults to fallbackToAkedly. Vodafone uses providerKey vodafone-elen, requires --sender-name and accountId/password/secureHashSecretKey in the secrets file, manages its own steps/onFailure and fixes countries to EG (omit those flags). Vodafone provisioning requires a supported region and the account to be allowed in the pilot; HTTP 403 names the blocker. Check akedly byo-sms vodafone readiness. Creation may notify Akedly staff. The server strips secrets and vodafoneSetup, but visible steps are returned verbatim and can contain literal credentials in auth, url or bodyTemplate; keep output private. --steps-file replaces the whole steps array, including headers and query: supply a complete file. Current get/list output serializes step headers/query as {}, so do not build a replacement steps file from that output or saved header/query values can be lost. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are refused.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Connect a provider route before test and activation.
akedly byo-sms create --name 'Synthetic route' --countries EG --steps-file ./steps.json --secrets-file ./secrets.json
Input file: steps.json (json).
[
{
"url": "https://sms.example.invalid/send",
"method": "POST",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none"
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true
}
]
Input file: secrets.json (json).
{
"apiKey": "[REDACTED — synthetic secret omitted]"
}
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
The secrets file value SYNTHETIC-ONLY is a fake placeholder; never copy a real secret into chat or rendered docs.
This example explicitly passes --countries EG. If omitted, the backend stores an empty country list and treats it as catch-all; --countries ALL is also catch-all.
Create an inactive Egypt-only generic SMS route from complete JSON files
Register a generic HTTP SMS route with one send step and a separately stored API key. Explicit EG coverage prevents the omitted-country catch-all behavior. The route remains inactive until tested and deliberately activated.
akedly byo-sms create --name 'Synthetic EG route' --countries EG --steps-file ./fixtures/eg-sms-steps.json --secrets-file ./fixtures/eg-sms-secrets.json
Input file: ./fixtures/eg-sms-steps.json (json).
[
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"api_key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none"
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
]
},
"extract": []
}
]
Input file: ./fixtures/eg-sms-secrets.json (json).
{
"apiKey": "[REDACTED — synthetic secret omitted]"
}
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic EG route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"api_key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
Synthetic-only endpoint uses the reserved .invalid domain. The fake key is illustrative and must never be used. The server response omits secret values and reports only secretNames. No provider request was made.
Additional command forms
akedly byo-sms create --name <name> --steps-file <path>
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "dry-run",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "dry-run",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "dry-run",
"inputRole": "local-control",
"transform": "identity",
"purpose": "dry-run",
"contributors": [
"dry-run"
]
}
]
},
{
"name": "name",
"schema": {
"type": "string"
},
"required": true,
"wire": [
{
"path": "name",
"transform": "trim",
"role": "body",
"input": "name",
"contributors": [
"name"
]
}
]
},
{
"name": "description",
"schema": {
"type": "string"
},
"required": false,
"wire": [
{
"path": "description",
"transform": "identity",
"role": "body",
"input": "description",
"contributors": [
"description"
]
}
]
},
{
"name": "on-failure",
"schema": {
"type": "string",
"enum": [
"strict",
"fall-back-to-akedly"
]
},
"required": false,
"wire": [
{
"path": "onFailure",
"transform": "identity",
"role": "body",
"input": "on-failure",
"contributors": [
"on-failure"
]
}
],
"valueTranslations": {
"strict": "strict",
"fall-back-to-akedly": "fallbackToAkedly"
}
},
{
"name": "provider",
"schema": {
"type": "string",
"enum": [
"custom-http",
"vodafone-elen"
]
},
"required": false,
"wire": [
{
"path": "providerKey",
"transform": "identity",
"role": "body",
"input": "provider",
"contributors": [
"provider"
]
}
],
"valueTranslations": {
"custom-http": "generic",
"vodafone-elen": "vodafone-elen"
}
},
{
"name": "sender-name",
"schema": {
"type": "string"
},
"required": false,
"wire": [
{
"path": "providerConfig.senderName",
"transform": "trim",
"role": "body",
"input": "sender-name",
"contributors": [
"sender-name"
]
}
]
},
{
"name": "countries",
"schema": {
"type": "string",
"pattern": "^(?:[^,]*[^,\\s][^,]*)(?:,(?:[^,]*[^,\\s][^,]*))*$"
},
"required": false,
"wire": [
{
"path": "countries",
"transform": "split-trim",
"role": "body",
"input": "countries",
"contributors": [
"countries"
]
}
],
"validation": {
"syntax": "Comma-separated values; every segment must contain at least one non-whitespace character."
}
},
{
"name": "steps-file",
"schema": {
"type": "string"
},
"required": false,
"input": {
"role": "input-file",
"contract": "byo-steps",
"requestTargets": [
{
"role": "body",
"path": "steps",
"transform": "json-array"
}
],
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS HTTP steps",
"description": "Ordered HTTP request steps. The CLI requires a JSON array and create requires at least one step. Each step needs url; other fields follow the server model. JSON bodyTemplate is checked as JSON after replacing {{secret/extraction placeholders}} with a scalar.",
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/step"
},
"$defs": {
"stringMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"auth": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"successAssertion": {
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "number"
}
},
"jsonPathEquals": {},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"extract": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": false
}
},
"step": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"minLength": 1,
"pattern": "\\S"
},
"headers": {
"$ref": "#/$defs/stringMap"
},
"query": {
"$ref": "#/$defs/stringMap"
},
"bodyType": {
"type": "string",
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$ref": "#/$defs/auth"
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$ref": "#/$defs/successAssertion"
},
"extract": {
"$ref": "#/$defs/extract"
}
},
"required": [
"url"
],
"additionalProperties": false
}
}
},
"localReaderSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": true
}
]
}
},
"fileContract": {
"format": "json-array",
"maxBytes": 1048576,
"regularFile": true,
"reader": "files.js:readJsonArray",
"type": "json-array",
"exclusive": false,
"mode": null,
"trim": false,
"writer": null,
"minBytes": 0,
"detectChanges": true,
"fieldMappings": {}
},
"semanticSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS HTTP steps",
"description": "Ordered HTTP request steps. The CLI requires a JSON array and create requires at least one step. Each step needs url; other fields follow the server model. JSON bodyTemplate is checked as JSON after replacing {{secret/extraction placeholders}} with a scalar.",
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/step"
},
"$defs": {
"stringMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"auth": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"successAssertion": {
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "number"
}
},
"jsonPathEquals": {},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"extract": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": false
}
},
"step": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"minLength": 1,
"pattern": "\\S"
},
"headers": {
"$ref": "#/$defs/stringMap"
},
"query": {
"$ref": "#/$defs/stringMap"
},
"bodyType": {
"type": "string",
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$ref": "#/$defs/auth"
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$ref": "#/$defs/successAssertion"
},
"extract": {
"$ref": "#/$defs/extract"
}
},
"required": [
"url"
],
"additionalProperties": false
}
}
},
"validationBoundary": "semanticSchema describes curated command file-content constraints; localReaderSchema describes file-reader constraints. Read command help and schema descriptions for additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "steps-file",
"inputRole": "input-file",
"transform": "identity",
"fileContract": "byo-steps",
"contributors": [
"steps-file"
]
},
{
"role": "body",
"path": "steps",
"transform": "json-array",
"input": "steps-file",
"contributors": [
"steps-file"
],
"fileContract": "byo-steps"
}
]
},
{
"name": "secrets-file",
"schema": {
"type": "string"
},
"required": false,
"input": {
"role": "input-file",
"contract": "json-object",
"requestTargets": [
{
"role": "body",
"path": "secrets",
"transform": "json-object"
}
],
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS secret values",
"description": "JSON object mapping secret names to values used by {{secrets.name}} in steps. Names use letters, digits and underscore. Empty or null entries are ignored by the server secret-issue check; omit values that are not being set.",
"type": "object",
"propertyNames": {
"pattern": "^\\w+$"
},
"additionalProperties": {
"type": [
"string",
"null"
]
}
},
"localReaderSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": true
}
]
}
},
"fileContract": {
"format": "json-object",
"maxBytes": 65536,
"regularFile": true,
"reader": "files.js:readJsonObject",
"type": "json-object",
"exclusive": false,
"mode": null,
"trim": false,
"writer": null,
"minBytes": 0,
"detectChanges": true,
"fieldMappings": {}
},
"semanticSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS secret values",
"description": "JSON object mapping secret names to values used by {{secrets.name}} in steps. Names use letters, digits and underscore. Empty or null entries are ignored by the server secret-issue check; omit values that are not being set.",
"type": "object",
"propertyNames": {
"pattern": "^\\w+$"
},
"additionalProperties": {
"type": [
"string",
"null"
]
}
},
"validationBoundary": "semanticSchema describes curated command file-content constraints; localReaderSchema describes file-reader constraints. Read command help and schema descriptions for additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "secrets-file",
"inputRole": "input-file",
"transform": "identity",
"fileContract": "json-object",
"contributors": [
"secrets-file"
]
},
{
"role": "body",
"path": "secrets",
"transform": "json-object",
"input": "secrets-file",
"contributors": [
"secrets-file"
],
"fileContract": "json-object"
}
]
}
],
"files": [
{
"name": "byo-steps",
"contract": {
"format": "json-array",
"maxBytes": 1048576,
"regularFile": true,
"reader": "files.js:readJsonArray",
"type": "json-array",
"exclusive": false,
"mode": null,
"trim": false,
"writer": null,
"minBytes": 0,
"detectChanges": true,
"fieldMappings": {}
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": true
}
]
}
},
"uses": [
{
"flag": "steps-file",
"role": "input-file",
"purpose": null,
"semanticSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS HTTP steps",
"description": "Ordered HTTP request steps. The CLI requires a JSON array and create requires at least one step. Each step needs url; other fields follow the server model. JSON bodyTemplate is checked as JSON after replacing {{secret/extraction placeholders}} with a scalar.",
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/step"
},
"$defs": {
"stringMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"auth": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"successAssertion": {
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "number"
}
},
"jsonPathEquals": {},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"extract": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": false
}
},
"step": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"minLength": 1,
"pattern": "\\S"
},
"headers": {
"$ref": "#/$defs/stringMap"
},
"query": {
"$ref": "#/$defs/stringMap"
},
"bodyType": {
"type": "string",
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$ref": "#/$defs/auth"
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$ref": "#/$defs/successAssertion"
},
"extract": {
"$ref": "#/$defs/extract"
}
},
"required": [
"url"
],
"additionalProperties": false
}
}
}
}
]
},
{
"name": "json-object",
"contract": {
"format": "json-object",
"maxBytes": 65536,
"regularFile": true,
"reader": "files.js:readJsonObject",
"type": "json-object",
"exclusive": false,
"mode": null,
"trim": false,
"writer": null,
"minBytes": 0,
"detectChanges": true,
"fieldMappings": {}
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": true
}
]
}
},
"uses": [
{
"flag": "secrets-file",
"role": "input-file",
"purpose": null,
"semanticSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS secret values",
"description": "JSON object mapping secret names to values used by {{secrets.name}} in steps. Names use letters, digits and underscore. Empty or null entries are ignored by the server secret-issue check; omit values that are not being set.",
"type": "object",
"propertyNames": {
"pattern": "^\\w+$"
},
"additionalProperties": {
"type": [
"string",
"null"
]
}
}
}
]
}
],
"wire": {
"method": "POST",
"route": "/byo-sms/integrations",
"query": {},
"body": {
"name": [
{
"path": "name",
"transform": "trim",
"role": "body",
"input": "name",
"contributors": [
"name"
]
}
],
"description": [
{
"path": "description",
"transform": "identity",
"role": "body",
"input": "description",
"contributors": [
"description"
]
}
],
"on-failure": [
{
"path": "onFailure",
"transform": "identity",
"role": "body",
"input": "on-failure",
"contributors": [
"on-failure"
]
}
],
"provider": [
{
"path": "providerKey",
"transform": "identity",
"role": "body",
"input": "provider",
"contributors": [
"provider"
]
}
],
"sender-name": [
{
"path": "providerConfig.senderName",
"transform": "trim",
"role": "body",
"input": "sender-name",
"contributors": [
"sender-name"
]
}
],
"countries": [
{
"path": "countries",
"transform": "split-trim",
"role": "body",
"input": "countries",
"contributors": [
"countries"
]
}
]
},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"userID": {
"type": "string"
},
"pipelineID": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"uid": {
"type": "string"
},
"providerKey": {
"enum": [
"generic",
"vodafone-elen"
]
},
"adapterVersion": {
"type": [
"string",
"null"
]
},
"providerConfig": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"senderName": {
"type": [
"string",
"null"
]
}
},
"required": [
"senderName"
],
"additionalProperties": true
},
"countries": {
"type": "array",
"items": {
"type": "string"
}
},
"steps": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"format": "uri"
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"query": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"bodyType": {
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"additionalProperties": true
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "integer"
}
},
"jsonPathEquals": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
},
"extract": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"required": [
"url"
],
"additionalProperties": true
}
},
"onFailure": {
"enum": [
"strict",
"fallbackToAkedly"
]
},
"feeOverride": {
"type": [
"number",
"null"
]
},
"active": {
"type": "boolean"
},
"liveAnnouncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"testPhone": {
"type": [
"string",
"null"
]
},
"testStatus": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"passed": {
"type": "boolean"
},
"lastTestedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastResultSummary": {
"type": [
"string",
"null"
]
},
"providerSmsId": {
"type": [
"string",
"null"
]
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"contractFingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"passed",
"lastTestedAt",
"lastResultSummary",
"providerSmsId",
"certainty",
"failureCategory",
"lastTestKey",
"contractFingerprint"
],
"additionalProperties": true
},
"configHash": {
"type": [
"string",
"null"
]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"stepsVisible": {
"type": "boolean"
},
"feeVisible": {
"type": "boolean"
},
"secretNames": {
"type": "array",
"items": {
"type": "string"
}
},
"vodafoneReadiness": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"providerKey": {
"const": "vodafone-elen"
},
"adapterVersion": {
"type": "string"
},
"supportedCountries": {
"type": "array",
"items": {
"const": "EG"
}
},
"credentials": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"required": {
"type": "array",
"items": {
"type": "string"
}
},
"configured": {
"type": "boolean"
},
"missing": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"required"
],
"additionalProperties": true
},
"sender": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"configured": {
"type": "boolean"
}
},
"required": [
"configured"
],
"additionalProperties": true
},
"pilotEnabled": {
"type": "boolean"
},
"pilotAccountAllowed": {
"type": [
"boolean",
"null"
]
},
"pilotAccountIdsConfigured": {
"type": "boolean"
},
"adapterAvailable": {
"type": "boolean"
},
"quotaDebitLedgerReady": {
"type": "boolean"
},
"contractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractMatches": {
"type": "boolean"
},
"deploymentBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"egress": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"ips": {
"type": "array",
"items": {
"type": "string",
"format": "ipv4"
}
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
},
"fingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"environment",
"environmentLabel",
"ips",
"status",
"verified",
"fingerprint"
],
"additionalProperties": true
},
"externalActivationRequired": {
"type": "boolean"
},
"testEligible": {
"type": "boolean"
},
"activationEligible": {
"type": "boolean"
},
"setupBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"testBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"activationBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"blocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"whitelistAcknowledged": {
"type": "boolean"
}
},
"required": [
"providerKey",
"adapterVersion",
"supportedCountries",
"pilotEnabled",
"pilotAccountAllowed",
"pilotAccountIdsConfigured",
"adapterAvailable",
"quotaDebitLedgerReady",
"contractFingerprint",
"deploymentBlocker",
"egress",
"externalActivationRequired",
"testEligible",
"activationEligible",
"setupBlocker",
"testBlocker",
"activationBlocker",
"blocker"
],
"additionalProperties": true
},
"routingEnabled": {
"type": "boolean"
},
"routingHint": {
"type": "string"
},
"resolvedFeeEGP": {
"type": "number",
"minimum": 0
}
},
"required": [
"_id",
"userID",
"name",
"uid",
"providerKey",
"countries",
"onFailure",
"active",
"testStatus",
"stepsVisible",
"feeVisible",
"secretNames"
],
"additionalProperties": true
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": "The CLI scrubs recognized secret values (prefixed secrets, credential fields, secret query parameters and JSON secret fields inside strings) from the response before projection; keep the output private anyway."
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms update
Change only named integration settings; endpoint/secret/coverage changes can invalidate test and deactivate route.
When to use
- Maintain provider workflow or test destination.
How it works
- Partial write; steps file replaces complete chain; secrets merge, empty/null deletes; sensitive edits require
--yes. Provider immutable.
Prerequisites
- Owned integration; full intended steps including headers/query preserved.
Expected outcome
Updated route and readiness.
Next steps
- Retest after invalidation; only activate passing current config.
Usage
akedly byo-sms update <id>
Inspect this command offline before preparing an operation:
akedly describe "byo-sms update" --output json
| Positional | Grammar | Usage guidance |
|---|---|---|
| <id> | ^[a-fA-F0-9]{24}$ | Use the id from akedly byo-sms list. |
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--dry-run | boolean switch | — | Preview without sending the write or creating output files; planning reads may still occur. Bypass consent. |
--name | text | — | Command-specific option; see the full command details below. Manifest body field: name. |
--description | text | — | Command-specific option; see the full command details below. Manifest body field: description. |
--on-failure | enum | strict | fall-back-to-akedly | Command-specific option; see the full command details below. Manifest body field: onFailure. Tip: For OTP sends, fall-back-to-akedly is recommended: with it, if your provider fails, Akedly sends the SMS through its own providers at its normal SMS rates, not the flat Own SMS fee. With strict, the SMS fails and the pipeline's normal channel fallback continues. Utility SMS never uses the Akedly fallback, and on a managed Vodafone integration Akedly sets the failure behavior, so you cannot choose it. |
--provider | enum | custom-http | vodafone-elen | Command-specific option; see the full command details below. Manifest body field: providerKey. |
--sender-name | text | — | Command-specific option; see the full command details below. Manifest body field: providerConfig.senderName. |
--countries | list | — | Command-specific option; see the full command details below. Manifest body field: countries. |
--steps-file | text | — | Command-specific option; see the full command details below. Manifest body field: steps. Tip: By default Akedly judges each step only by its HTTP status, so a provider error returned with an accepted status counts as success; add successAssertion jsonPathEquals or bodyMatches to the step to catch it, or prefer an endpoint that reports failure with an HTTP error status, and confirm real delivery when you test the integration. |
--secrets-file | text | — | Command-specific option; see the full command details below. Manifest body field: secrets. |
--test-phone-number | text | — | Command-specific option; see the full command details below. Manifest body field: testPhone. |
--yes | boolean switch | — | Confirm this action where consent is required. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:update.
Server request: PUT /byo-sms/integrations/:id.
Consent
--yes when --steps-file or --secrets-file or --countries or --sender-name is given. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
write: Non-idempotent writes are not automatically retried. guarded-write: when an update carries the revision read from the server, its write is not automatically retried after network errors or HTTP 429/502/503/504. It may already be saved; inspect the resource before rerunning. A rerun reads current settings again. The explicit CLI_RATE_LIMITED refusal exception below still applies. recommendation-read: the recommendation request made while planning --recommended-limits is not automatically retried after network errors or HTTP 429/502/503/504. If planning fails, no update is sent; rerun the command to request a new recommendation. The explicit CLI_RATE_LIMITED refusal exception below still applies. This label does not change the retry policy of the standalone rate-recommendations command.
Output
Sensitive output: echo-scrub. echo-scrub masks secret-shaped text, credential fields, secret query parameters and JSON secret fields inside strings in the response, such as a webhook receiver echo or BYO-SMS steps; keep the output private anyway.
Full command details
Update only supplied fields; supply at least one. See akedly byo-sms create --help for the generic steps-file format and secret placeholders. --steps-file, --secrets-file, --countries or --sender-name requires --yes because changed steps/coverage/secrets can deactivate the integration and invalidate its test; a Vodafone sender-name change deactivates an active integration, which then needs a new test. Secret keys merge with stored values; null or an empty string removes a generic secret, while omitted keys are preserved. Provider cannot be changed. --provider is accepted only when it equals the current provider. Vodafone sender/credential changes also deactivate and invalidate the test; Vodafone manages steps/onFailure/testPhone and EG coverage. --test-phone-number changes the saved generic test destination without invalidating the test; the server validates and normalizes international numbers. The server strips secrets and vodafoneSetup, but visible steps are returned verbatim and can contain literal credentials in auth, url or bodyTemplate; keep output private. --steps-file replaces the whole steps array, including headers and query: supply a complete file. Current get/list output serializes step headers/query as {}, so do not build a replacement steps file from that output or saved header/query values can be lost. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are refused.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Maintain provider workflow or test destination.
akedly byo-sms update 64b000000000000000000001 --name 'Synthetic route v2'
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route v2",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Replace route steps and rotate its secret with confirmation
Replace the complete step chain and rotate apiKey on an existing generic route. Both file inputs are supplied because the steps file replaces the whole chain and the secrets file merges the named key. --yes is required for these configuration changes.
akedly byo-sms update 64b000000000000000000001 --countries EG --steps-file ./fixtures/eg-sms-steps.json --secrets-file ./fixtures/eg-sms-secrets.json --yes
Input file: ./fixtures/eg-sms-steps.json (json).
[
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"api_key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none"
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
]
},
"extract": []
}
]
Input file: ./fixtures/eg-sms-secrets.json (json).
{
"apiKey": "[REDACTED — synthetic secret omitted]"
}
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic EG route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"api_key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
Synthetic existing integration ID, endpoint, and key; no provider request was made. The response omits secret values. --yes confirms a potentially deactivating configuration change; it does not test or activate the route.
Additional command forms
akedly byo-sms update <id> --name <name>
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [
{
"name": "id",
"type": "string",
"required": true,
"pattern": "^[a-fA-F0-9]{24}$",
"validation": "Use the id from akedly byo-sms list.",
"mapsToRouteParameter": "id",
"routeParameterValidation": "A route parameter cannot be . or .., including percent-encoded forms."
}
],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "dry-run",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "dry-run",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "dry-run",
"inputRole": "local-control",
"transform": "identity",
"purpose": "dry-run",
"contributors": [
"dry-run"
]
}
]
},
{
"name": "name",
"schema": {
"type": "string"
},
"required": false,
"wire": [
{
"path": "name",
"transform": "trim",
"role": "body",
"input": "name",
"contributors": [
"name"
]
}
]
},
{
"name": "description",
"schema": {
"type": "string"
},
"required": false,
"wire": [
{
"path": "description",
"transform": "identity",
"role": "body",
"input": "description",
"contributors": [
"description"
]
}
]
},
{
"name": "on-failure",
"schema": {
"type": "string",
"enum": [
"strict",
"fall-back-to-akedly"
]
},
"required": false,
"wire": [
{
"path": "onFailure",
"transform": "identity",
"role": "body",
"input": "on-failure",
"contributors": [
"on-failure"
]
}
],
"valueTranslations": {
"strict": "strict",
"fall-back-to-akedly": "fallbackToAkedly"
}
},
{
"name": "provider",
"schema": {
"type": "string",
"enum": [
"custom-http",
"vodafone-elen"
]
},
"required": false,
"wire": [
{
"path": "providerKey",
"transform": "identity",
"role": "body",
"input": "provider",
"contributors": [
"provider"
]
}
],
"valueTranslations": {
"custom-http": "generic",
"vodafone-elen": "vodafone-elen"
}
},
{
"name": "sender-name",
"schema": {
"type": "string"
},
"required": false,
"requiresConsent": true,
"wire": [
{
"path": "providerConfig.senderName",
"transform": "trim",
"role": "body",
"input": "sender-name",
"contributors": [
"sender-name"
]
}
]
},
{
"name": "countries",
"schema": {
"type": "string",
"pattern": "^(?:[^,]*[^,\\s][^,]*)(?:,(?:[^,]*[^,\\s][^,]*))*$"
},
"required": false,
"requiresConsent": true,
"wire": [
{
"path": "countries",
"transform": "split-trim",
"role": "body",
"input": "countries",
"contributors": [
"countries"
]
}
],
"validation": {
"syntax": "Comma-separated values; every segment must contain at least one non-whitespace character."
}
},
{
"name": "steps-file",
"schema": {
"type": "string"
},
"required": false,
"requiresConsent": true,
"input": {
"role": "input-file",
"contract": "byo-steps",
"requestTargets": [
{
"role": "body",
"path": "steps",
"transform": "json-array"
}
],
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS HTTP steps replacement",
"description": "Ordered HTTP request step array replacing the saved chain when supplied. Empty array is accepted for update; each present step needs url and follows the server model.",
"type": "array",
"items": {
"$ref": "#/$defs/step"
},
"$defs": {
"stringMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"auth": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"successAssertion": {
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "number"
}
},
"jsonPathEquals": {},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"extract": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": false
}
},
"step": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"minLength": 1,
"pattern": "\\S"
},
"headers": {
"$ref": "#/$defs/stringMap"
},
"query": {
"$ref": "#/$defs/stringMap"
},
"bodyType": {
"type": "string",
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$ref": "#/$defs/auth"
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$ref": "#/$defs/successAssertion"
},
"extract": {
"$ref": "#/$defs/extract"
}
},
"required": [
"url"
],
"additionalProperties": false
}
}
},
"localReaderSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": true
}
]
}
},
"fileContract": {
"format": "json-array",
"maxBytes": 1048576,
"regularFile": true,
"reader": "files.js:readJsonArray",
"type": "json-array",
"exclusive": false,
"mode": null,
"trim": false,
"writer": null,
"minBytes": 0,
"detectChanges": true,
"fieldMappings": {}
},
"semanticSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS HTTP steps replacement",
"description": "Ordered HTTP request step array replacing the saved chain when supplied. Empty array is accepted for update; each present step needs url and follows the server model.",
"type": "array",
"items": {
"$ref": "#/$defs/step"
},
"$defs": {
"stringMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"auth": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"successAssertion": {
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "number"
}
},
"jsonPathEquals": {},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"extract": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": false
}
},
"step": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"minLength": 1,
"pattern": "\\S"
},
"headers": {
"$ref": "#/$defs/stringMap"
},
"query": {
"$ref": "#/$defs/stringMap"
},
"bodyType": {
"type": "string",
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$ref": "#/$defs/auth"
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$ref": "#/$defs/successAssertion"
},
"extract": {
"$ref": "#/$defs/extract"
}
},
"required": [
"url"
],
"additionalProperties": false
}
}
},
"validationBoundary": "semanticSchema describes curated command file-content constraints; localReaderSchema describes file-reader constraints. Read command help and schema descriptions for additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "steps-file",
"inputRole": "input-file",
"transform": "identity",
"fileContract": "byo-steps",
"contributors": [
"steps-file"
]
},
{
"role": "body",
"path": "steps",
"transform": "json-array",
"input": "steps-file",
"contributors": [
"steps-file"
],
"fileContract": "byo-steps"
}
]
},
{
"name": "secrets-file",
"schema": {
"type": "string"
},
"required": false,
"requiresConsent": true,
"input": {
"role": "input-file",
"contract": "json-object",
"requestTargets": [
{
"role": "body",
"path": "secrets",
"transform": "json-object"
}
],
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS secret values",
"description": "JSON object mapping secret names to values used by {{secrets.name}} in steps. Names use letters, digits and underscore. Empty or null entries are ignored by the server secret-issue check; omit values that are not being set.",
"type": "object",
"propertyNames": {
"pattern": "^\\w+$"
},
"additionalProperties": {
"type": [
"string",
"null"
]
}
},
"localReaderSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": true
}
]
}
},
"fileContract": {
"format": "json-object",
"maxBytes": 65536,
"regularFile": true,
"reader": "files.js:readJsonObject",
"type": "json-object",
"exclusive": false,
"mode": null,
"trim": false,
"writer": null,
"minBytes": 0,
"detectChanges": true,
"fieldMappings": {}
},
"semanticSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS secret values",
"description": "JSON object mapping secret names to values used by {{secrets.name}} in steps. Names use letters, digits and underscore. Empty or null entries are ignored by the server secret-issue check; omit values that are not being set.",
"type": "object",
"propertyNames": {
"pattern": "^\\w+$"
},
"additionalProperties": {
"type": [
"string",
"null"
]
}
},
"validationBoundary": "semanticSchema describes curated command file-content constraints; localReaderSchema describes file-reader constraints. Read command help and schema descriptions for additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "secrets-file",
"inputRole": "input-file",
"transform": "identity",
"fileContract": "json-object",
"contributors": [
"secrets-file"
]
},
{
"role": "body",
"path": "secrets",
"transform": "json-object",
"input": "secrets-file",
"contributors": [
"secrets-file"
],
"fileContract": "json-object"
}
]
},
{
"name": "test-phone-number",
"schema": {
"type": "string"
},
"required": false,
"wire": [
{
"path": "testPhone",
"transform": "identity",
"role": "body",
"input": "test-phone-number",
"contributors": [
"test-phone-number"
]
}
]
},
{
"name": "yes",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "yes",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "yes",
"inputRole": "local-control",
"transform": "identity",
"purpose": "yes",
"contributors": [
"yes"
]
}
]
}
],
"files": [
{
"name": "byo-steps",
"contract": {
"format": "json-array",
"maxBytes": 1048576,
"regularFile": true,
"reader": "files.js:readJsonArray",
"type": "json-array",
"exclusive": false,
"mode": null,
"trim": false,
"writer": null,
"minBytes": 0,
"detectChanges": true,
"fieldMappings": {}
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": true
}
]
}
},
"uses": [
{
"flag": "steps-file",
"role": "input-file",
"purpose": null,
"semanticSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS HTTP steps replacement",
"description": "Ordered HTTP request step array replacing the saved chain when supplied. Empty array is accepted for update; each present step needs url and follows the server model.",
"type": "array",
"items": {
"$ref": "#/$defs/step"
},
"$defs": {
"stringMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"auth": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"successAssertion": {
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "number"
}
},
"jsonPathEquals": {},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"extract": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"type": "string",
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": false
}
},
"step": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"minLength": 1,
"pattern": "\\S"
},
"headers": {
"$ref": "#/$defs/stringMap"
},
"query": {
"$ref": "#/$defs/stringMap"
},
"bodyType": {
"type": "string",
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$ref": "#/$defs/auth"
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$ref": "#/$defs/successAssertion"
},
"extract": {
"$ref": "#/$defs/extract"
}
},
"required": [
"url"
],
"additionalProperties": false
}
}
}
}
]
},
{
"name": "json-object",
"contract": {
"format": "json-object",
"maxBytes": 65536,
"regularFile": true,
"reader": "files.js:readJsonObject",
"type": "json-object",
"exclusive": false,
"mode": null,
"trim": false,
"writer": null,
"minBytes": 0,
"detectChanges": true,
"fieldMappings": {}
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": true
}
]
}
},
"uses": [
{
"flag": "secrets-file",
"role": "input-file",
"purpose": null,
"semanticSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "BYO-SMS secret values",
"description": "JSON object mapping secret names to values used by {{secrets.name}} in steps. Names use letters, digits and underscore. Empty or null entries are ignored by the server secret-issue check; omit values that are not being set.",
"type": "object",
"propertyNames": {
"pattern": "^\\w+$"
},
"additionalProperties": {
"type": [
"string",
"null"
]
}
}
}
]
}
],
"wire": {
"method": "PUT",
"route": "/byo-sms/integrations/:id",
"query": {},
"body": {
"name": [
{
"path": "name",
"transform": "trim",
"role": "body",
"input": "name",
"contributors": [
"name"
]
}
],
"description": [
{
"path": "description",
"transform": "identity",
"role": "body",
"input": "description",
"contributors": [
"description"
]
}
],
"on-failure": [
{
"path": "onFailure",
"transform": "identity",
"role": "body",
"input": "on-failure",
"contributors": [
"on-failure"
]
}
],
"provider": [
{
"path": "providerKey",
"transform": "identity",
"role": "body",
"input": "provider",
"contributors": [
"provider"
]
}
],
"sender-name": [
{
"path": "providerConfig.senderName",
"transform": "trim",
"role": "body",
"input": "sender-name",
"contributors": [
"sender-name"
]
}
],
"countries": [
{
"path": "countries",
"transform": "split-trim",
"role": "body",
"input": "countries",
"contributors": [
"countries"
]
}
],
"test-phone-number": [
{
"path": "testPhone",
"transform": "identity",
"role": "body",
"input": "test-phone-number",
"contributors": [
"test-phone-number"
]
}
]
},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"userID": {
"type": "string"
},
"pipelineID": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"uid": {
"type": "string"
},
"providerKey": {
"enum": [
"generic",
"vodafone-elen"
]
},
"adapterVersion": {
"type": [
"string",
"null"
]
},
"providerConfig": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"senderName": {
"type": [
"string",
"null"
]
}
},
"required": [
"senderName"
],
"additionalProperties": true
},
"countries": {
"type": "array",
"items": {
"type": "string"
}
},
"steps": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"format": "uri"
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"query": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"bodyType": {
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"additionalProperties": true
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "integer"
}
},
"jsonPathEquals": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
},
"extract": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"required": [
"url"
],
"additionalProperties": true
}
},
"onFailure": {
"enum": [
"strict",
"fallbackToAkedly"
]
},
"feeOverride": {
"type": [
"number",
"null"
]
},
"active": {
"type": "boolean"
},
"liveAnnouncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"testPhone": {
"type": [
"string",
"null"
]
},
"testStatus": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"passed": {
"type": "boolean"
},
"lastTestedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastResultSummary": {
"type": [
"string",
"null"
]
},
"providerSmsId": {
"type": [
"string",
"null"
]
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"contractFingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"passed",
"lastTestedAt",
"lastResultSummary",
"providerSmsId",
"certainty",
"failureCategory",
"lastTestKey",
"contractFingerprint"
],
"additionalProperties": true
},
"configHash": {
"type": [
"string",
"null"
]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"stepsVisible": {
"type": "boolean"
},
"feeVisible": {
"type": "boolean"
},
"secretNames": {
"type": "array",
"items": {
"type": "string"
}
},
"vodafoneReadiness": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"providerKey": {
"const": "vodafone-elen"
},
"adapterVersion": {
"type": "string"
},
"supportedCountries": {
"type": "array",
"items": {
"const": "EG"
}
},
"credentials": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"required": {
"type": "array",
"items": {
"type": "string"
}
},
"configured": {
"type": "boolean"
},
"missing": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"required"
],
"additionalProperties": true
},
"sender": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"configured": {
"type": "boolean"
}
},
"required": [
"configured"
],
"additionalProperties": true
},
"pilotEnabled": {
"type": "boolean"
},
"pilotAccountAllowed": {
"type": [
"boolean",
"null"
]
},
"pilotAccountIdsConfigured": {
"type": "boolean"
},
"adapterAvailable": {
"type": "boolean"
},
"quotaDebitLedgerReady": {
"type": "boolean"
},
"contractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractMatches": {
"type": "boolean"
},
"deploymentBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"egress": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"ips": {
"type": "array",
"items": {
"type": "string",
"format": "ipv4"
}
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
},
"fingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"environment",
"environmentLabel",
"ips",
"status",
"verified",
"fingerprint"
],
"additionalProperties": true
},
"externalActivationRequired": {
"type": "boolean"
},
"testEligible": {
"type": "boolean"
},
"activationEligible": {
"type": "boolean"
},
"setupBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"testBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"activationBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"blocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"whitelistAcknowledged": {
"type": "boolean"
}
},
"required": [
"providerKey",
"adapterVersion",
"supportedCountries",
"pilotEnabled",
"pilotAccountAllowed",
"pilotAccountIdsConfigured",
"adapterAvailable",
"quotaDebitLedgerReady",
"contractFingerprint",
"deploymentBlocker",
"egress",
"externalActivationRequired",
"testEligible",
"activationEligible",
"setupBlocker",
"testBlocker",
"activationBlocker",
"blocker"
],
"additionalProperties": true
},
"routingEnabled": {
"type": "boolean"
},
"routingHint": {
"type": "string"
},
"resolvedFeeEGP": {
"type": "number",
"minimum": 0
}
},
"required": [
"_id",
"userID",
"name",
"uid",
"providerKey",
"countries",
"onFailure",
"active",
"testStatus",
"stepsVisible",
"feeVisible",
"secretNames"
],
"additionalProperties": true
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": "The CLI scrubs recognized secret values (prefixed secrets, credential fields, secret query parameters and JSON secret fields inside strings) from the response before projection; keep the output private anyway."
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route v2",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms delete
Permanently remove route; its coverage can no longer send.
When to use
- Retire route after checking dependencies.
How it works
- Deletes owned integration with
--yes.
Prerequisites
- Owned id and confirmation.
Expected outcome
Deletion message.
Next steps
- Reevaluate remaining coverage and routing.
Usage
akedly byo-sms delete <id>
Inspect this command offline before preparing an operation:
akedly describe "byo-sms delete" --output json
| Positional | Grammar | Usage guidance |
|---|---|---|
| <id> | ^[a-fA-F0-9]{24}$ | Use the id from akedly byo-sms list. |
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--dry-run | boolean switch | — | Preview without sending the write or creating output files; planning reads may still occur. Bypass consent. |
--yes | boolean switch | — | Confirm this action where consent is required. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:delete.
Server request: DELETE /byo-sms/integrations/:id.
Consent
--yes. --dry-run bypasses confirmation where supported. Destructive: true. Spends money: false. Real send: false.
Retry
write: Non-idempotent writes are not automatically retried. guarded-write: when an update carries the revision read from the server, its write is not automatically retried after network errors or HTTP 429/502/503/504. It may already be saved; inspect the resource before rerunning. A rerun reads current settings again. The explicit CLI_RATE_LIMITED refusal exception below still applies. recommendation-read: the recommendation request made while planning --recommended-limits is not automatically retried after network errors or HTTP 429/502/503/504. If planning fails, no update is sent; rerun the command to request a new recommendation. The explicit CLI_RATE_LIMITED refusal exception below still applies. This label does not change the retry policy of the standalone rate-recommendations command.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
Permanently delete your integration. Requires --yes; sends using this integration can no longer route through it. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are refused.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Retire route after checking dependencies.
akedly byo-sms delete 64b000000000000000000001 --yes
Illustrative response (sensitive values omitted):
{
"status": "success",
"message": "Integration deleted"
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms delete <id> --yes
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [
{
"name": "id",
"type": "string",
"required": true,
"pattern": "^[a-fA-F0-9]{24}$",
"validation": "Use the id from akedly byo-sms list.",
"mapsToRouteParameter": "id",
"routeParameterValidation": "A route parameter cannot be . or .., including percent-encoded forms."
}
],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "dry-run",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "dry-run",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "dry-run",
"inputRole": "local-control",
"transform": "identity",
"purpose": "dry-run",
"contributors": [
"dry-run"
]
}
]
},
{
"name": "yes",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "yes",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "yes",
"inputRole": "local-control",
"transform": "identity",
"purpose": "yes",
"contributors": [
"yes"
]
}
]
}
],
"files": [],
"wire": {
"method": "DELETE",
"route": "/byo-sms/integrations/:id",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"message": {
"const": "Integration deleted"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"message": "Integration deleted"
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms clone
Copy an integration and encrypted credentials/fee override to an owned non-system pipeline, inactive.
When to use
- Isolate route settings per pipeline.
How it works
- Revalidates countries and Vodafone provisioning gates before copying.
Prerequisites
- Source owned, pipeline owned/non-system, coverage routable; Vodafone pilot eligibility.
Expected outcome
New inactive pipeline integration.
Next steps
- Test copy independently, then activate and set pipeline route policy.
Usage
akedly byo-sms clone <id>
Inspect this command offline before preparing an operation:
akedly describe "byo-sms clone" --output json
| Positional | Grammar | Usage guidance |
|---|---|---|
| <id> | ^[a-fA-F0-9]{24}$ | Use the id from akedly byo-sms list. |
Flags
| Flag | Type | Enum / range | Required | Description |
|---|---|---|---|---|
--ksa | boolean switch | — | no | Select the KSA deployment. |
--dry-run | boolean switch | — | no | Preview without sending the write or creating output files; planning reads may still occur. Bypass consent. |
--pipeline-id | id | — | yes | Command-specific option; see the full command details below. Manifest body field: pipelineID. |
--help | boolean switch | — | no | Show offline command help. |
--output | enum | json | human | no | Select JSON or human output. |
--fields | dot-paths | — | no | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:create.
Server request: POST /byo-sms/integrations/:id/clone.
Consent
none. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
write: Non-idempotent writes are not automatically retried. guarded-write: when an update carries the revision read from the server, its write is not automatically retried after network errors or HTTP 429/502/503/504. It may already be saved; inspect the resource before rerunning. A rerun reads current settings again. The explicit CLI_RATE_LIMITED refusal exception below still applies. recommendation-read: the recommendation request made while planning --recommended-limits is not automatically retried after network errors or HTTP 429/502/503/504. If planning fails, no update is sent; rerun the command to request a new recommendation. The explicit CLI_RATE_LIMITED refusal exception below still applies. This label does not change the retry policy of the standalone rate-recommendations command.
Output
Sensitive output: echo-scrub. echo-scrub masks secret-shaped text, credential fields, secret query parameters and JSON secret fields inside strings in the response, such as a webhook receiver echo or BYO-SMS steps; keep the output private anyway.
Full command details
Copy an owned integration, including encrypted secrets and its fee override, onto an owned non-system pipeline. The copy is inactive and must pass its own test before activation. Vodafone provisioning requires a supported region and the account to be allowed in the pilot; HTTP 403 names the blocker. Check akedly byo-sms vodafone readiness. The source coverage is revalidated; SOURCE_COVERAGE_UNROUTABLE requires fixing the source before cloning. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are refused.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Isolate route settings per pipeline.
akedly byo-sms clone 64b000000000000000000001 --pipeline-id 64b000000000000000000002
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": "64b000000000000000000002",
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms clone <id> --pipeline-id <id>
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [
{
"name": "id",
"type": "string",
"required": true,
"pattern": "^[a-fA-F0-9]{24}$",
"validation": "Use the id from akedly byo-sms list.",
"mapsToRouteParameter": "id",
"routeParameterValidation": "A route parameter cannot be . or .., including percent-encoded forms."
}
],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "dry-run",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "dry-run",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "dry-run",
"inputRole": "local-control",
"transform": "identity",
"purpose": "dry-run",
"contributors": [
"dry-run"
]
}
]
},
{
"name": "pipeline-id",
"schema": {
"type": "string",
"pattern": "^[a-fA-F0-9]{24}$"
},
"required": true,
"wire": [
{
"path": "pipelineID",
"transform": "identity",
"role": "body",
"input": "pipeline-id",
"contributors": [
"pipeline-id"
]
}
],
"validation": {
"syntax": "24-digit hexadecimal object ID."
}
}
],
"files": [],
"wire": {
"method": "POST",
"route": "/byo-sms/integrations/:id/clone",
"query": {},
"body": {
"pipeline-id": [
{
"path": "pipelineID",
"transform": "identity",
"role": "body",
"input": "pipeline-id",
"contributors": [
"pipeline-id"
]
}
]
},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"userID": {
"type": "string"
},
"pipelineID": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"uid": {
"type": "string"
},
"providerKey": {
"enum": [
"generic",
"vodafone-elen"
]
},
"adapterVersion": {
"type": [
"string",
"null"
]
},
"providerConfig": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"senderName": {
"type": [
"string",
"null"
]
}
},
"required": [
"senderName"
],
"additionalProperties": true
},
"countries": {
"type": "array",
"items": {
"type": "string"
}
},
"steps": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"format": "uri"
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"query": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"bodyType": {
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"additionalProperties": true
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "integer"
}
},
"jsonPathEquals": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
},
"extract": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"required": [
"url"
],
"additionalProperties": true
}
},
"onFailure": {
"enum": [
"strict",
"fallbackToAkedly"
]
},
"feeOverride": {
"type": [
"number",
"null"
]
},
"active": {
"type": "boolean"
},
"liveAnnouncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"testPhone": {
"type": [
"string",
"null"
]
},
"testStatus": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"passed": {
"type": "boolean"
},
"lastTestedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastResultSummary": {
"type": [
"string",
"null"
]
},
"providerSmsId": {
"type": [
"string",
"null"
]
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"contractFingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"passed",
"lastTestedAt",
"lastResultSummary",
"providerSmsId",
"certainty",
"failureCategory",
"lastTestKey",
"contractFingerprint"
],
"additionalProperties": true
},
"configHash": {
"type": [
"string",
"null"
]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"stepsVisible": {
"type": "boolean"
},
"feeVisible": {
"type": "boolean"
},
"secretNames": {
"type": "array",
"items": {
"type": "string"
}
},
"vodafoneReadiness": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"providerKey": {
"const": "vodafone-elen"
},
"adapterVersion": {
"type": "string"
},
"supportedCountries": {
"type": "array",
"items": {
"const": "EG"
}
},
"credentials": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"required": {
"type": "array",
"items": {
"type": "string"
}
},
"configured": {
"type": "boolean"
},
"missing": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"required"
],
"additionalProperties": true
},
"sender": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"configured": {
"type": "boolean"
}
},
"required": [
"configured"
],
"additionalProperties": true
},
"pilotEnabled": {
"type": "boolean"
},
"pilotAccountAllowed": {
"type": [
"boolean",
"null"
]
},
"pilotAccountIdsConfigured": {
"type": "boolean"
},
"adapterAvailable": {
"type": "boolean"
},
"quotaDebitLedgerReady": {
"type": "boolean"
},
"contractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractMatches": {
"type": "boolean"
},
"deploymentBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"egress": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"ips": {
"type": "array",
"items": {
"type": "string",
"format": "ipv4"
}
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
},
"fingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"environment",
"environmentLabel",
"ips",
"status",
"verified",
"fingerprint"
],
"additionalProperties": true
},
"externalActivationRequired": {
"type": "boolean"
},
"testEligible": {
"type": "boolean"
},
"activationEligible": {
"type": "boolean"
},
"setupBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"testBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"activationBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"blocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"whitelistAcknowledged": {
"type": "boolean"
}
},
"required": [
"providerKey",
"adapterVersion",
"supportedCountries",
"pilotEnabled",
"pilotAccountAllowed",
"pilotAccountIdsConfigured",
"adapterAvailable",
"quotaDebitLedgerReady",
"contractFingerprint",
"deploymentBlocker",
"egress",
"externalActivationRequired",
"testEligible",
"activationEligible",
"setupBlocker",
"testBlocker",
"activationBlocker",
"blocker"
],
"additionalProperties": true
},
"routingEnabled": {
"type": "boolean"
},
"routingHint": {
"type": "string"
},
"resolvedFeeEGP": {
"type": "number",
"minimum": 0
}
},
"required": [
"_id",
"userID",
"name",
"uid",
"providerKey",
"countries",
"onFailure",
"active",
"testStatus",
"stepsVisible",
"feeVisible",
"secretNames"
],
"additionalProperties": true
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": "The CLI scrubs recognized secret values (prefixed secrets, credential fields, secret query parameters and JSON secret fields inside strings) from the response before projection; keep the output private anyway."
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": "64b000000000000000000002",
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms test
Send one real activation test through saved provider chain; provider acceptance is not handset delivery.
When to use
- Validate exact workflow and recipient before route activation.
How it works
- Generic: supplied/saved phone passed to placeholders, but saved steps decide actual recipient and may hardcode it; retries may send again. Vodafone uses managed handset and guarded operation recovery.
Prerequisites
- Reviewed get/config/actual recipient,
--yes, entitlement; Vodafone readiness.
Expected outcome
accepted and testedTo plus trace; 200 accepted:false means failed test, 202 may mean replay pending.
Next steps
- Inspect get before retrying ambiguity; preserve same Vodafone key. Activation requires current passing test.
Usage
akedly byo-sms test <id>
Inspect this command offline before preparing an operation:
akedly describe "byo-sms test" --output json
| Positional | Grammar | Usage guidance |
|---|---|---|
| <id> | ^[a-fA-F0-9]{24}$ | Use the id from akedly byo-sms list. |
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--dry-run | boolean switch | — | Preview without sending the write or creating output files; planning reads may still occur. Bypass consent. |
--yes | boolean switch | — | Confirm this action where consent is required. |
--test-phone-number | text | — | Command-specific option; see the full command details below. Manifest body field: testPhone. |
--idempotency-key | text | — | Command-specific option; see the full command details below. Manifest body field: idempotencyKey. |
--allow-new-after-unknown | boolean switch | — | Command-specific option; see the full command details below. Manifest body field: allowNewAfterUnknown. |
--previous-test-key | text | — | Command-specific option; see the full command details below. Manifest body field: newAttempt.reason, newAttempt.previousTestKey. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:execute.
Server request: POST /byo-sms/integrations/:id/test.
Consent
--yes. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: true.
Retry
write: Non-idempotent writes are not automatically retried. guarded-write: when an update carries the revision read from the server, its write is not automatically retried after network errors or HTTP 429/502/503/504. It may already be saved; inspect the resource before rerunning. A rerun reads current settings again. The explicit CLI_RATE_LIMITED refusal exception below still applies. recommendation-read: the recommendation request made while planning --recommended-limits is not automatically retried after network errors or HTTP 429/502/503/504. If planning fails, no update is sent; rerun the command to request a new recommendation. The explicit CLI_RATE_LIMITED refusal exception below still applies. This label does not change the retry policy of the standalone rate-recommendations command.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
Send a real activation-test message; requires --yes. Generic tests pass --test-phone-number, or else the saved destination, to the saved steps as {{phone}}/{{phoneLocal}}. The saved steps decide the real recipient; a step that hard-codes a number sends there. Check url and bodyTemplate with akedly byo-sms get <id>; step headers and query currently show as {}, so a number there is not visible. data.testedTo is the destination Akedly supplied, not proof of the real recipient. A generic test saves that supplied destination as its test number; a later bare test reuses the saved number. Vodafone ignores --test-phone-number and always uses Akedly's managed test handset. New Vodafone test attempts return HTTP 409 with a readiness blocker until vodafoneReadiness.testEligible. A UUID idempotencyKey is generated when omitted and retained in error details; reuse the same key after an ambiguous Vodafone result. Generic tests do not deduplicate by key and retrying can send again. --allow-new-after-unknown explicitly permits a new Vodafone attempt after an uncertain result; --previous-test-key explicitly requests another attempt after an accepted result. Keys are 1 to 255 characters. HTTP 200 with data.accepted:false is a failed test but exits 0; inspect data.accepted and data.testedTo. HTTP 202 is an in-progress replay. A pass can notify Akedly on Discord; failure deactivates the integration. Best-effort per-process hourly limits are 20/account and 10/recipient for generic tests, 10/account and 20/shared handset for Vodafone, and 60 duplicate replays/account. TEST_RATE_LIMITED returns HTTP 429 with Retry-After and cooldownSeconds; no automatic retry. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are refused.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Validate exact workflow and recipient before route activation.
akedly byo-sms test 64b000000000000000000001 --test-phone-number +201234567890 --idempotency-key 11111111-1111-4111-8111-111111111111 --yes
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"accepted": true,
"testedTo": "+201234567890",
"trace": [
{
"step": 0,
"attempt": 1,
"method": "POST",
"url": "https://sms.example.invalid/send",
"requestHeaders": {
"content-type": "application/json"
},
"requestBody": "{\"to\":\"+201234567890\",\"text\":\"[REDACTED]\",\"key\":\"[REDACTED]\"}",
"status": 200,
"responseBody": "{\"accepted\":true}",
"ok": true
}
],
"failureReason": null,
"failedStep": null,
"certainty": "accepted",
"failureCategory": null,
"providerSmsId": "provider-message-synthetic",
"providerAccepted": true,
"providerKey": "generic",
"submissionStatus": "accepted",
"lastTestKey": null,
"certificationBlocked": false,
"passed": true,
"billingPending": false,
"billed": false,
"transactionID": null,
"operationId": null,
"operationReplayable": false,
"replayable": false,
"guidance": null
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms test <id> --yes
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [
{
"name": "id",
"type": "string",
"required": true,
"pattern": "^[a-fA-F0-9]{24}$",
"validation": "Use the id from akedly byo-sms list.",
"mapsToRouteParameter": "id",
"routeParameterValidation": "A route parameter cannot be . or .., including percent-encoded forms."
}
],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "dry-run",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "dry-run",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "dry-run",
"inputRole": "local-control",
"transform": "identity",
"purpose": "dry-run",
"contributors": [
"dry-run"
]
}
]
},
{
"name": "yes",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "yes",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "yes",
"inputRole": "local-control",
"transform": "identity",
"purpose": "yes",
"contributors": [
"yes"
]
}
]
},
{
"name": "test-phone-number",
"schema": {
"type": "string"
},
"required": false,
"input": {
"role": "handler-built",
"targets": [
{
"role": "body",
"path": "testPhone",
"transform": "string"
}
],
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "body",
"path": "testPhone",
"transform": "string",
"input": "test-phone-number",
"contributors": [
"test-phone-number"
]
}
]
},
{
"name": "idempotency-key",
"schema": {
"type": "string",
"pattern": "^\\s*\\S(?:[\\s\\S]{0,253}\\S)?\\s*$"
},
"required": false,
"input": {
"role": "handler-built",
"targets": [
{
"role": "body",
"path": "idempotencyKey",
"transform": "trim"
}
],
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "body",
"path": "idempotencyKey",
"transform": "trim",
"input": "idempotency-key",
"contributors": [
"idempotency-key"
]
}
],
"validation": {
"syntax": "After trimming, length must be 1–255 characters."
}
},
{
"name": "allow-new-after-unknown",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "handler-built",
"targets": [
{
"role": "body",
"path": "allowNewAfterUnknown",
"transform": "present"
}
],
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "body",
"path": "allowNewAfterUnknown",
"transform": "present",
"input": "allow-new-after-unknown",
"contributors": [
"allow-new-after-unknown"
]
}
]
},
{
"name": "previous-test-key",
"schema": {
"type": "string",
"pattern": "^\\s*\\S(?:[\\s\\S]{0,253}\\S)?\\s*$"
},
"required": false,
"input": {
"role": "handler-built",
"targets": [
{
"role": "body",
"path": "newAttempt.reason",
"transform": "accepted-retest"
},
{
"role": "body",
"path": "newAttempt.previousTestKey",
"transform": "trim"
}
],
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "body",
"path": "newAttempt.reason",
"transform": "accepted-retest",
"input": "previous-test-key",
"contributors": [
"previous-test-key"
]
},
{
"role": "body",
"path": "newAttempt.previousTestKey",
"transform": "trim",
"input": "previous-test-key",
"contributors": [
"previous-test-key"
]
}
],
"validation": {
"syntax": "After trimming, length must be 1–255 characters."
}
}
],
"files": [],
"wire": {
"method": "POST",
"route": "/byo-sms/integrations/:id/test",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
Trace rows are the executor’s redacted/truncated HTTP request and response evidence; they may still include provider URLs and other operational data, so keep them private.
Generic and Vodafone test responses share accepted/testedTo/trace; Vodafone may add certainty, certification and operation-replay fields. HTTP 200 accepted:false is a completed failure; HTTP 202 carries inProgress.
Trace rows are redacted/truncated executor request and response evidence; URLs and operational metadata remain private.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"type": "object",
"properties": {
"accepted": {
"type": "boolean"
},
"testedTo": {
"type": "string"
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"providerAccepted": {
"type": "boolean"
},
"providerKey": {
"type": "string"
},
"submissionStatus": {
"type": "string"
},
"transactionID": {
"type": [
"string",
"null"
]
},
"operationId": {
"type": [
"string",
"null"
]
},
"operationReplayable": {
"type": "boolean"
},
"replayable": {
"type": "boolean"
},
"trace": {
"type": "array",
"items": {
"type": "object",
"properties": {
"step": {
"type": "integer"
},
"attempt": {
"type": "integer"
},
"method": {
"type": "string"
},
"url": {
"type": "string"
},
"requestHeaders": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"requestBody": {
"type": "string"
},
"status": {
"type": "integer"
},
"responseBody": {
"type": "string"
},
"ok": {
"type": "boolean"
},
"error": {
"type": "string"
}
},
"required": [
"step",
"attempt"
],
"additionalProperties": true
}
},
"failureReason": {
"type": [
"string",
"null"
]
},
"guidance": {
"type": [
"string",
"null"
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"certificationBlocked": {
"type": "boolean"
},
"passed": {
"type": "boolean"
},
"billingPending": {
"type": "boolean"
},
"billed": {
"type": "boolean"
}
},
"additionalProperties": true,
"required": [
"accepted",
"testedTo",
"trace"
]
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"type": "object",
"properties": {
"accepted": {
"type": "boolean"
},
"testedTo": {
"type": "string"
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"providerAccepted": {
"type": "boolean"
},
"providerKey": {
"type": "string"
},
"submissionStatus": {
"type": "string"
},
"transactionID": {
"type": [
"string",
"null"
]
},
"operationId": {
"type": [
"string",
"null"
]
},
"operationReplayable": {
"type": "boolean"
},
"replayable": {
"type": "boolean"
},
"trace": {
"type": "array",
"items": {
"type": "object",
"properties": {
"step": {
"type": "integer"
},
"attempt": {
"type": "integer"
},
"method": {
"type": "string"
},
"url": {
"type": "string"
},
"requestHeaders": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"requestBody": {
"type": "string"
},
"status": {
"type": "integer"
},
"responseBody": {
"type": "string"
},
"ok": {
"type": "boolean"
},
"error": {
"type": "string"
}
},
"required": [
"step",
"attempt"
],
"additionalProperties": true
}
},
"failureReason": {
"type": [
"string",
"null"
]
},
"guidance": {
"type": [
"string",
"null"
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"certificationBlocked": {
"type": "boolean"
},
"passed": {
"type": "boolean"
},
"billingPending": {
"type": "boolean"
},
"billed": {
"type": "boolean"
},
"inProgress": {
"type": "boolean"
}
},
"additionalProperties": true,
"required": [
"inProgress"
]
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"title": "A failed completed test may still be HTTP 200 with data.accepted false; an accepted replay may be HTTP 202 with data.inProgress true.",
"when": "A failed completed test may still be HTTP 200 with data.accepted false; an accepted replay may be HTTP 202 with data.inProgress true.",
"notes": []
}
],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"accepted": true,
"testedTo": "+201234567890",
"trace": [
{
"step": 0,
"attempt": 1,
"method": "POST",
"url": "https://sms.example.invalid/send",
"requestHeaders": {
"content-type": "application/json"
},
"requestBody": "{\"to\":\"+201234567890\",\"text\":\"[REDACTED]\",\"key\":\"[REDACTED]\"}",
"status": 200,
"responseBody": "{\"accepted\":true}",
"ok": true
}
],
"failureReason": null,
"failedStep": null,
"certainty": "accepted",
"failureCategory": null,
"providerSmsId": "provider-message-synthetic",
"providerAccepted": true,
"providerKey": "generic",
"submissionStatus": "accepted",
"lastTestKey": null,
"certificationBlocked": false,
"passed": true,
"billingPending": false,
"billed": false,
"transactionID": null,
"operationId": null,
"operationReplayable": false,
"replayable": false,
"guidance": null
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms set-active
Activate/deactivate integration; active state can differ from effective routing.
When to use
- Activate a tested conflict-free route or immediately stop one.
How it works
- Requires exactly one action; activation checks test/config, country overlap, and Vodafone eligibility; reports routingEnabled separately.
Prerequisites
- Owned route; activation needs
--yesand passing current test; Vodafone eligibility.
Expected outcome
Active state plus root routingEnabled and optional reason hint.
Next steps
- Follow hint to account/pipeline BYO-SMS switch; recheck get/list.
Usage
akedly byo-sms set-active <id>
Inspect this command offline before preparing an operation:
akedly describe "byo-sms set-active" --output json
| Positional | Grammar | Usage guidance |
|---|---|---|
| <id> | ^[a-fA-F0-9]{24}$ | Use the id from akedly byo-sms list. |
Flags
| Flag | Type | Enum / range | Description |
|---|---|---|---|
--ksa | boolean switch | — | Select the KSA deployment. |
--dry-run | boolean switch | — | Preview without sending the write or creating output files; planning reads may still occur. Bypass consent. |
--yes | boolean switch | — | Confirm this action where consent is required. |
--activate | boolean switch | — | Command-specific option; see the full command details below. Manifest body field: active. |
--deactivate | boolean switch | — | Command-specific option; see the full command details below. Manifest body field: active. |
--help | boolean switch | — | Show offline command help. |
--output | enum | json | human | Select JSON or human output. |
--fields | dot-paths | — | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:update.
Server request: POST /byo-sms/integrations/:id/activate.
Consent
--yes when --activate is given. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
write: Non-idempotent writes are not automatically retried. guarded-write: when an update carries the revision read from the server, its write is not automatically retried after network errors or HTTP 429/502/503/504. It may already be saved; inspect the resource before rerunning. A rerun reads current settings again. The explicit CLI_RATE_LIMITED refusal exception below still applies. recommendation-read: the recommendation request made while planning --recommended-limits is not automatically retried after network errors or HTTP 429/502/503/504. If planning fails, no update is sent; rerun the command to request a new recommendation. The explicit CLI_RATE_LIMITED refusal exception below still applies. This label does not change the retry policy of the standalone rate-recommendations command.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
Pass exactly one of --activate or --deactivate. Activation needs --yes, a passing test for the current configuration and no country conflict in this scope; deactivation needs no --yes. On a country-conflict HTTP 409, run akedly byo-sms list and deactivate the active integration in the same pipelineID scope that covers overlapping countries; the CLI omits the server conflicts list. Vodafone activation also requires vodafoneReadiness.activationEligible; HTTP 409 names its readiness blocker. First activation can notify Akedly on Discord and WhatsApp. routingEnabled and routingHint are top-level response fields beside data. The response includes routingEnabled and possibly routingHint: active alone does not mean traffic routes through it; account/pipeline routing must also allow BYO-SMS. Change a pipeline's BYO-SMS setting with akedly pipelines update <id> --byo-sms inherit|on|off --yes; the account switch is set by Akedly. When active is true but routingEnabled is false, this integration is not taking production traffic; follow routingHint. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are refused.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
Activate a tested conflict-free route or immediately stop one.
akedly byo-sms set-active 64b000000000000000000001 --activate --yes
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": true,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": true,
"lastTestedAt": "2026-01-01T00:00:00.000Z",
"lastResultSummary": "passed",
"providerSmsId": "synthetic-provider-id",
"certainty": "accepted",
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
},
"routingEnabled": false,
"routingHint": "Integration is active, but BYO-SMS routing is off for this account/pipeline."
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms set-active <id> --activate --yes
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [
{
"name": "id",
"type": "string",
"required": true,
"pattern": "^[a-fA-F0-9]{24}$",
"validation": "Use the id from akedly byo-sms list.",
"mapsToRouteParameter": "id",
"routeParameterValidation": "A route parameter cannot be . or .., including percent-encoded forms."
}
],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "dry-run",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "dry-run",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "dry-run",
"inputRole": "local-control",
"transform": "identity",
"purpose": "dry-run",
"contributors": [
"dry-run"
]
}
]
},
{
"name": "yes",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "yes",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "yes",
"inputRole": "local-control",
"transform": "identity",
"purpose": "yes",
"contributors": [
"yes"
]
}
]
},
{
"name": "activate",
"schema": {
"type": "boolean"
},
"required": false,
"requiresConsent": true,
"input": {
"role": "handler-built",
"targets": [
{
"role": "body",
"path": "active",
"transform": "present"
}
],
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "body",
"path": "active",
"transform": "present",
"input": "activate",
"contributors": [
"activate"
]
}
]
},
{
"name": "deactivate",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "handler-built",
"targets": [
{
"role": "body",
"path": "active",
"transform": "absent"
}
],
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "body",
"path": "active",
"transform": "absent",
"input": "deactivate",
"contributors": [
"deactivate"
]
}
]
}
],
"files": [],
"wire": {
"method": "POST",
"route": "/byo-sms/integrations/:id/activate",
"query": {},
"body": {},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"userID": {
"type": "string"
},
"pipelineID": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"uid": {
"type": "string"
},
"providerKey": {
"enum": [
"generic",
"vodafone-elen"
]
},
"adapterVersion": {
"type": [
"string",
"null"
]
},
"providerConfig": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"senderName": {
"type": [
"string",
"null"
]
}
},
"required": [
"senderName"
],
"additionalProperties": true
},
"countries": {
"type": "array",
"items": {
"type": "string"
}
},
"steps": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"format": "uri"
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"query": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"bodyType": {
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"additionalProperties": true
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "integer"
}
},
"jsonPathEquals": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
},
"extract": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"required": [
"url"
],
"additionalProperties": true
}
},
"onFailure": {
"enum": [
"strict",
"fallbackToAkedly"
]
},
"feeOverride": {
"type": [
"number",
"null"
]
},
"active": {
"type": "boolean"
},
"liveAnnouncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"testPhone": {
"type": [
"string",
"null"
]
},
"testStatus": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"passed": {
"type": "boolean"
},
"lastTestedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastResultSummary": {
"type": [
"string",
"null"
]
},
"providerSmsId": {
"type": [
"string",
"null"
]
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"contractFingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"passed",
"lastTestedAt",
"lastResultSummary",
"providerSmsId",
"certainty",
"failureCategory",
"lastTestKey",
"contractFingerprint"
],
"additionalProperties": true
},
"configHash": {
"type": [
"string",
"null"
]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"stepsVisible": {
"type": "boolean"
},
"feeVisible": {
"type": "boolean"
},
"secretNames": {
"type": "array",
"items": {
"type": "string"
}
},
"vodafoneReadiness": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"providerKey": {
"const": "vodafone-elen"
},
"adapterVersion": {
"type": "string"
},
"supportedCountries": {
"type": "array",
"items": {
"const": "EG"
}
},
"credentials": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"required": {
"type": "array",
"items": {
"type": "string"
}
},
"configured": {
"type": "boolean"
},
"missing": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"required"
],
"additionalProperties": true
},
"sender": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"configured": {
"type": "boolean"
}
},
"required": [
"configured"
],
"additionalProperties": true
},
"pilotEnabled": {
"type": "boolean"
},
"pilotAccountAllowed": {
"type": [
"boolean",
"null"
]
},
"pilotAccountIdsConfigured": {
"type": "boolean"
},
"adapterAvailable": {
"type": "boolean"
},
"quotaDebitLedgerReady": {
"type": "boolean"
},
"contractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractMatches": {
"type": "boolean"
},
"deploymentBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"egress": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"ips": {
"type": "array",
"items": {
"type": "string",
"format": "ipv4"
}
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
},
"fingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"environment",
"environmentLabel",
"ips",
"status",
"verified",
"fingerprint"
],
"additionalProperties": true
},
"externalActivationRequired": {
"type": "boolean"
},
"testEligible": {
"type": "boolean"
},
"activationEligible": {
"type": "boolean"
},
"setupBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"testBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"activationBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"blocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"whitelistAcknowledged": {
"type": "boolean"
}
},
"required": [
"providerKey",
"adapterVersion",
"supportedCountries",
"pilotEnabled",
"pilotAccountAllowed",
"pilotAccountIdsConfigured",
"adapterAvailable",
"quotaDebitLedgerReady",
"contractFingerprint",
"deploymentBlocker",
"egress",
"externalActivationRequired",
"testEligible",
"activationEligible",
"setupBlocker",
"testBlocker",
"activationBlocker",
"blocker"
],
"additionalProperties": true
},
"routingEnabled": {
"type": "boolean"
},
"routingHint": {
"type": "string"
},
"resolvedFeeEGP": {
"type": "number",
"minimum": 0
}
},
"required": [
"_id",
"userID",
"name",
"uid",
"providerKey",
"countries",
"onFailure",
"active",
"testStatus",
"stepsVisible",
"feeVisible",
"secretNames"
],
"additionalProperties": true
},
"routingEnabled": {
"type": "boolean"
},
"routingHint": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"status",
"data",
"routingEnabled"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "generic",
"adapterVersion": null,
"providerConfig": {
"senderName": null
},
"countries": [
"EG"
],
"steps": [
{
"method": "POST",
"url": "https://sms.example.invalid/send",
"headers": {},
"query": {},
"bodyType": "json",
"bodyTemplate": "{\"to\":\"{{phone}}\",\"text\":\"{{otpMessage}}\",\"key\":\"{{secrets.apiKey}}\"}",
"auth": {
"type": "none",
"token": null,
"username": null,
"password": "[REDACTED — synthetic secret omitted]",
"headerName": null,
"headerValue": null
},
"timeoutMs": 3000,
"retryCount": 0,
"isSendStep": true,
"successAssertion": {
"statusIn": [
200,
201,
202
],
"jsonPathEquals": null,
"bodyMatches": null
},
"extract": []
}
],
"onFailure": "fallbackToAkedly",
"feeOverride": null,
"active": true,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": true,
"lastTestedAt": "2026-01-01T00:00:00.000Z",
"lastResultSummary": "passed",
"providerSmsId": "synthetic-provider-id",
"certainty": "accepted",
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"apiKey"
]
},
"routingEnabled": false,
"routingHint": "Integration is active, but BYO-SMS routing is off for this account/pipeline."
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
byo-sms vodafone acknowledge
Record customer acknowledgement that current Vodafone outbound addresses were allow-listed.
When to use
- After provider-side IP allowlisting.
How it works
- Stores matching current egress fingerprint; stale fingerprint refused. This is not a send gate or activation.
Prerequisites
- Vodafone integration, verified current fingerprint, completed allowlisting,
--yes.
Expected outcome
whitelistAcknowledged true; timestamp/fingerprint hidden.
Next steps
- Refresh readiness then separately test/activate when eligible.
Usage
akedly byo-sms vodafone acknowledge <id>
Inspect this command offline before preparing an operation:
akedly describe "byo-sms vodafone acknowledge" --output json
| Positional | Grammar | Usage guidance |
|---|---|---|
| <id> | ^[a-fA-F0-9]{24}$ | Use the id from akedly byo-sms list. |
Flags
| Flag | Type | Enum / range | Required | Description |
|---|---|---|---|---|
--ksa | boolean switch | — | no | Select the KSA deployment. |
--dry-run | boolean switch | — | no | Preview without sending the write or creating output files; planning reads may still occur. Bypass consent. |
--yes | boolean switch | — | no | Confirm this action where consent is required. |
--fingerprint | text | — | yes | Command-specific option; see the full command details below. Manifest body field: egressFingerprint. |
--help | boolean switch | — | no | Show offline command help. |
--output | enum | json | human | no | Select JSON or human output. |
--fields | dot-paths | — | no | Select comma-separated safe dot paths; apply to each element of a root array. |
Authentication, permission and guards
Auth: login. Permission: byo_sms:update.
Server request: POST /byo-sms/integrations/:id/whitelist-acknowledgment.
Consent
--yes. --dry-run bypasses confirmation where supported. Destructive: false. Spends money: false. Real send: false.
Retry
write: Non-idempotent writes are not automatically retried. guarded-write: when an update carries the revision read from the server, its write is not automatically retried after network errors or HTTP 429/502/503/504. It may already be saved; inspect the resource before rerunning. A rerun reads current settings again. The explicit CLI_RATE_LIMITED refusal exception below still applies. recommendation-read: the recommendation request made while planning --recommended-limits is not automatically retried after network errors or HTTP 429/502/503/504. If planning fails, no update is sent; rerun the command to request a new recommendation. The explicit CLI_RATE_LIMITED refusal exception below still applies. This label does not change the retry policy of the standalone rate-recommendations command.
Output
Output: data. API output is pass-through server data unless the command details describe a CLI shape. Local commands, composites and hand-offs use their documented CLI shapes.
Full command details
Record that you have whitelisted the displayed Vodafone sending addresses; requires --fingerprint and --yes. Read data.egress.fingerprint from akedly byo-sms vodafone readiness or data.vodafoneReadiness.egress.fingerprint from akedly byo-sms get <id>. EGRESS_FINGERPRINT_STALE means refresh and check the address list before acknowledging again. This does not send, enable sending or act as a send gate. The saved state appears as vodafoneReadiness.whitelistAcknowledged; the stored acknowledgement timestamp and fingerprint are hidden. Only Vodafone integrations support it. Requires BYO-SMS entitlement and granular pricing; HTTP 403 (shown as PERMISSION_DENIED) can mean BYO-SMS is not enabled, not only a missing role permission; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and a role refusal names details.permission. Read-only logins are refused.
Worked examples
Examples are synthetic illustrations, never live account records. Replace resource IDs with your own authorized resources. Secret values are omitted here; redacted examples illustrate the shape and are not schema-validation fixtures.
Synthetic request and response
After provider-side IP allowlisting.
akedly byo-sms vodafone acknowledge 64b000000000000000000001 --fingerprint aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --yes
Illustrative response (sensitive values omitted):
{
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "vodafone-elen",
"adapterVersion": "1",
"providerConfig": {
"senderName": "SYNTHETIC"
},
"countries": [
"EG"
],
"steps": [],
"onFailure": "strict",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"accountId",
"password",
"secureHashSecretKey"
],
"vodafoneReadiness": {
"providerKey": "vodafone-elen",
"adapterVersion": "1",
"supportedCountries": [
"EG"
],
"credentials": "[REDACTED — synthetic secret omitted]",
"sender": {
"configured": true
},
"pilotEnabled": false,
"pilotAccountAllowed": false,
"pilotAccountIdsConfigured": false,
"adapterAvailable": false,
"quotaDebitLedgerReady": false,
"contractFingerprint": null,
"testContractFingerprint": null,
"testContractMatches": false,
"deploymentBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"egress": {
"environment": "example",
"environmentLabel": "EXAMPLE",
"ips": [
"8.8.8.8"
],
"status": "ready",
"verified": true,
"fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"externalActivationRequired": true,
"testEligible": false,
"activationEligible": false,
"setupBlocker": null,
"testBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"activationBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"blocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"whitelistAcknowledged": true
}
}
}
Example is documentation only and has not been run. Resource ids do not assert existing state.
Keep BYO-SMS output private: visible endpoint/template text may contain literal credentials.
Additional command forms
akedly byo-sms vodafone acknowledge <id> --fingerprint <fingerprint> --yes
Input and file contracts
The contract below describes typed arguments, file formats, validation boundaries and API mappings. Boolean switches take no value; numeric and comma-separated values are each passed as one argument. Read prerequisites and full command details for state-dependent requirements.
{
"flagTokenEncoding": {
"booleans": "A boolean schema describes switch presence: write --flag with no value.",
"valuedFlags": "A string schema describes the single argv token after --flag or after --flag=; numeric token schemas parse as decimal strings and validation.parsedValueSchema documents the converted number.",
"csvFlags": "A list schema describes one comma-separated argv token, not multiple shell arguments.",
"projection": "--fields is one comma-separated argv token of safe dot paths and changes returned output only."
},
"positionals": [
{
"name": "id",
"type": "string",
"required": true,
"pattern": "^[a-fA-F0-9]{24}$",
"validation": "Use the id from akedly byo-sms list.",
"mapsToRouteParameter": "id",
"routeParameterValidation": "A route parameter cannot be . or .., including percent-encoded forms."
}
],
"flags": [
{
"name": "help",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "output",
"schema": {
"type": "string",
"enum": [
"json",
"human"
]
},
"required": false
},
{
"name": "fields",
"schema": {
"type": "string",
"pattern": "^(?!.*(?:^|[.,])(?:__proto__|constructor|prototype)(?:[.,]|$))(?!,)(?!.*,,)(?!.*,$)[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*(?:,[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*)*$"
},
"required": false,
"validation": {
"syntax": "Comma-separated safe dot paths; reserved prototype keys are refused."
}
},
{
"name": "ksa",
"schema": {
"type": "boolean"
},
"required": false
},
{
"name": "dry-run",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "dry-run",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "dry-run",
"inputRole": "local-control",
"transform": "identity",
"purpose": "dry-run",
"contributors": [
"dry-run"
]
}
]
},
{
"name": "yes",
"schema": {
"type": "boolean"
},
"required": false,
"input": {
"role": "local-control",
"purpose": "yes",
"validationBoundary": "No separately curated semantic file schema is available; use the local reader contract and command help to understand additional handler or server validation."
},
"wire": [
{
"role": "local",
"input": "yes",
"inputRole": "local-control",
"transform": "identity",
"purpose": "yes",
"contributors": [
"yes"
]
}
]
},
{
"name": "fingerprint",
"schema": {
"type": "string"
},
"required": true,
"requiresConsent": true,
"wire": [
{
"path": "egressFingerprint",
"transform": "identity",
"role": "body",
"input": "fingerprint",
"contributors": [
"fingerprint"
]
}
]
}
],
"files": [],
"wire": {
"method": "POST",
"route": "/byo-sms/integrations/:id/whitelist-acknowledgment",
"query": {},
"body": {
"fingerprint": [
{
"path": "egressFingerprint",
"transform": "identity",
"role": "body",
"input": "fingerprint",
"contributors": [
"fingerprint"
]
}
]
},
"ownerParams": []
}
}
Response contract
Schema describes the stable CLI-visible success envelope and known stable fields. API-owned fields may be added; fields omitted by member visibility or absent resource state may be absent.
Examples are synthetic; resource state, readiness, provider outcome, and fees vary by account.
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": {
"const": "success"
},
"data": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"userID": {
"type": "string"
},
"pipelineID": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"uid": {
"type": "string"
},
"providerKey": {
"enum": [
"generic",
"vodafone-elen"
]
},
"adapterVersion": {
"type": [
"string",
"null"
]
},
"providerConfig": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"senderName": {
"type": [
"string",
"null"
]
}
},
"required": [
"senderName"
],
"additionalProperties": true
},
"countries": {
"type": "array",
"items": {
"type": "string"
}
},
"steps": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
"url": {
"type": "string",
"format": "uri"
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"query": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"bodyType": {
"enum": [
"json",
"form",
"raw",
"none"
]
},
"bodyTemplate": {
"type": "string"
},
"auth": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"none",
"bearer",
"basic",
"apiKeyHeader"
]
},
"token": {
"type": [
"string",
"null"
]
},
"username": {
"type": [
"string",
"null"
]
},
"password": {
"type": [
"string",
"null"
]
},
"headerName": {
"type": [
"string",
"null"
]
},
"headerValue": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"additionalProperties": true
},
"timeoutMs": {
"type": "number"
},
"retryCount": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"isSendStep": {
"type": "boolean"
},
"successAssertion": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"statusIn": {
"type": "array",
"items": {
"type": "integer"
}
},
"jsonPathEquals": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"bodyMatches": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
},
"extract": {
"type": "array",
"items": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"from": {
"enum": [
"status",
"header",
"jsonPath",
"regex"
]
},
"expr": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"required": [
"url"
],
"additionalProperties": true
}
},
"onFailure": {
"enum": [
"strict",
"fallbackToAkedly"
]
},
"feeOverride": {
"type": [
"number",
"null"
]
},
"active": {
"type": "boolean"
},
"liveAnnouncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"testPhone": {
"type": [
"string",
"null"
]
},
"testStatus": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"passed": {
"type": "boolean"
},
"lastTestedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastResultSummary": {
"type": [
"string",
"null"
]
},
"providerSmsId": {
"type": [
"string",
"null"
]
},
"certainty": {
"enum": [
"accepted",
"not_sent",
"unknown",
null
]
},
"failureCategory": {
"type": [
"string",
"null"
]
},
"lastTestKey": {
"type": [
"string",
"null"
]
},
"contractFingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"passed",
"lastTestedAt",
"lastResultSummary",
"providerSmsId",
"certainty",
"failureCategory",
"lastTestKey",
"contractFingerprint"
],
"additionalProperties": true
},
"configHash": {
"type": [
"string",
"null"
]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"stepsVisible": {
"type": "boolean"
},
"feeVisible": {
"type": "boolean"
},
"secretNames": {
"type": "array",
"items": {
"type": "string"
}
},
"vodafoneReadiness": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"providerKey": {
"const": "vodafone-elen"
},
"adapterVersion": {
"type": "string"
},
"supportedCountries": {
"type": "array",
"items": {
"const": "EG"
}
},
"credentials": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"required": {
"type": "array",
"items": {
"type": "string"
}
},
"configured": {
"type": "boolean"
},
"missing": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"required"
],
"additionalProperties": true
},
"sender": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"configured": {
"type": "boolean"
}
},
"required": [
"configured"
],
"additionalProperties": true
},
"pilotEnabled": {
"type": "boolean"
},
"pilotAccountAllowed": {
"type": [
"boolean",
"null"
]
},
"pilotAccountIdsConfigured": {
"type": "boolean"
},
"adapterAvailable": {
"type": "boolean"
},
"quotaDebitLedgerReady": {
"type": "boolean"
},
"contractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractFingerprint": {
"type": [
"string",
"null"
]
},
"testContractMatches": {
"type": "boolean"
},
"deploymentBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"egress": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"environmentLabel": {
"type": "string"
},
"ips": {
"type": "array",
"items": {
"type": "string",
"format": "ipv4"
}
},
"status": {
"enum": [
"ready",
"missing",
"invalid"
]
},
"verified": {
"type": "boolean"
},
"fingerprint": {
"type": [
"string",
"null"
]
}
},
"required": [
"environment",
"environmentLabel",
"ips",
"status",
"verified",
"fingerprint"
],
"additionalProperties": true
},
"externalActivationRequired": {
"type": "boolean"
},
"testEligible": {
"type": "boolean"
},
"activationEligible": {
"type": "boolean"
},
"setupBlocker": {
"oneOf": [
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
{
"type": "null"
}
]
},
"testBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"activationBlocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"blocker": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
},
"whitelistAcknowledged": {
"type": "boolean"
}
},
"required": [
"providerKey",
"adapterVersion",
"supportedCountries",
"pilotEnabled",
"pilotAccountAllowed",
"pilotAccountIdsConfigured",
"adapterAvailable",
"quotaDebitLedgerReady",
"contractFingerprint",
"deploymentBlocker",
"egress",
"externalActivationRequired",
"testEligible",
"activationEligible",
"setupBlocker",
"testBlocker",
"activationBlocker",
"blocker"
],
"additionalProperties": true
},
"routingEnabled": {
"type": "boolean"
},
"routingHint": {
"type": "string"
},
"resolvedFeeEGP": {
"type": "number",
"minimum": 0
}
},
"required": [
"_id",
"userID",
"name",
"uid",
"providerKey",
"countries",
"onFailure",
"active",
"testStatus",
"stepsVisible",
"feeVisible",
"secretNames"
],
"additionalProperties": true
},
"message": {
"type": "string"
}
},
"required": [
"status"
],
"additionalProperties": true
},
"variants": [],
"fieldSelection": {
"flag": "fields",
"behavior": "Selects comma-separated dot paths from the response root; for root arrays, paths apply to each element. Missing paths are omitted.",
"sensitiveData": null
}
}
[
{
"title": "Representative synthetic response",
"value": {
"status": "success",
"data": {
"_id": "64b000000000000000000001",
"userID": "64b000000000000000000010",
"pipelineID": null,
"name": "Synthetic route",
"description": null,
"uid": "11111111-1111-4111-8111-111111111111",
"providerKey": "vodafone-elen",
"adapterVersion": "1",
"providerConfig": {
"senderName": "SYNTHETIC"
},
"countries": [
"EG"
],
"steps": [],
"onFailure": "strict",
"feeOverride": null,
"active": false,
"liveAnnouncedAt": null,
"testPhone": null,
"testStatus": {
"passed": false,
"lastTestedAt": null,
"lastResultSummary": null,
"providerSmsId": null,
"certainty": null,
"failureCategory": null,
"lastTestKey": null,
"contractFingerprint": null
},
"configHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z",
"stepsVisible": true,
"feeVisible": true,
"secretNames": [
"accountId",
"password",
"secureHashSecretKey"
],
"vodafoneReadiness": {
"providerKey": "vodafone-elen",
"adapterVersion": "1",
"supportedCountries": [
"EG"
],
"credentials": "[REDACTED — synthetic secret omitted]",
"sender": {
"configured": true
},
"pilotEnabled": false,
"pilotAccountAllowed": false,
"pilotAccountIdsConfigured": false,
"adapterAvailable": false,
"quotaDebitLedgerReady": false,
"contractFingerprint": null,
"testContractFingerprint": null,
"testContractMatches": false,
"deploymentBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"egress": {
"environment": "example",
"environmentLabel": "EXAMPLE",
"ips": [
"8.8.8.8"
],
"status": "ready",
"verified": true,
"fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"externalActivationRequired": true,
"testEligible": false,
"activationEligible": false,
"setupBlocker": null,
"testBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"activationBlocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"blocker": {
"code": "VODAFONE_PILOT_DISABLED",
"message": "The Vodafone BYO-SMS pilot is not enabled yet."
},
"whitelistAcknowledged": true
}
}
}
}
]
{
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"status",
"code",
"message",
"httpStatus",
"hint"
],
"properties": {
"status": {
"const": "error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"httpStatus": {
"type": [
"integer",
"null"
]
},
"details": {
"anyOf": [
{
"type": "object",
"minProperties": 1
},
{
"type": "array",
"minItems": 1
}
]
},
"hint": {
"type": "string"
}
},
"additionalProperties": false
},
"globalHints": {
"TOTP_DELETE_RECONCILIATION_PENDING": "Any Pending request blocks deletion, including one just opened; Failed requests can block too. Ordinary unverified, unbilled requests expire about five minutes after creation and normally become Expired on the next five-minute reconciliation run, within about ten minutes of creation; job delays or billing/counter recovery can extend that wait. Check the stored status in akedly totp history list; computed isExpired alone does not unblock deletion. Ask Akedly support if a request stays Pending or Failed beyond that normal window, or is Verified and still blocks.",
"CLI_SECRET_ALREADY_DELIVERED": "Run akedly login again.",
"CLI_LOGIN_INVALID": "Run akedly login.",
"PERMISSION_DENIED": "If details.permission names a permission, ask an organization admin to grant it to your role. Otherwise read the server message: an organization admin cannot clear refusals such as an inactive or unverified account, an Akedly-managed global role, a reported-invitation pause or missing enterprise entitlement; follow the message or contact Akedly support. If the message says BYO-SMS is not enabled for this account, the account needs BYO-SMS entitlement and granular pricing; akedly whoami shows user.byoSmsEntitled and user.pricingModel, and only Akedly can change either.",
"ORG_ADMIN_REQUIRED": "If the message asks for an active organization session, run akedly login and approve it with the intended organization active in the dashboard. If it says organization admin access is required, ask an organization admin for access or to run the command. Role changes can take up to 60 seconds to apply.",
"OWNER_REQUIRED": "Only the account owner can do this; an organization admin is not necessarily the owner. Ask the owner to run it from their own login. For seats, organization admins can read akedly seats visibility; akedly seats status is owner-only.",
"LOGIN_REQUIRED": "Run akedly login. If you supplied a credential, check --ksa or AKEDLY_REGION.",
"CLI_DISABLED": "Customer CLI access is turned off on this Akedly server. Try again later or contact Akedly support.",
"CLI_IDENTITY_UNAVAILABLE": "Akedly could not confirm your login right now. Wait a minute and try again.",
"CLI_LOGIN_INDEX_UNAVAILABLE": "CLI login is temporarily unavailable on this server. Try again later or contact Akedly support.",
"WABA_PHONE_CHECK_UNAVAILABLE": "Meta did not confirm the number. Wait a few minutes, then run the command again.",
"CLI_UPGRADE_REQUIRED": "Install the latest @akedly/cli.",
"NOT_AVAILABLE_ON_SERVER": "Check the selected region or ask an operator to deploy CLI support.",
"CLI_RATE_LIMITED": "Wait before trying again.",
"CLI_LOGIN_DENIED": "Run akedly login again and approve the new code.",
"CLI_LOGIN_REQUEST_EXPIRED": "Run akedly login again.",
"LOGIN_TIMEOUT": "Run akedly login again and approve before the timeout.",
"CLI_READ_ONLY": "This login is read-only. Run akedly login and choose Full access.",
"CLI_LOGIN_LIMIT_REACHED": "Run akedly cli-logins list, then akedly cli-logins end <id>",
"CLI_LOGIN_NOT_FOUND": "Run akedly cli-logins list and check the login id.",
"CLI_LOGIN_OWNER_PROTECTED": "Ask the account owner to end this login.",
"IMPERSONATION_BLOCKED": "This login was approved by Akedly staff and can't do this. Run akedly login and approve it in your own dashboard session.",
"SELF_IDENTITY_REQUIRED": "Personal settings need your own login. Run akedly login and approve it in your own dashboard session.",
"MEMBER_NOT_FOUND": "Check the email with akedly team members list; a pending invitation is not a member yet.",
"MEMBER_AMBIGUOUS": "Pass the memberId from akedly team members list instead of the email.",
"API_KEY_ACTIVE_CAP_REACHED": "Revoke an unused key before creating another.",
"API_KEY_ISSUANCE_DISABLED": "Ask an operator to enable API-key issuance.",
"API_KEY_VERSION_CONFLICT": "List the keys and retry the edit with the current version.",
"API_KEY_BUDGET_CONFIRMATION_REQUIRED": "Review the reduction and pass --confirm-budget-reduction.",
"API_KEY_ISSUE_RECOVERY_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_TRANSITION_UNAVAILABLE": "Revoke the key and create a new one.",
"API_KEY_ISSUE_IDEMPOTENCY_CONFLICT": "Use a new idempotency key for different issue details.",
"API_KEY_SCOPE_NOT_ACTIVE": "Ask an operator to enable API-key scope editing.",
"API_KEY_BUDGET_NOT_ACTIVE": "Ask an operator to enable API-key budget editing.",
"API_KEY_NOT_FOUND": "Run akedly api-keys list and check the id.",
"API_KEY_BUDGET_RACE": "List the keys and retry.",
"V1_CREATION_NOT_ENTITLED": "V1 pipelines are not available to this account. Omit --api-version to create a V1.2 pipeline.",
"SECRET_SUFFIX_REQUIRED": "Pass exactly one of --secret-file <saved path> or --secret-suffix <last 4 characters>.",
"SECRET_SUFFIX_MISMATCH": "Reveal the current secret to --secret-file <new path> with akedly pipelines webhook-secret reveal <id> for webhook secrets, or akedly utilities pipelines signing-secret reveal <id> for utility request-signing secrets, then acknowledge with exactly one of --secret-file <that path> or --secret-suffix <last 4 characters>.",
"CHANNEL_CHOICE_NOT_AVAILABLE": "Nothing in this request was saved. Each value in details.blockers names what is missing: not_entitled means Channel Choice isn't enabled on the account yet; legacy_pricing means the account needs granular pricing; not_pay_per_message means the pipeline is not billed per message, because it is billed per successful verification or the account has no billing plan yet; account_excluded and global_switch_off mean Channel Choice isn't offered to this account right now. Only Akedly can change these, so contact Akedly. utility_pipeline means utility pipelines have no Channel Choice. Resend any other changes without the Channel Choice flags.",
"VERIFICATION_METHOD_UNAVAILABLE": "Nothing was saved. details.method is the word you typed, or the method's id if the method became unavailable after the CLI checked; running the command again rechecks the list and names the word. Remove that method from --verification-methods and retry, or contact Akedly if you expected it to be available.",
"VERIFICATION_METHODS_REQUIRED": "A pipeline that has verification methods must keep at least one. Send the methods to keep instead of an empty list, for example akedly pipelines update <id> --verification-methods whatsapp,sms --yes.",
"SETTING_NOT_FOR_THIS_PIPELINE": "Nothing in this request was saved. details.setting names the setting and details.useInstead what to use instead. For rate limits and the circuit breaker, reason api-version means this is a V1 or V2 pipeline, where they belong to a widget instead, so useInstead is widgets update. A widget must be attached to this pipeline first. Find attached widgets with akedly widgets list-filtered --pipeline-id <pipelineId>, then inspect the intended widget with akedly widgets get <widgetId>, using its 24-hex _id. This listing excludes inactive widgets by default. Also run akedly widgets list-filtered --pipeline-id <pipelineId> --status inactive before concluding that no widget is attached. Change its limits with akedly widgets update <widgetId> --rate-limits-file <file> --yes; use the widget file shape, not the pipeline file shape. Set its breaker with akedly widgets update <widgetId> --circuit-breaker on --yes (or off), and use --circuit-breaker-file <file> for widget thresholds and suspension durations; pipeline --circuit-breaker-sensitivity does not apply to widgets. If no widget is attached, create one with akedly widgets create --name <name> --pipeline-id <pipelineId> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <pipelineId> --yes. V2 attachment requires both callback URLs; V1 attachment requires entitlement. pipelines update now routes shared rate-limit, breaker and PoW flags to every attached widget on V1/V2. V1.2-only controls remain refused on V1/V2. Use the appropriate file shape for the resulting API version. For the Channel Choice widget experience, reason api-version means it needs a V2 pipeline, so create one with akedly pipelines create --name <name> --api-version v2 --redirect-url <url> --webhook-url <url> --secret-file <new path>, then attach a widget to it; reason no-widget means it needs a widget attached to this V2 pipeline, so create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, or attach an existing one with akedly widgets update <widgetId> --pipeline-id <id> --yes, then retry. Resend any other changes without that flag.",
"PIPELINE_CHANGED": "Another change landed first, so nothing was saved. Run the same command again; if you changed rate limits or destination countries, it reads the pipeline again first and merges your change onto the new version. Run akedly pipelines get <id> to see what changed.",
"RATE_LIMIT_INVALID": "Check details.field and any details.errors. Use whole numbers above zero in the expected JSON object shape. Pipeline --rate-limits-file uses perPhoneNumber, perIpAddress and perPipeline; widget --rate-limits-file uses widgetAttempts and otpRequests, each with perPhoneNumber, perDevice and perWidget. Each takes perMinute, perHour and perDay. Widget --circuit-breaker-file uses floodThresholds and suspensionSeconds; durations are whole seconds. Correct the named field and rerun the command.",
"EXPECTED_UPDATED_AT_INVALID": "The CLI reads the revision automatically. Run the command again to read the current settings; if this continues, check that the CLI and server are up to date and contact Akedly support. Do not invent a revision or remove the conflict check.",
"DESTINATION_COUNTRIES_MODE_REQUIRED": "Nothing in this request was saved. A changed country list under mode all would be discarded: pass --destination-countries-mode only-selected or all-except-selected with --destination-countries, or leave the list out to keep every country open. --destination-countries-mode all on its own clears the list.",
"NOT_ENOUGH_TRAFFIC": "Keep the current limits for now. Recommended limits need at least 100 analysed requests and better than low confidence; try again after more real traffic or with a longer --period such as last-quarter, or set limits yourself with --rate-limits-file.",
"INVALID_COUNTRY_POLICY": "Nothing in this request was saved. Use two-letter country codes such as EG,SA with --destination-countries and a mode of all, only-selected or all-except-selected; the message names the problem.",
"OTP_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"TURNSTILE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_PIPELINE_CHANGED": "Another change landed first. Run akedly pipelines get <id> and retry.",
"CLUSTER_GATE_NOT_AVAILABLE": "Nothing in this request was saved. Act on details.reason: not-offered means Cluster Gate is not offered for this pipeline right now, for example on a utility pipeline or in the KSA region; attaching a widget will not help on a V1.2 pipeline, because V1.2 pipelines cannot be attached to widgets; drop the Cluster Gate flags and resend any other changes; no-widget means Cluster Gate applies here only through a widget and none is attached, so attach an existing widget with akedly widgets update <widgetId> --pipeline-id <id> --yes, or create one with akedly widgets create --name <name> --pipeline-id <id> --secret-file <new path>, then retry; the pipeline must first be active, with both callback URLs and a verification method, and WIDGET_PIPELINE_NOT_READY says what is missing; on-create comes only from raw API requests that set Cluster Gate while creating a pipeline: create the pipeline first, then set Cluster Gate with akedly pipelines update <id> --cluster-gate on --cluster-gate-position balanced --yes; check-failed means availability could not be checked, so retry later; intent-missing comes only from raw API requests that omit clusterGateIntent: true.",
"SECRET_ALREADY_ACKNOWLEDGED": "The secret was already acknowledged and cannot be shown again. Use the saved copy from pipeline creation (webhook secrets) or from reveal, for example the file written with --secret-file. Only if that copy is lost, use akedly pipelines webhook-secret regenerate <id> --yes --secret-file <new path> for webhook secrets, or akedly utilities pipelines signing-secret regenerate <id> --yes --secret-file <new path> for utility request-signing secrets. Regenerating stops the current secret immediately; deploy the new secret before using signed requests or accepting signed webhooks.",
"WIDGET_PIPELINE_NOT_READY": "Fix each item in details.missing with akedly pipelines update <pipelineId> --status active --redirect-url <url> --webhook-url <url> --verification-methods whatsapp,sms,email --yes, passing only the flags for the missing items, then retry.",
"WIDGET_IDENTITY_INDEX_UNAVAILABLE": "Widget identity indexes are unavailable. Retry later or contact Akedly support.",
"INCOMPLETE_DATE_RANGE": "Supply both --start-date and --end-date, or neither.",
"DATE_RANGE_TOO_LARGE": "Choose a date range of at most 30 days, counted as the command help describes.",
"INVALID_DATE_RANGE": "Check both date bounds and their order.",
"INVALID_DATE": "Use valid ISO dates for the requested range.",
"NEEDS_BROWSER": "Turn Turnstile off for the pipeline in the dashboard, or run akedly pipelines dev-mode set <pipelineId> --dev-mode on --bypass-turnstile on --bypass-pow on --yes.",
"SETUP_REQUIRED": "Finish phone setup on the Personal Notifications page of your Akedly dashboard, then retry.",
"INVALID_API_KEY": "Check the API key: akedly api-keys list, or create one with akedly api-keys create --label <name> --save --yes.",
"API_KEY_SCOPE_DENIED": "This API key is scoped to other pipelines; use a key that includes this pipeline.",
"MAX_ATTEMPTS_EXCEEDED": "Too many wrong codes; send a new OTP.",
"ALREADY_VERIFIED": "This request is already verified; check it with akedly transactions status.",
"INVALID_OTP": "Check the code and retry; attempts are limited.",
"PIPELINE_INACTIVE": "Run akedly pipelines update <pipelineId> --status active --yes, then retry.",
"POW_CHALLENGE_EXPIRED": "Run the command again; it fetches a fresh challenge.",
"POW_CHALLENGE_REPLAYED": "Run the command again; it fetches a fresh challenge.",
"POW_DIFFICULTY_STALE": "Run the command again; it fetches a fresh challenge.",
"STATUS_LOOKUP_RATE_LIMITED": "Wait for the retryAfter seconds in details, then retry; each lookup counts against the 300 ids per minute limit.",
"TRANSACTION_EXPIRED": "This code or request has expired; send a new one.",
"USE_V1_2_ENDPOINT": "This pipeline is V1.2. Use akedly otp v1.2 send.",
"SIGNATURE_EXPIRED": "This machine's clock differs from the server's by more than 60 seconds. Fix the clock and run the command again; it signs a fresh request each time.",
"WIDGET_USER_MISMATCH": "This API key belongs to a different account than the widget. Use a key from the widget owner's account.",
"WEBHOOK_SIGNATURE_INVALID": "Use the pipeline's current whsec_ secret (regenerating invalidates the old one), the exact raw body bytes the server received (no re-serialising, no trailing newline), and the svix-id, svix-timestamp and svix-signature headers.",
"WAIT_EXPIRED": "The request expired before it was verified; it cannot complete. Start a new one (otp v2 attempt, otp v1.2 send or otp v1 create).",
"WAIT_FAILED": "The verification failed and cannot complete; start a new one.",
"WAIT_TIMEOUT": "The wait ended; the verification may still complete. Check it with akedly transactions status. Resume with akedly transactions status --attempt-id <id> --wait (or --transaction-id <id> --wait).",
"WEBHOOK_TIMESTAMP_STALE": "The webhook timestamp is outside the allowed window. Check this machine's clock, or pass --at <unix seconds> to check an older delivery, or --tolerance-seconds <seconds>.",
"WEBHOOK_HEADERS_MISSING": "Pass a headers JSON file with the svix-id, svix-timestamp and svix-signature headers exactly as received.",
"NO_USABLE_API_KEY": "This account has no usable API key for this pipeline. Create one with akedly api-keys create --label <name> --save --yes; a key scoped to selected pipelines must include this pipeline.",
"INVALID_API_VERSION": "This command does not support this pipeline's API version. Check it with akedly pipelines get <id> and use the matching command for that version.",
"DOCTOR_FAILED": "Fix the failed checks in details.checks, then run akedly doctor again.",
"PAGINATION_LIMIT": "Narrow the filters, for example a date range or status, or raise --limit (maximum 100), then run again.",
"PAGINATION_STALLED": "The list changed while it was being read, or the server stopped advancing. Run the command again.",
"REPORT_NOT_FOUND": "Run akedly reports list and check the reportId.",
"DAILY_LIMIT_REACHED": "Wait until an earlier successful, sent or in-flight report generation leaves the rolling 24-hour window; for example, a generation at 14:00 stops counting after 14:00 the next day. This limit covers the whole account, including dashboard and teammate generations.",
"REPORT_IN_PROGRESS": "A report is already generating for this account; dashboard and teammate generations share the same slot. Run akedly reports list before trying again.",
"TEMPLATE_DUPLICATE": "Run akedly whatsapp otp-templates list; recover missing languages with update --add-languages instead of adding the same template again.",
"TEMPLATE_LIMIT_REACHED": "Archive an unused template with akedly whatsapp otp-templates delete <id> --yes before adding another; the limit is 20 per WABA.",
"TEMPLATE_PROVISIONING_FAILED": "The template or changes are saved. Run akedly whatsapp otp-templates list, then update <id> --add-languages <failed languages> rather than creating again.",
"TEMPLATE_WABA_REQUIRED": "Connect an active own WhatsApp Business Account in the dashboard before managing OTP templates; check akedly whatsapp status.",
"TEMPLATE_WABA_CHANGED": "This template belongs to the previous WABA. Add it again for the currently connected account.",
"COUNTRY_CATALOG_UNAVAILABLE": "The country catalog is temporarily unavailable. Try again later.",
"NOT_A_UTILITY_PIPELINE": "Choose an owned utility pipeline from akedly pipelines list, or create one with akedly pipelines create --product utility --name <name>.",
"TEST_RATE_LIMITED": "Wait for Retry-After before testing again; account, recipient or replay safety limits may apply. Reuse the same Vodafone idempotencyKey when checking an existing attempt.",
"EGRESS_FINGERPRINT_STALE": "The sending address changed. Run akedly byo-sms vodafone readiness, check the new address list, then acknowledge again with the new fingerprint.",
"VODAFONE_EGRESS_NOT_READY": "Run akedly byo-sms vodafone readiness; ask Akedly support to verify the outbound addresses before acknowledging.",
"UNROUTABLE_COUNTRY": "Run akedly byo-sms countries and choose routable country codes; use ALL alone for catch-all coverage.",
"SOURCE_COVERAGE_UNROUTABLE": "Update the source integration countries using akedly byo-sms countries, then clone again.",
"INVALID_TEST_PHONE": "Supply --test-phone-number in international format or update the saved test destination.",
"TEST_PHONE_OUT_OF_COVERAGE": "Choose a test number in the integration countries, or update its countries before testing.",
"VODAFONE_TEST_GUARD_UNAVAILABLE": "No new test was sent. Retry with the same idempotencyKey; contact Akedly support if send protection remains unavailable.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_REQUIRED": "Supply --idempotency-key with a unique request key and reuse it after an uncertain response.",
"VODAFONE_TEST_IDEMPOTENCY_KEY_INVALID": "Supply --idempotency-key with 1 to 255 characters.",
"VODAFONE_ACCEPTED_RETEST_INVALID": "Supply --previous-test-key with the accepted test key before requesting another test.",
"VODAFONE_ACCEPTED_RETEST_REQUIRED": "Check akedly byo-sms get <id>; to send again after an accepted result, supply --previous-test-key and a new --idempotency-key with --yes.",
"VODAFONE_NEW_TEST_CONFIRMATION_REQUIRED": "Check the previous result with akedly byo-sms get <id>; --allow-new-after-unknown with a new --idempotency-key and --yes can send another message.",
"VODAFONE_NEW_TEST_IN_PROGRESS": "Wait for the current Vodafone test result; inspect akedly byo-sms get <id> before starting another attempt.",
"BYO_SMS_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"VODAFONE_CONFIGURATION_CHANGED": "Run akedly byo-sms get <id>, test the current configuration, then try activation again.",
"BYO_SMS_STEPS_INVALID": "Check the steps JSON and secret names; secret and extraction names use letters, numbers and underscore."
},
"commandHints": {},
"hintPrecedence": [
"explicit handler hint",
"command-specific hint",
"global code hint",
"HTTP 429 fallback",
"generic command-help fallback"
],
"exitCodes": {
"USAGE": 64,
"CONFIRMATION_REQUIRED": 65,
"LOGIN_REQUIRED": 66,
"PERMISSION_DENIED": 67,
"ORG_ADMIN_REQUIRED": 67,
"OWNER_REQUIRED": 67,
"CLI_READ_ONLY": 67,
"IMPERSONATION_BLOCKED": 67,
"SELF_IDENTITY_REQUIRED": 67,
"CLI_LOGIN_OWNER_PROTECTED": 67,
"NEEDS_BROWSER": 68,
"SETUP_REQUIRED": 68,
"CLI_RATE_LIMITED": 69,
"NOT_AVAILABLE_ON_SERVER": 70,
"CLI_UPGRADE_REQUIRED": 70,
"CLI_DISABLED": 70
}
}
