Alwafi user and Expressly client manual
Audience: Expressly developers and developers of any application that consumes Alwafi AI services.
Contract version: 1.5, verified 2026-07-14.
Claude Code instruction
Give Claude Code this entire file together with GET /openapi.json. Tell it:
Implement Alwafi as the only AI transport. Treat the OpenAPI document as the wire-schema authority and this manual as the lifecycle authority. Do not call OpenAI, ElevenLabs, DigitalOcean, or another provider directly. Preserve idempotency keys, job IDs, SSE event IDs, settled usage, and artifact authentication. Never place credentials in URLs or logs.
Environments
| Purpose | Base URL |
|---|---|
| Local native development | http://127.0.0.1:8118 |
| Testing | https://alwafi-api.expressly.tools |
| OpenAPI 3.1 | <base URL>/openapi.json |
Set ALWAFI_URL to the origin only, without /v1 and without a trailing slash.
Store the Alwafi consumer token in Keychain or an equivalent secret store.
Never ship an administrator session or provider credential in Expressly.
ALWAFI_URL=https://alwafi-api.expressly.tools
Authorization: Bearer alw_...
Authentication and scopes
Every AI, discovery, wallet, job, event, and artifact request uses the consumer
token in the Authorization header. Tokens are user-bound, revocable, may
expire, and may restrict models, rate, spend, and scopes.
| Function | Required scope |
|---|---|
| Chat | chat |
| Embeddings | embeddings |
| Moderation | moderation |
| Responses and tools | responses |
| Image generation and edit | images |
| Speech and voices | speech |
| Transcription and translation | transcription |
Treat 401 as an authentication state, 403 as a policy/scope state, and
402 as a wallet state. Do not hide them behind a generic provider error.
Startup discovery
After authentication, load these catalogs instead of hard-coding provider models, voices, or prices:
GET /v1/models— enabled public model aliases allowed by the token.GET /v1/pricing— Alwafi-native meters and current customer prices.GET /v1/voices— paginated provider voice catalog.GET /v1/me/balance— available and reserved microcredits.
The public alias is the only model identifier Expressly stores. Upstream model and provider names are routing details and may change without a client release.
Choose connected or durable execution
Use a connected endpoint when the result must stream live or the request is small and naturally synchronous. Use a durable job when the client must be able to disconnect, restart, retry observation, or recover later.
| Capability | Connected endpoint | Durable job |
|---|---|---|
| Chat | POST /v1/chat/completions | capability: chat |
| Responses/tools | POST /v1/responses | Not enabled |
| Embeddings | POST /v1/embeddings | Not enabled |
| Moderation | POST /v1/moderations | Not enabled |
| Image generation | POST /v1/images/generations | capability: image_generation |
| Image edit | POST /v1/images/edits | capability: image_edit |
| Speech | POST /v1/text-to-speech/{voice_id} | capability: speech |
| Transcription | POST /v1/audio/transcriptions | capability: transcription |
| Translation | POST /v1/audio/translations | transcription job with task: translation |
Durable request lifecycle
- Generate and persist a unique idempotency key before the first request.
- Submit
POST /v1/jobswith that key inIdempotency-Key. - On
202, persist the returned job ID before starting observation. - Observe
GET /v1/jobs/{id}/eventsusing SSE. - Persist every SSE
idbefore applying its event. - Reconnect with
Last-Event-IDafter interruption. - Fall back to bounded polling of
GET /v1/jobs/{id}. - Fetch the canonical job after every terminal event.
- Download
artifact_urlwith the same bearer token.
POST /v1/jobs
Authorization: Bearer alw_...
Idempotency-Key: expressly-<persistent-uuid>
Content-Type: application/json
{
"capability": "chat",
"model": "gpt-5.4",
"max_attempts": 3,
"input": {
"messages": [{"role": "user", "content": "Hello"}]
}
}
The response is 202 Accepted with a Location header. Retrying the same
submission with the same key returns the existing job and does not create a
second reservation.
Terminal states are succeeded, failed, and cancelled. A terminal SSE
event is a notification, not the result payload; always fetch the job.
SSE client requirements
Use a line-oriented parser supporting id, event, multiple data lines,
comments, and blank-line frame termination. Send:
GET /v1/jobs/{id}/events
Authorization: Bearer alw_...
Accept: text/event-stream
Last-Event-ID: 418
On macOS use URLSession.bytes(for:); do not use a WebKit EventSource bridge.
The native session supports bearer headers and integrates with cancellation.
Use exponential reconnect backoff with jitter and a ceiling. Never submit a new
job merely because the event connection failed.
Output handling
- Chat returns the OpenAI-compatible choice and usage shape under
result. - Image responses preserve provider
urlorb64_jsonentries. - Speech returns an authenticated audio artifact, commonly
audio/mpeg. - Durable transcription returns an authenticated artifact. Parse it using its
response
Content-Type; transcription text is not assumed to be inline. - Embeddings return ordered vectors and provider usage.
- Responses preserve tool calls, reasoning, structured output, and usage.
- Final durable jobs expose
settled_usageandsettled_charge_micredits; these are authoritative.
Never persist temporary artifact URLs as permanent resources. Download or cache the authenticated content according to the product's retention rules.
Metering and wallet behavior
Alwafi is the pricing authority. Expressly must not calculate final provider
costs. Display estimates from /v1/pricing, but reconcile UI state to the final
settled values. Native meter units are token, image, character,
audio_second, and request.
A reservation temporarily reduces available balance while work is queued or
running. A successful request settles the measured amount; a terminal failure
or valid cancellation releases the reservation. Do not automatically retry
402.
Error handling
| Status | Client behavior |
|---|---|
400 | Show a validation/configuration error; do not retry unchanged input. |
401 | Request a fresh token/session; do not create another job. |
402 | Show insufficient credits; wait for a wallet change. |
403 | Show missing scope or policy restriction. |
404 | Treat the model/resource as unavailable to this token. |
409 | Refetch the job; its state changed before cancellation. |
429 | Retry with jitter and respect any retry metadata. |
502 | Connected upstream failure; durable work uses job retry state instead. |
Errors and job error_code values are intentionally sanitized. Do not infer a
provider or credential from them.
Recommended Swift structure
Expressly feature
-> AlwafiClient actor
-> Keychain token provider
-> URLSession transport
-> AlwafiJobStore
-> SSE decoder
-> artifact downloader
Persist idempotencyKey, jobID, status, lastEventID, and timestamps.
At launch, load non-terminal records, poll each once, finish terminal jobs, and
resume SSE for the remainder.
Acceptance tests for Expressly
- Catalogs load without hard-coded provider names.
- Every scope failure maps to an actionable UI state.
- Duplicate submission produces one job and one reservation.
- The app terminates after
202and recovers the job on launch. - SSE disconnects after each event and resumes without duplicate effects.
- Polling and SSE converge on the same terminal representation.
- Chat streaming withholds completion until usage settlement finishes.
- Images render from URL and base64 responses.
- Speech audio uses the returned
Content-Type. - Transcription downloads and parses the authenticated artifact.
- Wallet estimates reconcile to settled usage and charge.
- Revoked and expired tokens never trigger replacement job submission.
Canonical companion documents
GET /openapi.json— exact paths, schemas, security, and status responses.02-api-contract.md— detailed durable state and delivery contract.03-capability-payloads.md— capability examples.04-swift-client.md— macOS implementation detail.06-signed-webhooks.md— optional terminal callbacks.07-pricing-and-metering.md— native meters and settlement.