useExperienceApi
The optional adapter that turns a callApi function (typically from @gxp-dev/devtools) into a set of typed named operations the experience flow can dispatch by name.
Pages stay pure — they emit captured data via next(data) and never call HTTP themselves. The flow looks at the page's action (either declared via withExperienceDefaults or set on the page def) and runs the matching api[action]() automatically.
Setup
import { callApi } from '@gxp-dev/devtools'
import { useExperience, useExperienceApi, CameraReviewPage, FinalPage } from '@gxp-dev/uikit'
const api = useExperienceApi({ callApi })
useExperience({
api,
pages: [
{ name: 'review', component: CameraReviewPage }, // → api.publishPost(data) → ctx.post
{ name: 'final', component: FinalPage },
],
})
callApi is the only hard dependency. If your platform exposes it under a different name, just pass that function in. Without api, named actions error out at runtime — but inline function actions (action: async (data) => …) still work.
Operations
Each method unwraps the standard { data } envelope (so you get the inner payload) and FormData-encodes any payload containing a Blob/File.
| Method | Default endpoint | Payload |
|---|---|---|
publishPost | social_stream.createPost | { blob?, caption?, attendee_id?, poster_name?, metadata? } |
fetchPost | social_stream.getPost | (id, { include? }) |
fetchRecentPosts | social_stream.getRecentPosts | — |
tagPost | social_stream.updatePost | (id, tags[]) |
createPrintJob | print_job.create | { social_stream_post_id, access_point_id? } |
processImage | ai_interface.processImage | { blob, prompt?, background_remover?, background_generator? } |
refinePrompt | ai_interface_prompt.refinePrompt | (prompt: string) |
generateImages | ai_interface_image.generateImages | { prompt, count? } |
analyzeSketch | ai_interface_text.analyzeSketch | { blob } |
call | any | (endpoint, payload?) — escape hatch |
Overrides
Replace a single operation
Useful when migrating from a legacy backend or stubbing in tests:
const api = useExperienceApi({
callApi,
overrides: {
publishPost: async (data) => {
const response = await fetch('/legacy/posts', { method: 'POST', body: data.blob })
return await response.json()
},
},
})
Remap endpoints
Keep the defaults' behavior but point a method at a different endpoint string:
const api = useExperienceApi({
callApi,
endpoints: {
publishPost: 'social_stream_v2.createPost',
createPrintJob: 'badging.print_job.create',
},
})
The call escape hatch
For one-off endpoints that don't warrant a typed operation, use api.call:
{ name: 'enrich', component: MyEnrichPage,
action: async (data, _ctx, api) => api!.call('enrichment.run', { id: data.id }) }
call still unwraps the { data } envelope but doesn't FormData-encode — pass your own FormData if you need it.
Behavior details
Blob → FormData
publishPost, processImage, and analyzeSketch automatically convert their payload into FormData when it contains a Blob or File. Plain objects pass through unchanged. Nested objects in a FormData payload are JSON.stringify-ed under their key.
Envelope unwrapping
The platform's typical response shape is { data: T }. Every method returns T. If the response doesn't have a data key, it's returned as-is.
Errors
Any rejected promise bubbles up through flow.next() → sets flow.error → keeps the page mounted. You can intercept globally via useExperience({ onError }), or per-page via an inline function action:
{
name: 'review',
component: CameraReviewPage,
action: async (data, ctx, api) => {
try {
return await api!.publishPost(data)
} catch (err) {
reportSentry(err)
throw err // re-throw to set flow.error
}
},
}
Typing
import type { ExperienceApi, CallApi, UseExperienceApiOptions } from '@gxp-dev/uikit'
ExperienceApi is the full interface returned by useExperienceApi. CallApi is the shape of the underlying function: <T>(endpoint: string, payload?: unknown) => Promise<T>.
See also
- Overview — flow, context, actions, slots.
- Pages reference — which built-in pages have default actions wired up.