Skip to content

Brand Engine API

The FOTOhub Brand Engine (/v1/brands) is the centralized repository for brand visual identity kits, virtual brand faces and ambassadors, logos, product catalogs, color palettes, and automated brand compliance checking.

This powerful engine acts as the unified configuration layer across all FOTOhub generation pipelines, allowing your AI image, video, and audio generation requests to automatically inject consistent brand styling, fonts, colors, and characters.

Unified Architecture

By using the Brand Engine, you no longer need to pass complex prompt injections for styles, colors, and LoRA character models in every single API call. Instead, simply pass the brand_id parameter to any generation endpoint, and the FOTOhub backend handles the context assembly.

Base URL: https://apis.fotohub.app/v1/brands


Architecture Overview

The Brand Engine integrates deeply with FOTOhub's underlying GPU cluster and storage layers. FOTOhub employs a specialized orchestration layer that intelligently distributes brand-specific tasks across our geographically distributed GPU nodes.

mermaid
flowchart TD
    API[FOTOhub API Gateway] --> BE[Brand Engine]
    BE --> DB[(Identity DB PostgreSQL)]
    BE --> C[(Redis Context Cache)]
    
    API --> GEN[Generation Pipelines]
    GEN --> C
    
    GEN --> GPU1[GPU Node 1: Flux / SDXL Core]
    GEN --> GPU2[GPU Node 2: MMAudio]
    GEN --> GPU3[GPU Node 3: MuseTalk / LipSync]
    GEN --> GPU4[GPU Node 4: 3D / ControlNet]
    GEN --> GPU5[GPU Node 5: 3D / Mesh Processing]
    
    GPU1 -.-> BYOB[BYOB S3/R2 Storage]
    GPU2 -.-> BYOB
    GPU3 -.-> BYOB
    GPU4 -.-> BYOB

GPU Affinity & Allocation

The FOTOhub backend routes brand engine tasks to specialized GPU nodes based on the asset type and required compute topology:

  • GPU 1: Handles high-resolution image and core generative tasks (Flux, SDXL, LoRA training for brand faces).
  • GPU 2 (MMAudio): Handles brand voice synthesis, background track generation, and auditory compliance.
  • GPU 3 (MuseTalk/LipSync): Dedicated to virtual brand ambassador lip-syncing and expression temporal consistency.
  • GPU 4/5 (3D): Manage 3D logo extrusion, mesh generation, spatial compliance checks, and ControlNet structure enforcement.

Unit Economics & Cost Breakdown

FOTOhub uses pure USD billing only. We do not use credits, token packs, PLN, zł, or any synthetic currencies. All costs are transparently deducted from your wallet.available_usd balance.

OperationCost (USD)Detailed Description
Brand DNA extraction$0.015 / uploadVision-LLM extraction of brand guidelines from reference images (includes palette, tone, typography).
Color extraction$0.005 / uploadAutomated palette extraction and color naming via clustering.
Face generation$0.035 / faceMaster character generation using nano-banana-pro and identity locking.
Perspective variant$0.025 / angleConsistency-enforced rotation of master face (e.g., front, profile, three-quarter).
Expression variant$0.022 / variantPose or facial expression modification with ControlNet enforcement.
Compliance check$0.005 / imageScoring an asset against brand identity parameters.
Brand text generation$0.003 / textAI copywriting aligned with brand tone of voice.
Context AssemblyFreeInjecting brand context into generation endpoints.
Brand ExportFreeStreaming brand configuration to BYOB storage.

Account Balances

Ensure your wallet.available_usd has sufficient funds before initiating bulk generation tasks. Async jobs that run out of funds will instantly fail validation or be moved to the DLQ (Dead Letter Queue) with a insufficient_funds error state. To auto-top-up, use the /v1/billing/auto-reload endpoint.


List Brand Kits

Retrieve a paginated list of all brand identity kits associated with your workspace.

GET /v1/brands

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const response = await fetch(url, { method: "GET", headers });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X GET "https://apis.fotohub.app/v1/brands" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json"

Create Brand Kit

Initialize a new brand identity kit.

POST /v1/brands

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Get Full Brand Details

Fetch all stored configuration for a specific brand kit.

GET /v1/brands/{id}

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const response = await fetch(url, { method: "GET", headers });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X GET "https://apis.fotohub.app/v1/brands/{id}" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json"

Update Brand Profile

Modify fields on an existing brand profile.

