Support
DTO Flow help center
Practical guidance for launching partner file exchanges, helping partners succeed, connecting systems, and getting support when you need it.
Integration
Automation
Connect trusted systems when recurring files should be uploaded, checked, or collected automatically without adding manual steps for your team.
Explore API file upload for partner feedsReview security for connected workflowsPrivate Access Tokens
Private access tokens are scoped credentials for scripts, jobs, and connected partner systems. Create a token only for the feed and action the automation needs, store the raw secret right away, and revoke old tokens when the owner, server, or integration changes.
- Use one token per script, scheduled job, partner system, or deployment environment.
- Grant the smallest useful scopes, such as read-only period checks, upload, or download.
- Store the secret in a password manager, CI/CD secret, vault, or scheduler secret store.
- Rotate tokens on a planned schedule and immediately after access ownership changes.
- Review feed logs when investigating which token checked, uploaded, or downloaded files.
Treat private access tokens like passwords. DTO Flow shows the raw secret once so it can be stored securely by the system that will use it.
Upload and Download APIs
API and portal submissions follow the same feed expectations, validation, period status, and accepted-file history. Your team keeps one consistent view regardless of how a file arrives.
- Read the feed manifest to confirm expected files, extensions, upload limits, and scopes.
- Create an upload session for the target deadline period and list every file in the batch.
- Upload each file to its direct upload URL before the URL expires.
- Complete the submission so DTO Flow can match files, validate them, and update the period.
- Check the period by deadline before a downstream import or collection job starts.
- Request accepted file links and download each file before the download URLs expire.
API base URL: https://api.dtoflow.com. Send automation API requests with Authorization: Bearer <private-access-token>. The OpenAPI contract is available at https://api.dtoflow.com/api/automation/v1/openapi.json.
If your automation connects through a custom API host, use that same base URL for the OpenAPI document.
Basic File Round Trip
Start with one small, repeatable exchange: upload an expected file, complete the submission, and collect the accepted file when the period is ready.
- Upload CSV, Excel, ZIP, or other expected files from a scheduled folder.
- Check whether the current period has an accepted file before a downstream import starts.
- Download the current accepted original when it is ready.
.NET reference: the .NET tab previews DtoFlow.Integration types. The sample classes, including DtoFlowAutomationClient and CreateAutomationUploadFileRequest, illustrate the client shape. Build current integrations with the raw HTTP example and the published OpenAPI contract.
Upload a file, complete the submission, and collect accepted files
Use environment variables for secrets and deployment-specific feed settings.
using System.Globalization;
using DtoFlow.Integration;
using DtoFlow.Integration.Models;
var baseUrl = Environment.GetEnvironmentVariable("DTOFLOW_BASE_URL") ?? "https://api.dtoflow.com";
var token = Environment.GetEnvironmentVariable("DTOFLOW_TOKEN")
?? throw new InvalidOperationException("Set DTOFLOW_TOKEN.");
var feedSlug = Environment.GetEnvironmentVariable("DTOFLOW_FEED_SLUG") ?? "supplier-prices";
var filePath = args.Length > 0 ? args[0] : "prices.csv";
var deadlineAt = DateTimeOffset.Parse(
Environment.GetEnvironmentVariable("DTOFLOW_DEADLINE_AT") ?? "2026-06-01T12:00:00Z",
CultureInfo.InvariantCulture);
using var httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
var client = new DtoFlowAutomationClient(httpClient, token);
var fileInfo = new FileInfo(filePath);
try
{
var session = await client.CreateUploadSessionAsync(
feedSlug,
deadlineAt,
new[]
{
new CreateAutomationUploadFileRequest(
FeedFileSlug: null,
OriginalFileName: fileInfo.Name,
ContentType: "text/csv",
SizeBytes: fileInfo.Length)
});
using var upload = File.OpenRead(filePath);
await client.UploadFileAsync(session.Files[0], upload);
await client.CompleteUploadAsync(feedSlug, session.SubmissionId);
AutomationPeriodStatus? period = null;
for (var attempt = 0; attempt < 30; attempt++)
{
period = await client.GetPeriodStatusByDeadlineAsync(feedSlug, deadlineAt);
if (period.AcceptedFilesAvailable)
{
break;
}
if (period.Status is "ValidationFailed" or "Rejected" or "Archived")
{
throw new InvalidOperationException(
$"Submission {period.SubmissionId} finished with status {period.Status}.");
}
await Task.Delay(TimeSpan.FromSeconds(2));
}
if (period?.AcceptedFilesAvailable != true)
{
throw new TimeoutException("The accepted file was not ready within one minute.");
}
var accepted = await client.GetAcceptedFilesByDeadlineAsync(feedSlug, deadlineAt);
foreach (var acceptedFile in accepted.Files)
{
using var destination = File.Create(acceptedFile.FileName);
await client.DownloadAcceptedFileAsync(acceptedFile, destination);
Console.WriteLine($"Downloaded {acceptedFile.FileName}");
}
}
catch (DtoFlowAutomationException ex) when (ex.IsTenantAccessReadOnly)
{
Console.Error.WriteLine($"Tenant access is {ex.TenantAccessState}; required action: {ex.TenantAccessRequiredAction}.");
Environment.ExitCode = 2;
}API and .NET Reference Methods
The .NET reference illustrates common client operations. Build current integrations against the matching endpoints with raw HTTP or the published OpenAPI contract.
GetManifestAsync(fileFeedSlug)- Reads feed direction, status, upload limits, expected files, matching rules, and token scopes so each run can confirm the current setup.
GetPeriodStatusByDeadlineAsync(fileFeedSlug, deadlineAt)- Checks whether the expected period has a submission, whether validation finished, and whether accepted files are available for download.
CreateUploadSessionAsync(...)andCompleteUploadAsync(...)- Creates direct upload targets, then marks the submission ready for matching and validation after every file has been uploaded.
GetAcceptedFilesByDeadlineAsync(...)andDownloadAcceptedFileAsync(...)- Gets short-lived direct download URLs for the current accepted submission and streams each accepted file into your local destination.
Read the manifest and check period readiness
Use this before starting a downstream import or collection job.
using System.Globalization;
using System.Linq;
using DtoFlow.Integration;
var baseUrl = Environment.GetEnvironmentVariable("DTOFLOW_BASE_URL") ?? "https://api.dtoflow.com";
var token = Environment.GetEnvironmentVariable("DTOFLOW_TOKEN")
?? throw new InvalidOperationException("Set DTOFLOW_TOKEN.");
var feedSlug = Environment.GetEnvironmentVariable("DTOFLOW_FEED_SLUG") ?? "supplier-prices";
var deadlineAt = DateTimeOffset.Parse(
Environment.GetEnvironmentVariable("DTOFLOW_DEADLINE_AT") ?? "2026-06-01T12:00:00Z",
CultureInfo.InvariantCulture);
using var httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
var client = new DtoFlowAutomationClient(httpClient, token);
var manifest = await client.GetManifestAsync(feedSlug);
Console.WriteLine($"Feed: {manifest.Name} ({manifest.Slug})");
Console.WriteLine($"Direction: {manifest.Direction}; status: {manifest.Status}");
Console.WriteLine($"Token scopes: {string.Join(", ", manifest.TokenScopes)}");
foreach (var file in manifest.Files.OrderBy(file => file.SortOrder))
{
Console.WriteLine(
$"- {file.Slug}: {file.ExpectedExtension}, required={file.RequiredInSubmission}, validation={file.IsValidationEnabled}");
}
var period = await client.GetPeriodStatusByDeadlineAsync(feedSlug, deadlineAt);
if (!period.HasSubmission)
{
Console.WriteLine("No submission has been completed for this period yet.");
return;
}
if (!period.Accepted || !period.AcceptedFilesAvailable)
{
Console.WriteLine($"Submission {period.SubmissionId} is {period.Status}; validation is {period.ValidationStatus}.");
return;
}
var accepted = await client.GetAcceptedFilesByDeadlineAsync(feedSlug, deadlineAt);
Console.WriteLine($"{accepted.Files.Count} accepted file(s) are ready from {accepted.SubmissionId}.");Uploading and Downloading Multiple Files
Multi-file feeds work best when each expected file has a feed file slug. Include every file in the upload session request, upload each returned target, complete the submission once, and then download all accepted files for the period once DTO Flow accepts the submission.
- Use feed file slugs for strict expected files, such as
pricesorinventory. - Use
FeedFileSlug: nullwhen DTO Flow should match one expected file by filename, or for an allowed additional file. Prefer explicit slugs in multi-file jobs to avoid ambiguity. - Complete the submission after all uploads finish, not after each individual file.
- Download URLs are short-lived, so request fresh accepted-file links for each run.
Upload and download a multi-file feed period
The same pattern works for two files or a larger expected file set.
using System.Globalization;
using System.Linq;
using DtoFlow.Integration;
using DtoFlow.Integration.Models;
var baseUrl = Environment.GetEnvironmentVariable("DTOFLOW_BASE_URL") ?? "https://api.dtoflow.com";
var token = Environment.GetEnvironmentVariable("DTOFLOW_TOKEN")
?? throw new InvalidOperationException("Set DTOFLOW_TOKEN.");
var feedSlug = Environment.GetEnvironmentVariable("DTOFLOW_FEED_SLUG") ?? "weekly-pack";
var deadlineAt = DateTimeOffset.Parse(
Environment.GetEnvironmentVariable("DTOFLOW_DEADLINE_AT") ?? "2026-06-01T12:00:00Z",
CultureInfo.InvariantCulture);
var downloadDirectory = Environment.GetEnvironmentVariable("DTOFLOW_DOWNLOAD_DIR") ?? "accepted";
var uploadFiles = new[]
{
new { FeedFileSlug = "prices", Path = "incoming/prices.csv", ContentType = "text/csv" },
new { FeedFileSlug = "inventory", Path = "incoming/inventory.xlsx", ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
};
using var httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
var client = new DtoFlowAutomationClient(httpClient, token);
try
{
var session = await client.CreateUploadSessionAsync(
feedSlug,
deadlineAt,
uploadFiles.Select(file =>
{
var fileInfo = new FileInfo(file.Path);
return new CreateAutomationUploadFileRequest(
file.FeedFileSlug,
fileInfo.Name,
file.ContentType,
fileInfo.Length);
}).ToArray());
var uploadTargets = session.Files.ToDictionary(
file => file.OriginalFileName,
StringComparer.OrdinalIgnoreCase);
foreach (var uploadFile in uploadFiles)
{
var fileName = Path.GetFileName(uploadFile.Path);
await using var stream = File.OpenRead(uploadFile.Path);
await client.UploadFileAsync(uploadTargets[fileName], stream);
Console.WriteLine($"Uploaded {fileName}.");
}
await client.CompleteUploadAsync(feedSlug, session.SubmissionId);
var period = await client.GetPeriodStatusByDeadlineAsync(feedSlug, deadlineAt);
if (!period.AcceptedFilesAvailable)
{
Console.WriteLine($"Submission {session.SubmissionId} is {period.Status}; try download after validation completes.");
return;
}
Directory.CreateDirectory(downloadDirectory);
var accepted = await client.GetAcceptedFilesByDeadlineAsync(feedSlug, deadlineAt);
foreach (var acceptedFile in accepted.Files)
{
var destinationPath = Path.Combine(downloadDirectory, acceptedFile.FileName);
await using var destination = File.Create(destinationPath);
await client.DownloadAcceptedFileAsync(acceptedFile, destination);
Console.WriteLine($"Downloaded {destinationPath}.");
}
}
catch (DtoFlowAutomationException ex) when (ex.IsTenantAccessReadOnly)
{
Console.Error.WriteLine($"Tenant access is {ex.TenantAccessState}; required action: {ex.TenantAccessRequiredAction}.");
Environment.ExitCode = 2;
}