API Documentation
Integrate Feedback Pulse into your application and start collecting user feedback in minutes.
Quick Start: Create a project in the
Projects page to get your API key,
then use it in the X-API-Key header for all requests.
Authentication
All API requests must include your project's API key in the X-API-Key header.
Your API key is automatically generated when you create a project. Go to the Projects page, create a new project, and your API key will be displayed. Each project has its own unique API key.
Example Header
X-API-Key: fp_your_api_key_here
Security Tip: Never expose your API key in client-side code for web applications. Use a backend proxy or environment variables for mobile apps.
Base URL
All API endpoints are relative to the following base URL:
https://api.fpulse.app/v1
For local development: http://localhost:5052/api/v1
Rate Limits
API rate limits vary by subscription tier:
| Plan | Rate Limit | Monthly Feedback |
|---|---|---|
| Free | 100 requests/minute | 1,000 |
| Pro | 1,000 requests/minute | 50,000 |
| Enterprise | 10,000 requests/minute | Unlimited |
When rate limited, the API returns 429 Too Many Requests with a retry_after value.
GET /health
Check if the API is operational. No authentication required.
Request
curl https://api.fpulse.app/v1/health
Response
{
"status": "ok",
"service": "Feedback Pulse API"
}
POST /feedback
Submit user feedback from your application. This is the primary endpoint you'll use.
Request Headers
| Header | Value | Required |
|---|---|---|
| X-API-Key | Your project API key | Required |
| Content-Type | application/json | Required |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sentiment | string | Required | "positive" or "negative" |
| app_version | string | Required | Your app version (e.g., "2.1.0"). Max 50 chars. |
| app_type | string | Required | "ios", "android", "web", "desktop", or "other" |
| comment | string | Optional | User's feedback text. Max 5,000 chars. |
| screen_id | string | Optional | Screen/page identifier (e.g., "checkout", "settings"). Max 100 chars. |
| user_identifier | string | Optional | User email or ID for follow-up. Max 255 chars. |
| environment | string | Optional | "development" or "production" (default) |
| device_info | object | Optional | Device details object (see below) |
| metadata | object | Optional | Custom key-value pairs for additional context |
device_info Object
| Field | Type | Example |
|---|---|---|
| os | string | "iOS", "Android", "Windows", "macOS" |
| os_version | string | "17.2", "14", "11" |
| device_model | string | "iPhone 15 Pro", "Pixel 8" |
| locale | string | "en_US", "de_DE" |
| timezone | string | "America/New_York", "Europe/London" |
Complete Request Example
POST /api/v1/feedback HTTP/1.1
Host: api.fpulse.app
Content-Type: application/json
X-API-Key: fp_your_api_key_here
{
"sentiment": "positive",
"comment": "Love the new dark mode feature! Makes it much easier to use at night.",
"app_version": "2.1.0",
"app_type": "ios",
"screen_id": "settings",
"user_identifier": "user@example.com",
"environment": "production",
"device_info": {
"os": "iOS",
"os_version": "17.2",
"device_model": "iPhone 15 Pro",
"locale": "en_US",
"timezone": "America/New_York"
},
"metadata": {
"feature_used": "dark_mode",
"session_duration": 300,
"is_premium_user": true
}
}
Response
Success (201 Created)
{
"success": true,
"feedback_id": 12345,
"message": "Feedback submitted successfully"
}
Validation Error (400 Bad Request)
{
"success": false,
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": [
"sentiment is required",
"app_type must be one of: ios, android, web, desktop, other"
]
}
Authentication Error (401 Unauthorized)
{
"success": false,
"error": "Invalid or missing API key",
"code": "UNAUTHORIZED"
}
Limit Exceeded (403 Forbidden)
{
"success": false,
"error": "Monthly feedback limit exceeded. Please upgrade your plan.",
"code": "LIMIT_EXCEEDED"
}
POST /feedback/batch
Submit multiple feedback items in a single request. Useful for syncing offline feedback or bulk imports. Maximum 100 items per batch.
Request Body
{
"feedbacks": [
{
"sentiment": "positive",
"comment": "Great app!",
"app_version": "2.1.0",
"app_type": "ios",
"screen_id": "home"
},
{
"sentiment": "negative",
"comment": "App crashed on checkout",
"app_version": "2.1.0",
"app_type": "android",
"screen_id": "checkout"
}
]
}
Response
Full Success (201 Created)
{
"success": true,
"total": 2,
"succeeded": 2,
"failed": 0,
"results": [
{ "index": 0, "success": true, "feedback_id": 12345 },
{ "index": 1, "success": true, "feedback_id": 12346 }
]
}
Partial Success (207 Multi-Status)
{
"success": false,
"total": 2,
"succeeded": 1,
"failed": 1,
"results": [
{ "index": 0, "success": true, "feedback_id": 12345 },
{ "index": 1, "success": false, "errors": ["invalid sentiment"] }
]
}
GET /project/info
Retrieve information about your project. Useful for verifying your API key is valid and checking your subscription tier.
Request
curl -H "X-API-Key: fp_your_api_key" https://api.fpulse.app/v1/project/info
Response
{
"success": true,
"project": {
"id": 1,
"name": "My Mobile App"
},
"organization": {
"name": "My Company",
"tier": "pro"
}
}
JavaScript / TypeScript
Use this example for web applications, React, Vue, Node.js, or any JavaScript environment.
Installation
# No package needed - use fetch API
# Or install axios if preferred:
npm install axios
Basic Example (Fetch)
async function submitFeedback(sentiment, comment) {
const response = await fetch('https://api.fpulse.app/v1/feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'fp_your_api_key_here'
},
body: JSON.stringify({
sentiment: sentiment, // 'positive' or 'negative'
comment: comment,
app_version: '2.1.0',
app_type: 'web',
screen_id: window.location.pathname,
environment: process.env.NODE_ENV === 'production' ? 'production' : 'development'
})
});
const data = await response.json();
if (data.success) {
console.log('Feedback submitted:', data.feedback_id);
} else {
console.error('Error:', data.error);
}
return data;
}
// Usage
submitFeedback('positive', 'Great new feature!');
React Component Example
import { useState } from 'react';
const API_KEY = process.env.REACT_APP_FPULSE_API_KEY;
const API_URL = 'https://api.fpulse.app/v1/feedback';
function FeedbackWidget({ screenId }) {
const [comment, setComment] = useState('');
const [submitted, setSubmitted] = useState(false);
const [loading, setLoading] = useState(false);
const submitFeedback = async (sentiment) => {
setLoading(true);
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY
},
body: JSON.stringify({
sentiment,
comment,
app_version: process.env.REACT_APP_VERSION || '1.0.0',
app_type: 'web',
screen_id: screenId,
environment: process.env.NODE_ENV
})
});
const data = await response.json();
if (data.success) {
setSubmitted(true);
}
} catch (error) {
console.error('Feedback error:', error);
} finally {
setLoading(false);
}
};
if (submitted) {
return <div>Thank you for your feedback!</div>;
}
return (
<div className="feedback-widget">
<p>How was your experience?</p>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Tell us more (optional)"
/>
<div>
<button onClick={() => submitFeedback('positive')} disabled={loading}>
Good
</button>
<button onClick={() => submitFeedback('negative')} disabled={loading}>
Bad
</button>
</div>
</div>
);
}
Swift (iOS)
Native Swift implementation for iOS, iPadOS, and macOS applications.
FeedbackPulse.swift
import Foundation
import UIKit
class FeedbackPulse {
static let shared = FeedbackPulse()
private let apiKey: String
private let baseURL = "https://api.fpulse.app/v1"
private let appVersion: String
private let environment: String
init(apiKey: String = "fp_your_api_key_here",
appVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0",
environment: String = "production") {
self.apiKey = apiKey
self.appVersion = appVersion
self.environment = environment
}
struct FeedbackResult: Codable {
let success: Bool
let feedbackId: Int?
let error: String?
enum CodingKeys: String, CodingKey {
case success
case feedbackId = "feedback_id"
case error
}
}
func submit(
sentiment: String, // "positive" or "negative"
comment: String? = nil,
screenId: String? = nil,
userIdentifier: String? = nil,
metadata: [String: Any]? = nil,
completion: @escaping (Result<FeedbackResult, Error>) -> Void
) {
guard let url = URL(string: "\(baseURL)/feedback") else {
completion(.failure(NSError(domain: "FeedbackPulse", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(apiKey, forHTTPHeaderField: "X-API-Key")
var body: [String: Any] = [
"sentiment": sentiment,
"app_version": appVersion,
"app_type": "ios",
"environment": environment,
"device_info": [
"os": "iOS",
"os_version": UIDevice.current.systemVersion,
"device_model": UIDevice.current.model,
"locale": Locale.current.identifier,
"timezone": TimeZone.current.identifier
]
]
if let comment = comment { body["comment"] = comment }
if let screenId = screenId { body["screen_id"] = screenId }
if let userIdentifier = userIdentifier { body["user_identifier"] = userIdentifier }
if let metadata = metadata { body["metadata"] = metadata }
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NSError(domain: "FeedbackPulse", code: -2, userInfo: [NSLocalizedDescriptionKey: "No data"])))
return
}
do {
let result = try JSONDecoder().decode(FeedbackResult.self, from: data)
completion(.success(result))
} catch {
completion(.failure(error))
}
}.resume()
}
}
// Usage Example
FeedbackPulse.shared.submit(
sentiment: "positive",
comment: "Love the new update!",
screenId: "settings",
userIdentifier: "user@example.com"
) { result in
switch result {
case .success(let response):
print("Feedback submitted: \(response.feedbackId ?? 0)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
Kotlin (Android)
Native Kotlin implementation for Android applications using coroutines.
FeedbackPulse.kt
import android.content.Context
import android.os.Build
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
class FeedbackPulse(
private val apiKey: String,
private val context: Context,
private val environment: String = "production"
) {
companion object {
private const val BASE_URL = "https://api.fpulse.app/v1"
}
data class FeedbackResult(
val success: Boolean,
val feedbackId: Int? = null,
val error: String? = null
)
suspend fun submit(
sentiment: String,
comment: String? = null,
screenId: String? = null,
userIdentifier: String? = null,
metadata: Map<String, Any>? = null
): FeedbackResult = withContext(Dispatchers.IO) {
try {
val url = URL("$BASE_URL/feedback")
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("X-API-Key", apiKey)
connection.doOutput = true
val appVersion = context.packageManager
.getPackageInfo(context.packageName, 0).versionName
val deviceInfo = JSONObject().apply {
put("os", "Android")
put("os_version", Build.VERSION.RELEASE)
put("device_model", "${Build.MANUFACTURER} ${Build.MODEL}")
put("locale", Locale.getDefault().toString())
put("timezone", TimeZone.getDefault().id)
}
val body = JSONObject().apply {
put("sentiment", sentiment)
put("app_version", appVersion)
put("app_type", "android")
put("environment", environment)
put("device_info", deviceInfo)
comment?.let { put("comment", it) }
screenId?.let { put("screen_id", it) }
userIdentifier?.let { put("user_identifier", it) }
metadata?.let { put("metadata", JSONObject(it)) }
}
connection.outputStream.bufferedWriter().use {
it.write(body.toString())
}
val responseCode = connection.responseCode
val response = if (responseCode == HttpURLConnection.HTTP_CREATED) {
connection.inputStream.bufferedReader().readText()
} else {
connection.errorStream.bufferedReader().readText()
}
val json = JSONObject(response)
FeedbackResult(
success = json.getBoolean("success"),
feedbackId = if (json.has("feedback_id")) json.getInt("feedback_id") else null,
error = if (json.has("error")) json.getString("error") else null
)
} catch (e: Exception) {
FeedbackResult(success = false, error = e.message)
}
}
}
// Usage Example (in a ViewModel or Activity)
class MyViewModel : ViewModel() {
private val feedbackPulse = FeedbackPulse(
apiKey = "fp_your_api_key_here",
context = applicationContext
)
fun submitFeedback(isPositive: Boolean, comment: String?) {
viewModelScope.launch {
val result = feedbackPulse.submit(
sentiment = if (isPositive) "positive" else "negative",
comment = comment,
screenId = "main_screen"
)
if (result.success) {
Log.d("Feedback", "Submitted: ${result.feedbackId}")
} else {
Log.e("Feedback", "Error: ${result.error}")
}
}
}
}
Dart / Flutter
Cross-platform Flutter implementation for iOS, Android, Web, and Desktop applications.
pubspec.yaml
dependencies:
http: ^1.1.0
lib/feedback_pulse.dart
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
class FeedbackResult {
final bool success;
final int? feedbackId;
final String? error;
FeedbackResult({required this.success, this.feedbackId, this.error});
factory FeedbackResult.fromJson(Map<String, dynamic> json) {
return FeedbackResult(
success: json['success'] ?? false,
feedbackId: json['feedback_id'],
error: json['error'],
);
}
}
class FeedbackPulse {
static const String _baseUrl = 'https://api.fpulse.app/v1';
final String apiKey;
final String appVersion;
final String appType;
final String environment;
FeedbackPulse({
required this.apiKey,
required this.appVersion,
this.appType = 'ios',
this.environment = 'production',
});
Map<String, String> get _deviceInfo {
return {
'os': Platform.operatingSystem,
'os_version': Platform.operatingSystemVersion,
'locale': Platform.localeName,
};
}
Future<FeedbackResult> submit({
required String sentiment,
String? comment,
String? screenId,
String? userIdentifier,
Map<String, dynamic>? metadata,
}) async {
try {
final body = {
'sentiment': sentiment,
'app_version': appVersion,
'app_type': appType,
'environment': environment,
'device_info': _deviceInfo,
if (comment != null) 'comment': comment,
if (screenId != null) 'screen_id': screenId,
if (userIdentifier != null) 'user_identifier': userIdentifier,
if (metadata != null) 'metadata': metadata,
};
final response = await http.post(
Uri.parse('$_baseUrl/feedback'),
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
},
body: jsonEncode(body),
);
final data = jsonDecode(response.body) as Map<String, dynamic>;
return FeedbackResult.fromJson(data);
} catch (e) {
return FeedbackResult(success: false, error: e.toString());
}
}
}
// Usage Example
// final client = FeedbackPulse(
// apiKey: 'fp_your_api_key_here',
// appVersion: '2.1.0',
// appType: Platform.isIOS ? 'ios' : 'android',
// environment: kReleaseMode ? 'production' : 'development',
// );
//
// final result = await client.submit(
// sentiment: 'positive',
// comment: 'Flutter is amazing!',
// screenId: 'home_screen',
// );
//
// if (result.success) {
// print('Feedback submitted: ${result.feedbackId}');
// } else {
// print('Error: ${result.error}');
// }
Flutter Widget Example
import 'package:flutter/material.dart';
class FeedbackWidget extends StatefulWidget {
final FeedbackPulse client;
final String screenId;
const FeedbackWidget({
super.key,
required this.client,
required this.screenId,
});
@override
State<FeedbackWidget> createState() => _FeedbackWidgetState();
}
class _FeedbackWidgetState extends State<FeedbackWidget> {
final _commentController = TextEditingController();
bool _submitted = false;
bool _loading = false;
Future<void> _submit(String sentiment) async {
setState(() => _loading = true);
final result = await widget.client.submit(
sentiment: sentiment,
comment: _commentController.text.isNotEmpty
? _commentController.text
: null,
screenId: widget.screenId,
);
setState(() {
_loading = false;
_submitted = result.success;
});
if (!result.success && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${result.error}')),
);
}
}
@override
Widget build(BuildContext context) {
if (_submitted) {
return const Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('Thank you for your feedback!'),
),
);
}
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('How was your experience?'),
const SizedBox(height: 8),
TextField(
controller: _commentController,
decoration: const InputDecoration(
hintText: 'Tell us more (optional)',
border: OutlineInputBorder(),
),
maxLines: 3,
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: _loading ? null : () => _submit('positive'),
icon: const Icon(Icons.thumb_up),
label: const Text('Good'),
),
ElevatedButton.icon(
onPressed: _loading ? null : () => _submit('negative'),
icon: const Icon(Icons.thumb_down),
label: const Text('Bad'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
),
],
),
],
),
),
);
}
}
Python
Python implementation for backend services, scripts, or desktop applications.
Installation
pip install requests
feedback_pulse.py
import requests
import platform
from typing import Optional, Dict, Any
from dataclasses import dataclass
@dataclass
class FeedbackResult:
success: bool
feedback_id: Optional[int] = None
error: Optional[str] = None
class FeedbackPulse:
BASE_URL = "https://api.fpulse.app/v1"
def __init__(
self,
api_key: str,
app_version: str = "1.0.0",
app_type: str = "desktop",
environment: str = "production"
):
self.api_key = api_key
self.app_version = app_version
self.app_type = app_type
self.environment = environment
self.session = requests.Session()
self.session.headers.update({
"Content-Type": "application/json",
"X-API-Key": api_key
})
def _get_device_info(self) -> Dict[str, str]:
return {
"os": platform.system(),
"os_version": platform.release(),
"device_model": platform.machine(),
"locale": "en_US", # You might want to detect this
}
def submit(
self,
sentiment: str, # "positive" or "negative"
comment: Optional[str] = None,
screen_id: Optional[str] = None,
user_identifier: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None
) -> FeedbackResult:
"""Submit feedback to Feedback Pulse API."""
payload = {
"sentiment": sentiment,
"app_version": self.app_version,
"app_type": self.app_type,
"environment": self.environment,
"device_info": self._get_device_info()
}
if comment:
payload["comment"] = comment
if screen_id:
payload["screen_id"] = screen_id
if user_identifier:
payload["user_identifier"] = user_identifier
if metadata:
payload["metadata"] = metadata
try:
response = self.session.post(
f"{self.BASE_URL}/feedback",
json=payload
)
data = response.json()
return FeedbackResult(
success=data.get("success", False),
feedback_id=data.get("feedback_id"),
error=data.get("error")
)
except Exception as e:
return FeedbackResult(success=False, error=str(e))
def submit_batch(
self,
feedbacks: list[Dict[str, Any]]
) -> Dict[str, Any]:
"""Submit multiple feedback items at once."""
try:
response = self.session.post(
f"{self.BASE_URL}/feedback/batch",
json={"feedbacks": feedbacks}
)
return response.json()
except Exception as e:
return {"success": False, "error": str(e)}
# Usage Example
if __name__ == "__main__":
# Initialize the client
client = FeedbackPulse(
api_key="fp_your_api_key_here",
app_version="2.1.0",
app_type="desktop"
)
# Submit positive feedback
result = client.submit(
sentiment="positive",
comment="The new export feature is amazing!",
screen_id="export_dialog",
user_identifier="user@example.com",
metadata={
"export_format": "csv",
"record_count": 1500
}
)
if result.success:
print(f"Feedback submitted! ID: {result.feedback_id}")
else:
print(f"Error: {result.error}")
Java
Java implementation for backend services, Android (if not using Kotlin), and enterprise applications.
Maven Dependency
<!-- Using java.net.http (Java 11+) - no external dependencies needed -->
<!-- Or add Gson for JSON parsing: -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
FeedbackPulse.java
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
public class FeedbackPulse {
private static final String BASE_URL = "https://api.fpulse.app/v1";
private final HttpClient httpClient;
private final Gson gson;
private final String apiKey;
private final String appVersion;
private final String appType;
private final String environment;
public FeedbackPulse(String apiKey, String appVersion) {
this(apiKey, appVersion, "desktop", "production");
}
public FeedbackPulse(String apiKey, String appVersion, String appType, String environment) {
this.apiKey = apiKey;
this.appVersion = appVersion;
this.appType = appType;
this.environment = environment;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
this.gson = new Gson();
}
public record FeedbackResult(
boolean success,
@SerializedName("feedback_id") Integer feedbackId,
String error
) {}
public FeedbackResult submit(String sentiment, String comment, String screenId) {
return submit(sentiment, comment, screenId, null, null);
}
public FeedbackResult submit(
String sentiment,
String comment,
String screenId,
String userIdentifier,
Map<String, Object> metadata
) {
try {
Map<String, Object> body = new HashMap<>();
body.put("sentiment", sentiment);
body.put("app_version", appVersion);
body.put("app_type", appType);
body.put("environment", environment);
body.put("device_info", getDeviceInfo());
if (comment != null) body.put("comment", comment);
if (screenId != null) body.put("screen_id", screenId);
if (userIdentifier != null) body.put("user_identifier", userIdentifier);
if (metadata != null) body.put("metadata", metadata);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/feedback"))
.header("Content-Type", "application/json")
.header("X-API-Key", apiKey)
.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(body)))
.build();
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
return gson.fromJson(response.body(), FeedbackResult.class);
} catch (Exception e) {
return new FeedbackResult(false, null, e.getMessage());
}
}
private Map<String, String> getDeviceInfo() {
Map<String, String> info = new HashMap<>();
info.put("os", System.getProperty("os.name"));
info.put("os_version", System.getProperty("os.version"));
info.put("java_version", System.getProperty("java.version"));
info.put("locale", java.util.Locale.getDefault().toString());
return info;
}
}
// Usage Example
// FeedbackPulse client = new FeedbackPulse(
// "fp_your_api_key_here",
// "2.1.0",
// "desktop",
// "production"
// );
//
// FeedbackPulse.FeedbackResult result = client.submit(
// "positive",
// "Java integration works great!",
// "main_screen"
// );
//
// if (result.success()) {
// System.out.println("Feedback submitted: " + result.feedbackId());
// } else {
// System.err.println("Error: " + result.error());
// }
Spring Boot Integration
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class FeedbackService {
private final WebClient webClient;
private final String appVersion;
public FeedbackService(
@Value("${fpulse.api-key}") String apiKey,
@Value("${fpulse.app-version:1.0.0}") String appVersion
) {
this.appVersion = appVersion;
this.webClient = WebClient.builder()
.baseUrl("https://api.fpulse.app/v1")
.defaultHeader("X-API-Key", apiKey)
.defaultHeader("Content-Type", "application/json")
.build();
}
public Mono<FeedbackResult> submit(String sentiment, String comment, String screenId) {
var body = Map.of(
"sentiment", sentiment,
"comment", comment != null ? comment : "",
"screen_id", screenId,
"app_version", appVersion,
"app_type", "web",
"environment", "production"
);
return webClient.post()
.uri("/feedback")
.bodyValue(body)
.retrieve()
.bodyToMono(FeedbackResult.class);
}
public record FeedbackResult(boolean success, Integer feedbackId, String error) {}
}
PHP 8+
Modern PHP implementation using native features. Works with Laravel, Symfony, or vanilla PHP.
FeedbackPulse.php
<?php
declare(strict_types=1);
readonly class FeedbackResult
{
public function __construct(
public bool $success,
public ?int $feedbackId = null,
public ?string $error = null
) {}
}
class FeedbackPulse
{
private const BASE_URL = 'https://api.fpulse.app/v1';
public function __construct(
private string $apiKey,
private string $appVersion = '1.0.0',
private string $appType = 'web',
private string $environment = 'production'
) {}
public function submit(
string $sentiment,
?string $comment = null,
?string $screenId = null,
?string $userIdentifier = null,
?array $metadata = null
): FeedbackResult {
$payload = [
'sentiment' => $sentiment,
'app_version' => $this->appVersion,
'app_type' => $this->appType,
'environment' => $this->environment,
'device_info' => [
'os' => PHP_OS_FAMILY,
'os_version' => php_uname('r'),
'locale' => locale_get_default() ?: 'en_US',
],
];
if ($comment !== null) $payload['comment'] = $comment;
if ($screenId !== null) $payload['screen_id'] = $screenId;
if ($userIdentifier !== null) $payload['user_identifier'] = $userIdentifier;
if ($metadata !== null) $payload['metadata'] = $metadata;
try {
$response = $this->request('POST', '/feedback', $payload);
return new FeedbackResult(
success: $response['success'] ?? false,
feedbackId: $response['feedback_id'] ?? null,
error: $response['error'] ?? null
);
} catch (Exception $e) {
return new FeedbackResult(success: false, error: $e->getMessage());
}
}
public function submitBatch(array $feedbacks): array
{
return $this->request('POST', '/feedback/batch', ['feedbacks' => $feedbacks]);
}
private function request(string $method, string $endpoint, array $data): array
{
$ch = curl_init(self::BASE_URL . $endpoint);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $this->apiKey,
],
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false) {
throw new Exception('Request failed');
}
return json_decode($response, true) ?? [];
}
}
// Usage Example
$client = new FeedbackPulse(
apiKey: 'fp_your_api_key_here',
appVersion: '2.1.0',
appType: 'web'
);
$result = $client->submit(
sentiment: 'positive',
comment: 'The checkout process is so smooth!',
screenId: 'checkout',
userIdentifier: 'user@example.com',
metadata: ['cart_total' => 99.99]
);
if ($result->success) {
echo "Feedback submitted! ID: {$result->feedbackId}\n";
} else {
echo "Error: {$result->error}\n";
}
Laravel Integration
// In a Controller or Service
use Illuminate\Support\Facades\Http;
class FeedbackService
{
public function submit(string $sentiment, ?string $comment = null): array
{
$response = Http::withHeaders([
'X-API-Key' => config('services.fpulse.api_key'),
])->post('https://api.fpulse.app/v1/feedback', [
'sentiment' => $sentiment,
'comment' => $comment,
'app_version' => config('app.version'),
'app_type' => 'web',
'screen_id' => request()->path(),
'user_identifier' => auth()->user()?->email,
'environment' => app()->environment(),
]);
return $response->json();
}
}
Go
Idiomatic Go implementation for backend services and CLI tools.
feedbackpulse.go
package feedbackpulse
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"runtime"
"time"
)
const baseURL = "https://api.fpulse.app/v1"
type Client struct {
APIKey string
AppVersion string
AppType string
Environment string
HTTPClient *http.Client
}
type FeedbackRequest struct {
Sentiment string `json:"sentiment"`
Comment string `json:"comment,omitempty"`
AppVersion string `json:"app_version"`
AppType string `json:"app_type"`
ScreenID string `json:"screen_id,omitempty"`
UserIdentifier string `json:"user_identifier,omitempty"`
Environment string `json:"environment"`
DeviceInfo map[string]string `json:"device_info,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type FeedbackResult struct {
Success bool `json:"success"`
FeedbackID int `json:"feedback_id,omitempty"`
Error string `json:"error,omitempty"`
}
func NewClient(apiKey, appVersion string) *Client {
return &Client{
APIKey: apiKey,
AppVersion: appVersion,
AppType: "desktop",
Environment: "production",
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func (c *Client) Submit(sentiment, comment, screenID string) (*FeedbackResult, error) {
req := FeedbackRequest{
Sentiment: sentiment,
Comment: comment,
AppVersion: c.AppVersion,
AppType: c.AppType,
ScreenID: screenID,
Environment: c.Environment,
DeviceInfo: map[string]string{
"os": runtime.GOOS,
"os_version": runtime.Version(),
"arch": runtime.GOARCH,
},
}
return c.doRequest("POST", "/feedback", req)
}
func (c *Client) SubmitWithMetadata(
sentiment, comment, screenID, userID string,
metadata map[string]interface{},
) (*FeedbackResult, error) {
req := FeedbackRequest{
Sentiment: sentiment,
Comment: comment,
AppVersion: c.AppVersion,
AppType: c.AppType,
ScreenID: screenID,
UserIdentifier: userID,
Environment: c.Environment,
Metadata: metadata,
DeviceInfo: map[string]string{
"os": runtime.GOOS,
"os_version": runtime.Version(),
"arch": runtime.GOARCH,
},
}
return c.doRequest("POST", "/feedback", req)
}
func (c *Client) doRequest(method, endpoint string, body interface{}) (*FeedbackResult, error) {
jsonData, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("marshal error: %w", err)
}
req, err := http.NewRequest(method, baseURL+endpoint, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("request error: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", c.APIKey)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("http error: %w", err)
}
defer resp.Body.Close()
var result FeedbackResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("decode error: %w", err)
}
return &result, nil
}
// Usage Example
// func main() {
// client := feedbackpulse.NewClient("fp_your_api_key", "2.1.0")
// client.Environment = "production"
//
// result, err := client.Submit("positive", "Great CLI tool!", "main")
// if err != nil {
// log.Fatal(err)
// }
//
// if result.Success {
// fmt.Printf("Feedback submitted! ID: %d\n", result.FeedbackID)
// }
// }
Rust
Type-safe Rust implementation using reqwest and serde.
Cargo.toml
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
src/feedback_pulse.rs
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
const BASE_URL: &str = "https://api.fpulse.app/v1";
#[derive(Debug, Serialize)]
pub struct FeedbackRequest {
pub sentiment: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
pub app_version: String,
pub app_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub screen_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_identifier: Option<String>,
pub environment: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_info: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub struct FeedbackResult {
pub success: bool,
pub feedback_id: Option<i32>,
pub error: Option<String>,
}
pub struct FeedbackPulse {
client: Client,
api_key: String,
app_version: String,
app_type: String,
environment: String,
}
impl FeedbackPulse {
pub fn new(api_key: &str, app_version: &str) -> Self {
Self {
client: Client::new(),
api_key: api_key.to_string(),
app_version: app_version.to_string(),
app_type: "desktop".to_string(),
environment: "production".to_string(),
}
}
pub fn with_environment(mut self, env: &str) -> Self {
self.environment = env.to_string();
self
}
pub fn with_app_type(mut self, app_type: &str) -> Self {
self.app_type = app_type.to_string();
self
}
fn get_device_info() -> HashMap<String, String> {
let mut info = HashMap::new();
info.insert("os".to_string(), std::env::consts::OS.to_string());
info.insert("arch".to_string(), std::env::consts::ARCH.to_string());
info
}
pub async fn submit(
&self,
sentiment: &str,
comment: Option<&str>,
screen_id: Option<&str>,
) -> Result<FeedbackResult, reqwest::Error> {
let request = FeedbackRequest {
sentiment: sentiment.to_string(),
comment: comment.map(|s| s.to_string()),
app_version: self.app_version.clone(),
app_type: self.app_type.clone(),
screen_id: screen_id.map(|s| s.to_string()),
user_identifier: None,
environment: self.environment.clone(),
device_info: Some(Self::get_device_info()),
metadata: None,
};
self.client
.post(format!("{}/feedback", BASE_URL))
.header("Content-Type", "application/json")
.header("X-API-Key", &self.api_key)
.json(&request)
.send()
.await?
.json()
.await
}
}
// Usage Example
// #[tokio::main]
// async fn main() -> Result<(), Box<dyn std::error::Error>> {
// let client = FeedbackPulse::new("fp_your_api_key", "2.1.0")
// .with_environment("production");
//
// let result = client
// .submit("positive", Some("Blazing fast!"), Some("main"))
// .await?;
//
// if result.success {
// println!("Feedback submitted! ID: {:?}", result.feedback_id);
// } else {
// eprintln!("Error: {:?}", result.error);
// }
//
// Ok(())
// }
Ruby
Ruby implementation for Rails applications and scripts.
feedback_pulse.rb
require 'net/http'
require 'json'
require 'uri'
class FeedbackPulse
BASE_URL = 'https://api.fpulse.app/v1'.freeze
FeedbackResult = Struct.new(:success, :feedback_id, :error, keyword_init: true)
def initialize(api_key:, app_version: '1.0.0', app_type: 'web', environment: 'production')
@api_key = api_key
@app_version = app_version
@app_type = app_type
@environment = environment
end
def submit(sentiment:, comment: nil, screen_id: nil, user_identifier: nil, metadata: nil)
payload = {
sentiment: sentiment,
app_version: @app_version,
app_type: @app_type,
environment: @environment,
device_info: device_info
}
payload[:comment] = comment if comment
payload[:screen_id] = screen_id if screen_id
payload[:user_identifier] = user_identifier if user_identifier
payload[:metadata] = metadata if metadata
response = request(:post, '/feedback', payload)
FeedbackResult.new(
success: response['success'],
feedback_id: response['feedback_id'],
error: response['error']
)
rescue StandardError => e
FeedbackResult.new(success: false, error: e.message)
end
def submit_batch(feedbacks)
request(:post, '/feedback/batch', { feedbacks: feedbacks })
end
private
def device_info
{
os: RUBY_PLATFORM,
os_version: RUBY_VERSION,
locale: ENV['LANG'] || 'en_US'
}
end
def request(method, endpoint, body)
uri = URI("#{BASE_URL}#{endpoint}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = case method
when :post then Net::HTTP::Post.new(uri)
when :get then Net::HTTP::Get.new(uri)
end
request['Content-Type'] = 'application/json'
request['X-API-Key'] = @api_key
request.body = body.to_json
response = http.request(request)
JSON.parse(response.body)
end
end
# Usage Example
client = FeedbackPulse.new(
api_key: 'fp_your_api_key_here',
app_version: '2.1.0',
app_type: 'web'
)
result = client.submit(
sentiment: 'positive',
comment: 'Ruby rocks!',
screen_id: 'dashboard',
user_identifier: 'user@example.com'
)
if result.success
puts "Feedback submitted! ID: #{result.feedback_id}"
else
puts "Error: #{result.error}"
end
Rails Integration
# config/initializers/feedback_pulse.rb
FEEDBACK_PULSE = FeedbackPulse.new(
api_key: Rails.application.credentials.fpulse_api_key,
app_version: Rails.application.config.version,
environment: Rails.env
)
# In a controller
class FeedbackController < ApplicationController
def create
result = FEEDBACK_PULSE.submit(
sentiment: params[:sentiment],
comment: params[:comment],
screen_id: request.path,
user_identifier: current_user&.email
)
if result.success
render json: { success: true, id: result.feedback_id }
else
render json: { success: false, error: result.error }, status: :unprocessable_entity
end
end
end
C# / .NET
Modern C# implementation for .NET 6+ applications, ASP.NET Core, MAUI, and Unity.
FeedbackPulse.cs
using System.Net.Http.Json;
using System.Text.Json.Serialization;
namespace FeedbackPulse;
public record FeedbackResult(
[property: JsonPropertyName("success")] bool Success,
[property: JsonPropertyName("feedback_id")] int? FeedbackId = null,
[property: JsonPropertyName("error")] string? Error = null
);
public class FeedbackRequest
{
[JsonPropertyName("sentiment")] public required string Sentiment { get; init; }
[JsonPropertyName("comment")] public string? Comment { get; init; }
[JsonPropertyName("app_version")] public required string AppVersion { get; init; }
[JsonPropertyName("app_type")] public required string AppType { get; init; }
[JsonPropertyName("screen_id")] public string? ScreenId { get; init; }
[JsonPropertyName("user_identifier")] public string? UserIdentifier { get; init; }
[JsonPropertyName("environment")] public string Environment { get; init; } = "production";
[JsonPropertyName("device_info")] public Dictionary<string, string>? DeviceInfo { get; init; }
[JsonPropertyName("metadata")] public Dictionary<string, object>? Metadata { get; init; }
}
public class FeedbackPulseClient : IDisposable
{
private const string BaseUrl = "https://api.fpulse.app/v1";
private readonly HttpClient _httpClient;
private readonly string _appVersion;
private readonly string _appType;
private readonly string _environment;
public FeedbackPulseClient(
string apiKey,
string appVersion,
string appType = "desktop",
string environment = "production")
{
_appVersion = appVersion;
_appType = appType;
_environment = environment;
_httpClient = new HttpClient
{
BaseAddress = new Uri(BaseUrl),
Timeout = TimeSpan.FromSeconds(30)
};
_httpClient.DefaultRequestHeaders.Add("X-API-Key", apiKey);
}
public async Task<FeedbackResult> SubmitAsync(
string sentiment,
string? comment = null,
string? screenId = null,
string? userIdentifier = null,
Dictionary<string, object>? metadata = null,
CancellationToken ct = default)
{
var request = new FeedbackRequest
{
Sentiment = sentiment,
Comment = comment,
AppVersion = _appVersion,
AppType = _appType,
ScreenId = screenId,
UserIdentifier = userIdentifier,
Environment = _environment,
Metadata = metadata,
DeviceInfo = new Dictionary<string, string>
{
["os"] = Environment.OSVersion.Platform.ToString(),
["os_version"] = Environment.OSVersion.VersionString,
["runtime"] = Environment.Version.ToString()
}
};
try
{
var response = await _httpClient.PostAsJsonAsync("/feedback", request, ct);
return await response.Content.ReadFromJsonAsync<FeedbackResult>(ct)
?? new FeedbackResult(false, Error: "Failed to parse response");
}
catch (Exception ex)
{
return new FeedbackResult(false, Error: ex.Message);
}
}
public void Dispose() => _httpClient.Dispose();
}
// Usage Example
// var client = new FeedbackPulseClient(
// apiKey: "fp_your_api_key_here",
// appVersion: "2.1.0",
// appType: "desktop"
// );
//
// var result = await client.SubmitAsync(
// sentiment: "positive",
// comment: ".NET is awesome!",
// screenId: "main_window"
// );
//
// if (result.Success)
// Console.WriteLine($"Feedback submitted! ID: {result.FeedbackId}");
// else
// Console.WriteLine($"Error: {result.Error}");
ASP.NET Core Dependency Injection
// Program.cs
builder.Services.AddSingleton(sp =>
new FeedbackPulseClient(
apiKey: builder.Configuration["FeedbackPulse:ApiKey"]!,
appVersion: Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.0.0",
appType: "web",
environment: builder.Environment.EnvironmentName.ToLower()
));
// In a Controller
[ApiController]
[Route("api/[controller]")]
public class FeedbackController : ControllerBase
{
private readonly FeedbackPulseClient _feedbackClient;
public FeedbackController(FeedbackPulseClient feedbackClient)
=> _feedbackClient = feedbackClient;
[HttpPost]
public async Task<IActionResult> Submit([FromBody] FeedbackDto dto)
{
var result = await _feedbackClient.SubmitAsync(
sentiment: dto.Sentiment,
comment: dto.Comment,
screenId: Request.Path,
userIdentifier: User.Identity?.Name
);
return result.Success
? Ok(new { id = result.FeedbackId })
: BadRequest(new { error = result.Error });
}
}
Elixir
Elixir implementation for Phoenix applications and OTP services.
mix.exs Dependencies
defp deps do
[
{:httpoison, "~> 2.0"},
{:jason, "~> 1.4"}
]
end
lib/feedback_pulse.ex
defmodule FeedbackPulse do
@moduledoc """
Client for the Feedback Pulse API.
"""
@base_url "https://api.fpulse.app/v1"
defstruct [:api_key, :app_version, :app_type, :environment]
@type t :: %__MODULE__{
api_key: String.t(),
app_version: String.t(),
app_type: String.t(),
environment: String.t()
}
@type feedback_result :: {:ok, map()} | {:error, String.t()}
@doc """
Creates a new FeedbackPulse client.
"""
@spec new(String.t(), String.t(), keyword()) :: t()
def new(api_key, app_version, opts \\ []) do
%__MODULE__{
api_key: api_key,
app_version: app_version,
app_type: Keyword.get(opts, :app_type, "web"),
environment: Keyword.get(opts, :environment, "production")
}
end
@doc """
Submits feedback to the API.
"""
@spec submit(t(), String.t(), keyword()) :: feedback_result()
def submit(%__MODULE__{} = client, sentiment, opts \\ []) do
body = %{
sentiment: sentiment,
app_version: client.app_version,
app_type: client.app_type,
environment: client.environment,
device_info: get_device_info()
}
|> maybe_put(:comment, Keyword.get(opts, :comment))
|> maybe_put(:screen_id, Keyword.get(opts, :screen_id))
|> maybe_put(:user_identifier, Keyword.get(opts, :user_identifier))
|> maybe_put(:metadata, Keyword.get(opts, :metadata))
headers = [
{"Content-Type", "application/json"},
{"X-API-Key", client.api_key}
]
case HTTPoison.post("#{@base_url}/feedback", Jason.encode!(body), headers) do
{:ok, %HTTPoison.Response{status_code: 201, body: body}} ->
{:ok, Jason.decode!(body)}
{:ok, %HTTPoison.Response{body: body}} ->
case Jason.decode(body) do
{:ok, %{"error" => error}} -> {:error, error}
_ -> {:error, "Unknown error"}
end
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, to_string(reason)}
end
end
defp get_device_info do
%{
os: to_string(:os.type() |> elem(0)),
os_version: :erlang.system_info(:otp_release) |> to_string(),
elixir_version: System.version(),
locale: System.get_env("LANG", "en_US")
}
end
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
end
# Usage Example
# client = FeedbackPulse.new(
# "fp_your_api_key_here",
# "2.1.0",
# app_type: "web",
# environment: "production"
# )
#
# case FeedbackPulse.submit(client, "positive",
# comment: "Elixir is elegant!",
# screen_id: "home",
# user_identifier: "user@example.com"
# ) do
# {:ok, %{"feedback_id" => id}} ->
# IO.puts("Feedback submitted: #{id}")
#
# {:error, reason} ->
# IO.puts("Error: #{reason}")
# end
Phoenix Integration
# config/config.exs
config :my_app, :feedback_pulse,
api_key: System.get_env("FPULSE_API_KEY"),
app_version: Mix.Project.config()[:version]
# lib/my_app/feedback.ex
defmodule MyApp.Feedback do
@config Application.compile_env(:my_app, :feedback_pulse)
def client do
FeedbackPulse.new(
@config[:api_key],
@config[:app_version],
app_type: "web",
environment: Mix.env() |> to_string()
)
end
def submit(sentiment, opts \\ []) do
FeedbackPulse.submit(client(), sentiment, opts)
end
end
# In a Phoenix Controller
defmodule MyAppWeb.FeedbackController do
use MyAppWeb, :controller
def create(conn, %{"sentiment" => sentiment, "comment" => comment}) do
case MyApp.Feedback.submit(sentiment,
comment: comment,
screen_id: conn.request_path,
user_identifier: get_session(conn, :user_email)
) do
{:ok, %{"feedback_id" => id}} ->
json(conn, %{success: true, id: id})
{:error, reason} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{success: false, error: reason})
end
end
end
cURL
Command-line examples for testing or shell scripts.
Submit Feedback
curl -X POST https://api.fpulse.app/v1/feedback \
-H "Content-Type: application/json" \
-H "X-API-Key: fp_your_api_key_here" \
-d '{
"sentiment": "positive",
"comment": "Great product!",
"app_version": "2.1.0",
"app_type": "web",
"screen_id": "checkout"
}'
Health Check
curl https://api.fpulse.app/v1/health
Get Project Info
curl https://api.fpulse.app/v1/project/info \
-H "X-API-Key: fp_your_api_key_here"
Error Handling
All API errors follow a consistent format for easy handling in your application.
Error Response Format
{
"success": false,
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": ["optional", "array", "of", "specific", "errors"]
}
Error Codes
| HTTP Status | Code | Description |
|---|---|---|
| 400 | INVALID_BODY | Request body is not valid JSON |
| 400 | VALIDATION_ERROR | One or more fields failed validation |
| 400 | BATCH_TOO_LARGE | Batch request exceeds 100 items |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | LIMIT_EXCEEDED | Monthly feedback limit reached |
| 403 | SUBSCRIPTION_INACTIVE | Subscription has expired |
| 429 | RATE_LIMITED | Too many requests, retry after delay |
| 500 | INTERNAL_ERROR | Server error, please retry |
Best Practices
1. Use screen_id Consistently
Always include a screen_id to identify where feedback came from.
This enables powerful analytics to identify problematic screens.
2. Set Environment Correctly
Use "development" during testing and
"production" for live apps.
This keeps your production analytics clean.
3. Handle Errors Gracefully
Never let a feedback submission failure crash your app. Always wrap API calls in try-catch and fail silently if needed.
4. Use Batch for Offline Sync
For mobile apps, queue feedback locally when offline and use the batch endpoint to sync when connectivity returns.
5. Include Relevant Metadata
Add context using the metadata field -
features used, session duration, user tier, etc. This helps correlate feedback with user behavior.
6. Protect Your API Key
For web apps, proxy API calls through your backend. For mobile apps, use secure storage and consider API key rotation.
Development vs Production
Feedback Pulse supports separate environments to keep your development testing separate from real user feedback.
Development
- Use for testing and QA
- Doesn't affect production metrics
- Visible in dashboard with "Dev" filter
- Set:
"environment": "development"
Production
- Real user feedback
- Used for analytics and reports
- Default if not specified
- Set:
"environment": "production"
Tip: Toggle between Dev and Prod views in your dashboard sidebar to analyze feedback from each environment separately.
Need Help?
Can't find what you're looking for? We're here to help!