PUT /v1/brands/{id}

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.put(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "PUT", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("PUT", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X PUT "https://apis.fotohub.app/v1/brands/{id}" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Delete Brand

Permanently delete a brand and all associated assets, faces, and trained LoRAs.

DELETE /v1/brands/{id}

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
response = requests.delete(url, headers=headers)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const response = await fetch(url, { method: "DELETE", headers });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}"
	req, _ := http.NewRequest("DELETE", url, nil)
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X DELETE "https://apis.fotohub.app/v1/brands/{id}" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json"

Get Brand Summary for LLMs

Returns a highly compressed, token-optimized text string representing the brand context.

GET /v1/brands/{id}/summary

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/summary"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/summary";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const response = await fetch(url, { method: "GET", headers });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/summary"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X GET "https://apis.fotohub.app/v1/brands/{id}/summary" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json"

Get Full Context Map

Fetches the complete resolution map for the brand, including references to internal LoRAs, cached face embeddings, style presets, and resolved CDN URLs.

GET /v1/brands/{id}/context

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/context"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/context";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const response = await fetch(url, { method: "GET", headers });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/context"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X GET "https://apis.fotohub.app/v1/brands/{id}/context" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json"

Export Brand Kit

Exports a static JSON/ZIP bundle of the entire brand kit to an external location (like a BYOB S3 bucket).

POST /v1/brands/{id}/export

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/export"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/export";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/export"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands/{id}/export" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Extract Dominant Colors

Upload a brand image to extract the core palette.

POST /v1/brands/{id}/extract-colors

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/extract-colors"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/extract-colors";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/extract-colors"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands/{id}/extract-colors" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Extract Brand DNA

Upload marketing collateral, packaging, or screenshots to automatically analyze and extract brand colors, visual style, tone of voice, typography, and keywords.

POST /v1/brands/{id}/extract-dna

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/extract-dna"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/extract-dna";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/extract-dna"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands/{id}/extract-dna" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Check Compliance

Score how closely a newly generated ad, banner, or photo matches the established brand guidelines on a scale of 0 to 100.

POST /v1/brands/{id}/check-compliance

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/check-compliance"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/check-compliance";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/check-compliance"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands/{id}/check-compliance" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Generate On-Brand Text

Generate copy (taglines, slogans, captions, or email subjects) that strictly adheres to the brand's stored tone of voice and keywords.

POST /v1/brands/{id}/generate-text

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/generate-text"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/generate-text";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/generate-text"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands/{id}/generate-text" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

List Brand Faces

Retrieves all virtual ambassadors linked to this brand kit.

GET /v1/brands/{id}/faces

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/faces"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/faces";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const response = await fetch(url, { method: "GET", headers });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/faces"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X GET "https://apis.fotohub.app/v1/brands/{id}/faces" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json"

Generate Master Virtual Ambassador

Initialize a new digital character from text parameters. This is an async job.

POST /v1/brands/{id}/faces/generate

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/faces/generate"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/faces/generate";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/faces/generate"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands/{id}/faces/generate" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Get Face Details

Retrieves face settings, parameters used for generation, and all currently generated variant URLs.

GET /v1/brands/{id}/faces/{face_id}

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const response = await fetch(url, { method: "GET", headers });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X GET "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json"

Delete Face

Removes the face embedding permanently.

DELETE /v1/brands/{id}/faces/{face_id}

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
response = requests.delete(url, headers=headers)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const response = await fetch(url, { method: "DELETE", headers });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}"
	req, _ := http.NewRequest("DELETE", url, nil)
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X DELETE "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json"

Generate Perspective Variants

Create consistent alternative angles for a master face embedding.

POST /v1/brands/{id}/faces/{face_id}/perspectives

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}/perspectives"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}/perspectives";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}/perspectives"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}/perspectives" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Generate Expression Variants

Create variations in facial expression or pose based on the master identity.

POST /v1/brands/{id}/faces/{face_id}/expressions

Path / Body Parameters

ParameterTypeRequiredDefaultDescription
idstringYes-Resource identifier
limitintegerNo20Pagination limit
offsetintegerNo0Pagination offset
verbosebooleanNofalseEnable verbose output
python
import requests
import json
import time

