This example demonstrates how to use ApiExecutor to make custom HTTP requests to the OpenFGA API with full response details (status code, headers, raw response, typed data). This is useful when you need more control over HTTP requests or want to call endpoints that aren't yet supported by the SDK's typed API.
- List Stores - Making a GET request to list all stores
- Create Store - Making a POST request with a typed response
- Get Store - Using path parameters to get store details
- Create Authorization Model - Creating an authorization model with custom request body
- Write Tuples - Writing relationship tuples
- Read Tuples - Reading relationship tuples with filters
- Check Permission - Checking if a user has permission
- Raw JSON Response - Getting responses as raw JSON strings
- Custom Headers - Adding custom headers to requests via ClientRequestOptions
- Fluent API - Using the enhanced RequestBuilder with fluent methods
- Streaming - Stream results using
ExecuteStreamingAsyncagainst the streamed-list-objects endpoint
The SDK provides two ways to interact with the OpenFGA API:
-
Standard SDK Methods (recommended for most use cases):
var response = await client.Check(checkRequest);
-
Custom API Requests (for advanced scenarios):
var executor = client.ApiExecutor; var request = RequestBuilder<object>.Create(...); var response = await executor.ExecuteAsync<object, CheckResponse>(request, "Check"); // Now you have: response.StatusCode, response.Headers, response.RawResponse, response.Data
You can build requests using either style:
Object Initializer (compatible with existing patterns):
var request = new RequestBuilder<object> {
Method = HttpMethod.Post,
BasePath = config.ApiUrl,
PathTemplate = "/stores/{store_id}/check",
PathParameters = new Dictionary<string, string> { { "store_id", storeId } },
QueryParameters = new Dictionary<string, string>(),
Body = requestBody
};Fluent API (enhanced developer experience):
var request = RequestBuilder<object>
.Create(HttpMethod.Post, config.ApiUrl, "/stores/{store_id}/check")
.WithPathParameter("store_id", storeId)
.WithQueryParameter("timeout", "30s")
.WithBody(requestBody);- .NET 8.0 SDK or later
- Docker (for running OpenFGA server)
The easiest way to run this example:
# Show available commands
make help
# Run everything (start OpenFGA, run example, stop OpenFGA)
make run-all
# Or run step by step:
make start-openfga # Start OpenFGA in Docker
make run # Run the example
make stop-openfga # Stop OpenFGA when done-
Start OpenFGA Server:
docker run -d --name openfga-example -p 8080:8080 openfga/openfga:latest run
-
Verify OpenFGA is running:
curl http://localhost:8080/healthz # Should return: {"status":"SERVING"} -
Run the example:
dotnet run
-
Stop OpenFGA when done:
docker stop openfga-example && docker rm openfga-example
=== OpenFGA Custom API Requests Example ===
Example 1: List Stores
Making GET request to /stores
Status: OK
Is Successful: True
Found 0 store(s)
Example 2: Create Store
Making POST request to /stores
Status: Created
Store ID: 01JQWXYZ123ABC456DEF789GHJ
Store Name: ApiExecutor-Example-1738713600000
Raw Response Length: 245 chars
Example 3: Get Store Details
Making GET request to /stores/{store_id}
Status: OK
Store Name: ApiExecutor-Example-1738713600000
Created At: 2025-02-04T10:00:00Z
Response Headers: 8
Example 4: Create Authorization Model
Making POST request to /stores/{store_id}/authorization-models
Status: Created
Model ID: 01JQWXYZ789DEF123ABC456GHJ
Example 5: Write Relationship Tuples
Making POST request to /stores/{store_id}/write
Status: OK
Tuples written successfully
Example 6: Read Relationship Tuples
Making POST request to /stores/{store_id}/read
Status: OK
Found 2 tuple(s):
- user:alice is writer of document:roadmap
- user:bob is reader of document:roadmap
Example 7: Check Permission
Making POST request to /stores/{store_id}/check
Status: OK
Allowed: True
Example 8: Raw JSON Response
Getting response as raw JSON string instead of typed object
Status: OK
Raw JSON (first 100 chars): {"stores":[],"continuation_token":""}...
RawResponse and Data are the same: True
Example 9: Custom Headers
Making request with custom headers
Status: OK
Custom headers sent successfully
Response has 8 headers
Example 10: Fluent API for Request Building
Using RequestBuilder with fluent methods
Status: OK
Found 0 store(s)
Example 11: Streaming API
Streaming list-objects for a computed relation via ExecuteStreamingAsync
Created store: 01JQWXYZ...
Created model: 01JQWXYZ...
Writing tuples (1000 as owner, 1000 as viewer)...
Wrote 2000 tuples
Streaming objects via computed 'can_read' relation...
- document:1
- document:2
- document:3
- document:500
- document:1000
- document:1500
- document:2000
Streamed 2000 objects
Streaming demo store deleted
Cleanup: Delete Store
Making DELETE request to /stores/{store_id}
Status: NoContent
Store deleted successfully
=== All examples completed successfully! ===
OpenFgaClient
└── ApiExecutor (property)
└── ExecuteAsync() / ExecuteStreamingAsync()
├── Authentication (OAuth/ApiToken)
├── Retry logic with exponential backoff
├── Error handling
└── Metrics & telemetry
RequestBuilder
├── Object initializer (existing style)
└── Fluent API (enhanced style)
-
Full Response Access
response.StatusCode- HTTP status coderesponse.Headers- All response headersresponse.RawResponse- Raw JSON stringresponse.Data- Strongly-typed response objectresponse.IsSuccessful- Quick success check
-
Shared Infrastructure
- Same authentication as standard SDK methods
- Same retry logic with exponential backoff
- Same error handling and exceptions
- Same metrics and telemetry
-
Flexibility
- Call any OpenFGA endpoint
- Add custom headers via
ClientRequestOptions - Get raw JSON or strongly-typed responses
- Use path and query parameters easily
- Stream results with
ExecuteStreamingAsyncfor streaming endpoints
- The operation is available in the SDK (Check, Write, Read, etc.)
- You don't need access to response headers
- You want the simplest API
- Calling endpoints not yet in the SDK
- You need response headers or status codes
- Building custom integrations
- Need fine-grained control over requests
- Working with experimental API features
using OpenFga.Sdk.ApiClient;
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Configuration;
using OpenFga.Sdk.Model;
// Optional: Use an alias to avoid namespace/class name conflicts
using FgaApiClient = OpenFga.Sdk.ApiClient.ApiClient;
var client = new OpenFgaClient(config);
var executor = client.ApiExecutor;
var request = new RequestBuilder<object> {
Method = HttpMethod.Get,
BasePath = config.ApiUrl,
PathTemplate = "/stores",
PathParameters = new Dictionary<string, string>(),
QueryParameters = new Dictionary<string, string>()
};
var response = await executor.ExecuteAsync<object, ListStoresResponse>(
request,
"ListStores"
);
Console.WriteLine($"Status: {response.StatusCode}");
Console.WriteLine($"Stores: {response.Data.Stores.Count}");Note: If you get namespace conflicts with
ApiClient, you can use a type alias:using FgaApiClient = OpenFga.Sdk.ApiClient.ApiClient;Then use
FgaApiClientas the type in your method signatures.
var request = RequestBuilder<object>
.Create(HttpMethod.Post, config.ApiUrl, "/stores/{store_id}/check")
.WithPathParameter("store_id", storeId)
.WithQueryParameter("timeout", "30s")
.WithBody(new {
tuple_key = new { user = "user:anne", relation = "reader", @object = "doc:1" }
});
var response = await executor.ExecuteAsync<object, CheckResponse>(
request,
"Check",
new ClientRequestOptions {
Headers = new Dictionary<string, string> {
{ "X-Trace-Id", traceId }
}
}
);// When you want the raw JSON without deserialization
var response = await executor.ExecuteAsync(request, "CustomEndpoint");
string json = response.Data; // Raw JSON stringvar request = RequestBuilder<object>
.Create(HttpMethod.Post, config.ApiUrl, "/stores/{store_id}/streamed-list-objects")
.WithPathParameter("store_id", storeId)
.WithBody(new {
user = "user:anne",
relation = "can_read",
type = "document",
authorization_model_id = modelId
});
await foreach (var item in executor.ExecuteStreamingAsync<object, StreamedListObjectsResponse>(
request, "StreamedListObjects")) {
Console.WriteLine(item.Object);
}If you see connection errors:
- Verify OpenFGA is running:
curl http://localhost:8080/healthz - Check Docker logs:
docker logs openfga-example - Ensure port 8080 is not in use by another application
Make sure you have .NET 8.0 SDK installed:
dotnet --version