Generate a four-view avatar sheet from a face image.
This document describes how to call the GOFA Avatar API from your backend or an authenticated user-facing application.
The Avatar API accepts a face image and returns:
POST /api/avatar
Call this endpoint on your assigned GOFA client domain.
Choose the credential issued for your integration. Send only one authentication mechanism per request.
Send your Avatar-enabled Commercial API key directly to POST /api/avatar:
Authorization: Bearer <GOFA_API_KEY>Use https://www.gofa.app for production Commercial API requests. The key identifies your workspace; do not send a clientId to switch workspaces. Keep the key on your backend. See API key authentication.
Exchange your client secret at POST /api/client/token, then send the returned token on your assigned GOFA client domain:
ClientToken: <client-token>See Client tokens for the exchange. A client secret and a Commercial API key are different credentials: do not exchange a Commercial API key for a ClientToken. The token's client must match the requested client domain and have API access enabled.
An existing GOFA/Firebase user integration can send:
Authorization: Bearer <Firebase ID Token>The signed-in user must have a direct claim for the client represented by the domain. A linked-client association alone does not grant Avatar access. See User authentication.
Send the request as multipart/form-data.
templateGender: female or maleoutfitMode: template or userSend the input image using exactly one of these fields:
faceFile: user face image filefaceImageBase64: user face image as a base64 string or data URLIf faceImageBase64 is a raw base64 string without a data:image/...;base64, prefix, also send:
faceImageMediaType: image/jpeg, image/png, or image/webpDo not send faceFile and faceImageBase64 in the same request.
outputFormat: sheet_url or panel_base64If omitted, outputFormat defaults to sheet_url.
image/jpegimage/pngimage/webp1 MiB2 MiBFor base64 input, GOFA decodes the base64 string and applies the 1 MiB limit to the decoded image bytes. Keep the complete multipart body within 2 MiB. When a Content-Length header is supplied, it must be a positive number no greater than 2,097,152 bytes. The endpoint does not require you to manually set that header. Let your HTTP client generate the multipart boundary and Content-Type.
curl --request POST 'https://www.gofa.app/api/avatar' \
--header "Authorization: Bearer $GOFA_API_KEY" \
--form 'faceFile=@face.jpg;type=image/jpeg' \
--form 'templateGender=female' \
--form 'outfitMode=template' \
--form 'outputFormat=sheet_url'const idToken = await auth.currentUser?.getIdToken();
const formData = new FormData();
formData.append("faceFile", file);
formData.append("templateGender", "female");
formData.append("outfitMode", "template");
formData.append("outputFormat", "sheet_url");
const response = await fetch("/api/avatar", {
method: "POST",
headers: {
Authorization: `Bearer ${idToken}`,
},
body: formData,
});
if (!response.ok) throw new Error(`Avatar request failed (${response.status})`);
const data = await response.json();const tokenResponse = await fetch("https://your-client.gofa.app/api/client/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
clientId: "your-client-id",
clientSecret: process.env.GOFA_CLIENT_SECRET,
}),
});
if (!tokenResponse.ok) throw new Error("Client token exchange failed");
const { token } = await tokenResponse.json();
const formData = new FormData();
formData.append("faceFile", file);
formData.append("templateGender", "female");
formData.append("outfitMode", "template");
formData.append("outputFormat", "panel_base64");
const response = await fetch("https://your-client.gofa.app/api/avatar", {
method: "POST",
headers: {
ClientToken: token,
},
body: formData,
});
if (!response.ok) throw new Error(`Avatar request failed (${response.status})`);
const data = await response.json();const formData = new FormData();
formData.append("faceImageBase64", "data:image/png;base64,iVBORw0KGgo...");
formData.append("templateGender", "female");
formData.append("outfitMode", "template");
formData.append("outputFormat", "panel_base64");
const response = await fetch("https://your-client.gofa.app/api/avatar", {
method: "POST",
headers: {
ClientToken: token,
},
body: formData,
});
if (!response.ok) throw new Error(`Avatar request failed (${response.status})`);
const data = await response.json();200 OK
Dimensions and coordinates below are illustrative. Always use the returned values. The signed sheetUrl expires one hour after it is created.
{
"sheetUrl": "https://...",
"sheet": {
"width": 1024,
"height": 1024
},
"panels": {
"front": {
"left": 20,
"top": 20,
"width": 472,
"height": 472
},
"rightThreeQuarter": {
"left": 532,
"top": 20,
"width": 472,
"height": 472
},
"rightProfile": {
"left": 20,
"top": 532,
"width": 472,
"height": 472
},
"back": {
"left": 532,
"top": 532,
"width": 472,
"height": 472
}
},
"panelImages": {
"front": {
"base64": "data:image/jpeg;base64,...",
"mediaType": "image/jpeg",
"width": 472,
"height": 472
},
"rightThreeQuarter": {
"base64": "data:image/jpeg;base64,...",
"mediaType": "image/jpeg",
"width": 472,
"height": 472
},
"rightProfile": {
"base64": "data:image/jpeg;base64,...",
"mediaType": "image/jpeg",
"width": 472,
"height": 472
},
"back": {
"base64": "data:image/jpeg;base64,...",
"mediaType": "image/jpeg",
"width": 472,
"height": 472
}
}
}The panelImages object is returned only when outputFormat is panel_base64.
sheetUrl: temporary URL of the generated avatar sheet imagesheet.width: width of the generated sheet imagesheet.height: height of the generated sheet imagepanels.front: crop rectangle for the front viewpanels.rightThreeQuarter: crop rectangle for the right three-quarter viewpanels.rightProfile: crop rectangle for the right profile viewpanels.back: crop rectangle for the back viewpanelImages.front.base64: optional base64 image for the front viewpanelImages.rightThreeQuarter.base64: optional base64 image for the right three-quarter viewpanelImages.rightProfile.base64: optional base64 image for the right profile viewpanelImages.back.base64: optional base64 image for the back view| Status | Meaning |
|---|---|
| 400 | Invalid fields, image data, multipart body, or supplied Content-Length |
| 401 | Missing or invalid authentication |
| 402 | Insufficient available credits or allowance |
| 403 | Credential lacks required client access, product entitlement, or Free membership |
| 413 | Input image exceeds 1 MiB, or supplied Content-Length exceeds 2 MiB |
| 415 | Unsupported image type |
| 500 | Generation or server processing failed, including generation timeout |
| 503 | Commercial allowance or commit state temporarily unavailable |
Commercial API failures use a structured error object with message, type, param, and code. Existing ClientToken/Firebase requests use a string error plus a code for handler errors. Authentication failures may be returned before the handler. Check the HTTP status before reading success fields.
For example, a Commercial API validation error may look like:
{
"error": {
"message": "Invalid form data",
"type": "invalid_request_error",
"param": null,
"code": "invalid_form_data"
}
}Generation may take time. A timeout does not prove that a request was never processed. Do not automatically resubmit generation after a connection loss or commit_outcome_unknown; check the outcome with GOFA first to avoid duplicate generation or usage.
A successful generation is one usage unit for avatar.generate. Commercial API access requires an Avatar-enabled key and product entitlement, a valid Free membership, and remaining allowance for the current cycle. Exhausted allowance returns 402 insufficient_credits.
Existing ClientToken and signed-in-user integrations follow their client's configured Avatar usage and billing rules. Do not infer their available balance or price from the Commercial API allowance. Confirm your workspace configuration with GOFA before production use.
By default, the response does not return four separate image URLs.
Instead:
This keeps storage usage low and avoids creating additional temporary panel files.
If you need separated panel image data, set:
outputFormat=panel_base64GOFA will still store only one generated sheet image, but the response will include four derived panel images as base64 strings.
<img
src={data.sheetUrl}
alt="Generated avatar sheet"
width={data.sheet.width}
height={data.sheet.height}
/>function renderPanel(
canvas: HTMLCanvasElement,
image: HTMLImageElement,
rect: { left: number; top: number; width: number; height: number }
) {
const context = canvas.getContext("2d");
if (!context) {
return;
}
canvas.width = rect.width;
canvas.height = rect.height;
context.clearRect(0, 0, rect.width, rect.height);
context.drawImage(
image,
rect.left,
rect.top,
rect.width,
rect.height,
0,
0,
rect.width,
rect.height,
);
}Example usage:
const image = new Image();
image.onload = () => {
renderPanel(frontCanvas, image, data.panels.front);
renderPanel(rightThreeQuarterCanvas, image, data.panels.rightThreeQuarter);
renderPanel(rightProfileCanvas, image, data.panels.rightProfile);
renderPanel(backCanvas, image, data.panels.back);
};
image.src = data.sheetUrl;multipart/form-data.faceFile or faceImageBase64, not both.1 MiB limit.1 MiB.sheetUrl as a temporary resource and should not assume it is permanent.sheetUrl.outputFormat is panel_base64, the returned panel base64 strings are derived from the generated sheet. GOFA still stores only the generated sheet image.