Skip to main content

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

PurposeBase URL
Local native developmenthttp://127.0.0.1:8118
Testinghttps://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.

FunctionRequired scope
Chatchat
Embeddingsembeddings
Moderationmoderation
Responses and toolsresponses
Image generation and editimages
Speech and voicesspeech
Transcription and translationtranscription

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.

CapabilityConnected endpointDurable job
ChatPOST /v1/chat/completionscapability: chat
Responses/toolsPOST /v1/responsesNot enabled
EmbeddingsPOST /v1/embeddingsNot enabled
ModerationPOST /v1/moderationsNot enabled
Image generationPOST /v1/images/generationscapability: image_generation
Image editPOST /v1/images/editscapability: image_edit
SpeechPOST /v1/text-to-speech/{voice_id}capability: speech
TranscriptionPOST /v1/audio/transcriptionscapability: transcription
TranslationPOST /v1/audio/translationstranscription job with task: translation

Durable request lifecycle

  1. Generate and persist a unique idempotency key before the first request.
  2. Submit POST /v1/jobs with that key in Idempotency-Key.
  3. On 202, persist the returned job ID before starting observation.
  4. Observe GET /v1/jobs/{id}/events using SSE.
  5. Persist every SSE id before applying its event.
  6. Reconnect with Last-Event-ID after interruption.
  7. Fall back to bounded polling of GET /v1/jobs/{id}.
  8. Fetch the canonical job after every terminal event.
  9. Download artifact_url with 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 url or b64_json entries.
  • 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_usage and settled_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

StatusClient behavior
400Show a validation/configuration error; do not retry unchanged input.
401Request a fresh token/session; do not create another job.
402Show insufficient credits; wait for a wallet change.
403Show missing scope or policy restriction.
404Treat the model/resource as unavailable to this token.
409Refetch the job; its state changed before cancellation.
429Retry with jitter and respect any retry metadata.
502Connected 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.

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 202 and 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.