url = "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}/expressions"
headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
}
payload = {"dummy_field": "dummy_value"}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code)
print(response.json())
typescript
async function callApi() {
  const url = "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}/expressions";
  const headers = {
    "Authorization": "Bearer fh_live_your_api_key",
    "Content-Type": "application/json"
  };
  const body = JSON.stringify({ dummy_field: "dummy_value" });
  const response = await fetch(url, { method: "POST", headers, body });
  const data = await response.json();
  console.log(data);
}
callApi();
go
package main
import (
	"fmt"
	"io"
	"net/http"
	"bytes"
)
func main() {
	url := "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}/expressions"
	payload := []byte(`{"dummy_field": "dummy_value"}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Add("Authorization", "Bearer fh_live_your_api_key")
	req.Header.Add("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}
bash
curl -X POST "https://apis.fotohub.app/v1/brands/{id}/faces/{face_id}/expressions" \
  -H "Authorization: Bearer fh_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"dummy_field": "dummy_value"}'

Webhooks & Async Handling

For long-running tasks like face generation, massive multi-angle perspective rendering, or bulk DNA extraction, it is highly recommended to use FOTOhub Webhooks instead of polling /v1/jobs/{job_id}.

DLQ and Retries

If an async job fails (e.g., due to temporary GPU starvation, bad prompts, or insufficient funds), it automatically enters the Dead Letter Queue (DLQ).

  • Jobs failing due to insufficient_funds will remain in DLQ for 72 hours, allowing you to top up your wallet.available_usd and trigger a manual retry via POST /v1/jobs/{job_id}/retry.
  • Network or transient errors (e.g. S3 timeout) are automatically attempted up to 3 times with exponential backoff before entering DLQ.
  • You can list all DLQ jobs by calling GET /v1/jobs/dlq.

Webhook Verification Flow

mermaid
sequenceDiagram
    participant User Server
    participant FOTOhub
    FOTOhub->>FOTOhub: Job Completes (GPU Node)
    FOTOhub->>User Server: POST /webhook (Payload + X-FOTOhub-Signature)
    User Server->>User Server: Compute HMAC-SHA256
    User Server-->>FOTOhub: 200 OK

FOTOhub sends webhooks with a signature in the X-FOTOhub-Signature header. Always verify the HMAC-SHA256 signature to ensure the payload is authentic.

python
import hmac
import hashlib
from fastapi import Request, HTTPException

WEBHOOK_SECRET = "whsec_your_webhook_secret"

async def verify_webhook(request: Request):
    payload = await request.body()
    signature_header = request.headers.get("X-FOTOhub-Signature")
    
    if not signature_header:
        raise HTTPException(status_code=400, detail="Missing signature")
        
    expected_mac = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    if not hmac.compare_digest(expected_mac, signature_header):
        raise HTTPException(status_code=401, detail="Invalid signature")
        
    print("Webhook successfully verified!")
    return True
typescript
import crypto from 'crypto';

const WEBHOOK_SECRET = "whsec_your_webhook_secret";

function verifyWebhook(rawBody: Buffer, signature: string): boolean {
  if (!signature) return false;
  
  const expectedMac = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(rawBody)
    .digest('hex');
    
  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expectedMac),
    Buffer.from(signature)
  );
}

// In Express.js:
// app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
//   if (verifyWebhook(req.body, req.headers['x-fotohub-signature'])) {
//     res.status(200).send('OK');
//   } else {
//     res.status(401).send('Unauthorized');
//   }
// });
go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"net/http"
)

const webhookSecret = "whsec_your_webhook_secret"

func verifyWebhook(w http.ResponseWriter, r *http.Request) {
	signature := r.Header.Get("X-FOTOhub-Signature")
	if signature == "" {
		http.Error(w, "Missing signature", http.StatusBadRequest)
		return
	}

	body, _ := io.ReadAll(r.Body)
	mac := hmac.New(sha256.New, []byte(webhookSecret))
	mac.Write(body)
	expectedMAC := hex.EncodeToString(mac.Sum(nil))

	if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
		http.Error(w, "Invalid signature", http.StatusUnauthorized)
		return
	}

	w.WriteHeader(http.StatusOK)
}

Detailed Error Codes Reference

FOTOhub returns standard HTTP status codes along with a JSON body describing the error in detail.

HTTP CodeError Code stringResolution / Note
400invalid_parametersOne or more parameters failed validation. Check details array.
401unauthorizedThe API key is missing or invalid.
402insufficient_fundsYour wallet.available_usd balance is too low for the request.
403forbidden_actionAction not allowed for your workspace tier (e.g. missing 3D features).
404resource_not_foundBrand kit or Face ID does not exist.
413payload_too_largeMax payload size exceeded. For example, image file > 20MB.
429rate_limit_exceededMax concurrent jobs reached for your tier.
500internal_server_errorUnexpected backend error. A trace_id will be provided.
503gpu_node_offlineScheduled maintenance or temporary unavailability on the required GPU affinity group.