<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Germey</title>
    <description>The latest articles on DEV Community by Germey (@germey).</description>
    <link>https://dev.to/germey</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3834065%2F818e1a39-4e87-4a5b-b466-f76b66eb7301.png</url>
      <title>DEV Community: Germey</title>
      <link>https://dev.to/germey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/germey"/>
    <language>en</language>
    <item>
      <title>How to Build a Simple Image Editing Workflow with an HTTP API</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Wed, 21 Oct 2026 01:06:12 +0000</pubDate>
      <link>https://dev.to/germey/how-to-build-a-simple-image-editing-workflow-with-an-http-api-43lj</link>
      <guid>https://dev.to/germey/how-to-build-a-simple-image-editing-workflow-with-an-http-api-43lj</guid>
      <description>&lt;p&gt;When an image workflow starts in a design tool, the hard part is often not the edit itself. It is making the edit repeatable: accepting a prompt, preserving the input references, authenticating the request, and keeping enough task information to troubleshoot a failed run.&lt;/p&gt;

&lt;p&gt;This guide shows a small, practical workflow for editing one or more publicly accessible images over HTTP. The example uses the Nano Banana Images API, but the useful pattern is broader: send an explicit action, keep the input URLs and prompt together, and retain the task identifiers returned by the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do
&lt;/h2&gt;

&lt;p&gt;The API exposes one endpoint for image generation and editing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base URL: &lt;code&gt;https://api.acedata.cloud&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Endpoint: &lt;code&gt;POST /nano-banana/images&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Authentication: &lt;code&gt;authorization: Bearer {token}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Content type: &lt;code&gt;application/json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For editing, set &lt;code&gt;action&lt;/code&gt; to &lt;code&gt;edit&lt;/code&gt;, provide at least one item in &lt;code&gt;image_urls&lt;/code&gt;, and describe the desired change in &lt;code&gt;prompt&lt;/code&gt;. The URLs must be publicly accessible direct links. You can pass more than one image when the edit needs multiple reference materials.&lt;/p&gt;

&lt;p&gt;The request can also include &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;aspect_ratio&lt;/code&gt;, &lt;code&gt;resolution&lt;/code&gt;, and an optional &lt;code&gt;callback_url&lt;/code&gt;. The documented model options include &lt;code&gt;nano-banana&lt;/code&gt;, &lt;code&gt;nano-banana-2-lite&lt;/code&gt;, &lt;code&gt;nano-banana-2&lt;/code&gt;, &lt;code&gt;nano-banana-2.1&lt;/code&gt;, and &lt;code&gt;nano-banana-pro&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the request works
&lt;/h2&gt;

&lt;p&gt;A useful mental model is that the request has three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt; — tells the endpoint whether this is a &lt;code&gt;generate&lt;/code&gt; or &lt;code&gt;edit&lt;/code&gt; operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Materials&lt;/strong&gt; — &lt;code&gt;image_urls[]&lt;/code&gt; contains the source images for an edit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instruction&lt;/strong&gt; — &lt;code&gt;prompt&lt;/code&gt; describes what should change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The response includes &lt;code&gt;success&lt;/code&gt;, a &lt;code&gt;task_id&lt;/code&gt;, a &lt;code&gt;trace_id&lt;/code&gt;, and a &lt;code&gt;data&lt;/code&gt; array. Each successful item in &lt;code&gt;data&lt;/code&gt; contains the prompt and an &lt;code&gt;image_url&lt;/code&gt;. Keep both identifiers in your application logs. They give you a way to associate a result with the original request when a workflow has several edits in flight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make your first edit with cURL
&lt;/h2&gt;

&lt;p&gt;Start with a single reference image and a deliberately narrow instruction. A small edit is easier to evaluate than a prompt that asks for several unrelated changes at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.acedata.cloud/nano-banana/images'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'authorization: Bearer {token}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "action": "edit",
    "model": "nano-banana-pro",
    "prompt": "Replace the background with a clean, softly lit studio backdrop while keeping the subject and its proportions unchanged.",
    "image_urls": [
      "https://example.com/input.png"
    ],
    "count": 1
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;{token}&lt;/code&gt; and the example image URL with values you control. The API documentation accepts HTTP or HTTPS image URLs, although HTTPS is the safer default for production systems.&lt;/p&gt;

&lt;p&gt;A successful response has this general shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"70e6931b-6e34-43db-9e36-8765e2809d04"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"60df8d38-f265-4986-aec7-75c9220bced2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Replace the background with a clean, softly lit studio backdrop while keeping the subject and its proportions unchanged."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.acedata.cloud/assets/example.png"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat the returned &lt;code&gt;image_url&lt;/code&gt; as the artifact your next workflow step consumes. Do not assume that a request with multiple images produces one result: the API describes each requested image as an independent generation call, and &lt;code&gt;data&lt;/code&gt; contains only successful results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use multiple references without losing context
&lt;/h2&gt;

&lt;p&gt;Multiple references are useful when the edit combines materials—for example, a subject image and a separate visual reference. Put them in the intended order in &lt;code&gt;image_urls&lt;/code&gt; and make their roles explicit in the prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Keep the person from the first image, and use the second image only as the clothing reference."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image_urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/person.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/clothing.png"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important engineering detail is not the wording of the example. It is that the materials and instruction travel together as one request. That makes a job reproducible and gives you a clean record to store alongside the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make longer jobs observable
&lt;/h2&gt;

&lt;p&gt;For a synchronous prototype, waiting for the HTTP response can be enough. For a production workflow, the documentation recommends an optional &lt;code&gt;callback_url&lt;/code&gt;. The endpoint can return a &lt;code&gt;task_id&lt;/code&gt; immediately, then send the completed JSON to your publicly accessible webhook with a &lt;code&gt;POST&lt;/code&gt; request.&lt;/p&gt;

&lt;p&gt;A minimal webhook handler should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validate that the payload contains the expected &lt;code&gt;task_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;store &lt;code&gt;trace_id&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, and the returned &lt;code&gt;data&lt;/code&gt; items;&lt;/li&gt;
&lt;li&gt;record failures separately from provider safety rejections;&lt;/li&gt;
&lt;li&gt;make processing idempotent, because retries should not create duplicate downstream work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The documented error codes also give you a useful starting point for retry policy: &lt;code&gt;401 invalid_token&lt;/code&gt; is an authentication problem, &lt;code&gt;429 too_many_requests&lt;/code&gt; is a rate-limit signal, and &lt;code&gt;500 api_error&lt;/code&gt; indicates a server-side exception. A &lt;code&gt;403 forbidden&lt;/code&gt; response means the provider's safety policy rejected the call or result, so it should not be handled as a normal transient error.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical checklist
&lt;/h2&gt;

&lt;p&gt;Before wiring this into an application, check the basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the API token in server-side configuration, not in browser code.&lt;/li&gt;
&lt;li&gt;Verify every &lt;code&gt;image_urls&lt;/code&gt; value is a direct, publicly accessible link.&lt;/li&gt;
&lt;li&gt;Start with &lt;code&gt;count: 1&lt;/code&gt; while validating prompts and output handling.&lt;/li&gt;
&lt;li&gt;Persist &lt;code&gt;task_id&lt;/code&gt; and &lt;code&gt;trace_id&lt;/code&gt; with your request record.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;callback_url&lt;/code&gt; when a long-running request should not hold an open connection.&lt;/li&gt;
&lt;li&gt;Handle &lt;code&gt;invalid_token&lt;/code&gt;, &lt;code&gt;too_many_requests&lt;/code&gt;, &lt;code&gt;api_error&lt;/code&gt;, and &lt;code&gt;forbidden&lt;/code&gt; differently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a small but dependable image-editing building block: a prompt-driven operation with explicit inputs, observable execution, and a URL that can move into the next step of a pipeline. For the complete parameter reference and response details, see the &lt;a href="https://platform.acedata.cloud/documents/nano-banana-images" rel="noopener noreferrer"&gt;Nano Banana Images API documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Connect Seedance MCP to OpenCode for Terminal-Based Video Workflows</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Tue, 20 Oct 2026 01:07:01 +0000</pubDate>
      <link>https://dev.to/germey/how-to-connect-seedance-mcp-to-opencode-for-terminal-based-video-workflows-blk</link>
      <guid>https://dev.to/germey/how-to-connect-seedance-mcp-to-opencode-for-terminal-based-video-workflows-blk</guid>
      <description>&lt;p&gt;When a video task starts in a terminal, the awkward part is often context switching: leave the editor, open a separate tool, upload an image, copy the result back, and repeat. This guide shows how to connect a remote Seedance MCP server to OpenCode so a terminal session can request short videos through natural-language instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do
&lt;/h2&gt;

&lt;p&gt;The integration exposes two documented tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;seedance_generate_video&lt;/code&gt; for text-to-video generation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;seedance_generate_video_from_image&lt;/code&gt; for image-to-video generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That covers vertical short clips, product-demo animations, technical opening clips, and other cases where the first draft starts as either a description or an existing image. OpenCode connects to the remote MCP endpoint below; no local MCP server is required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://seedance.mcp.acedata.cloud/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authentication uses a Bearer token in the &lt;code&gt;Authorization&lt;/code&gt; header. Keep the real token outside the configuration file and let OpenCode resolve it from an exported environment variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the configuration works
&lt;/h2&gt;

&lt;p&gt;OpenCode reads MCP definitions from &lt;code&gt;opencode.json&lt;/code&gt;. Use &lt;code&gt;~/.config/opencode/opencode.json&lt;/code&gt; for a global setup, or &lt;code&gt;opencode.json&lt;/code&gt; in a project root for a project-specific setup. The project-level file overrides the global one, so it is a useful place to test the integration first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://opencode.ai/config.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"seedance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://seedance.mcp.acedata.cloud/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"oauth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer {env:ACEDATACLOUD_API_KEY}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;type&lt;/code&gt; declares a remote MCP connection, &lt;code&gt;url&lt;/code&gt; selects the Seedance server, and &lt;code&gt;enabled&lt;/code&gt; controls whether OpenCode loads it. The &lt;code&gt;oauth&lt;/code&gt; field must explicitly be &lt;code&gt;false&lt;/code&gt;: this server expects Bearer authentication, not an OAuth challenge. The &lt;code&gt;headers.Authorization&lt;/code&gt; value supplies the token without putting the secret in JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store the token safely
&lt;/h2&gt;

&lt;p&gt;Export the token before starting OpenCode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ACEDATACLOUD_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"replace with your real token"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not commit the real value to Git. If you keep it in a &lt;code&gt;.env&lt;/code&gt; file, exporting matters. One documented way to load exported variables into the OpenCode process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .env &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; +a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise the placeholder may resolve to an empty value. The configuration can look correct while the request still carries an incomplete header.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the MCP handshake
&lt;/h2&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;opencode mcp list
opencode mcp debug seedance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful setup should show the Seedance connection as &lt;code&gt;connected&lt;/code&gt;. If you see HTTP 401 or an SSE non-200 error, check these items in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ACEDATACLOUD_API_KEY&lt;/code&gt; is exported in the shell that launches OpenCode.&lt;/li&gt;
&lt;li&gt;The header starts with the exact &lt;code&gt;Bearer&lt;/code&gt; prefix.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;oauth&lt;/code&gt; is present and set to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separates an environment-variable problem from an authentication-mode problem and avoids putting a real secret into debug output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try a text-to-video request
&lt;/h2&gt;

&lt;p&gt;After the handshake succeeds, stay in the OpenCode session and describe the output. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate a vertical short video: a girl turns under a cherry blossom tree, slow motion, beautiful light and shadow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specify orientation, subject, movement, and lighting rather than asking for a vague “cool video.” OpenCode can then use &lt;code&gt;seedance_generate_video&lt;/code&gt; through MCP while remaining the interface for the request.&lt;/p&gt;

&lt;p&gt;A developer-focused smoke test is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate a 3-second video intro: a glowing code symbol &amp;lt;/&amp;gt; transitions from blurry to clear, with particle effects in the background, in a tech blue tone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short prompts are easier to debug before adding references or several creative constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try image-to-video
&lt;/h2&gt;

&lt;p&gt;When you already have a still image, use the image-to-video capability instead of describing every visual detail again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use this image as a cover, generate a video with the camera slowly zooming in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relevant tool is &lt;code&gt;seedance_generate_video_from_image&lt;/code&gt;. This is useful for turning a product screenshot, illustration, or existing cover into a motion asset while preserving the starting composition.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical troubleshooting checklist
&lt;/h2&gt;

&lt;p&gt;If the server does not connect, avoid changing several variables at once. Confirm the URL is exactly &lt;code&gt;https://seedance.mcp.acedata.cloud/mcp&lt;/code&gt;, verify that the token is available to the OpenCode process, and run &lt;code&gt;opencode mcp debug seedance&lt;/code&gt; again. If the connection is healthy but a generation request is not selected, use a model that supports native tool calls, since the available model and tool schema can change over time.&lt;/p&gt;

&lt;p&gt;The complete configuration and current tool-list request are in the &lt;a href="https://platform.acedata.cloud/documents/opencode-mcp-seedance" rel="noopener noreferrer"&gt;OpenCode + Seedance MCP guide&lt;/a&gt;. The practical takeaway is simple: keep credentials in the environment, keep &lt;code&gt;oauth: false&lt;/code&gt; explicit, verify the handshake first, and only then build more elaborate terminal workflows.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>tutorial</category>
      <category>devtools</category>
    </item>
    <item>
      <title>How to Configure Kimi Code CLI with an OpenAI-Compatible Provider</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Sun, 18 Oct 2026 01:07:10 +0000</pubDate>
      <link>https://dev.to/germey/how-to-configure-kimi-code-cli-with-an-openai-compatible-provider-1c4l</link>
      <guid>https://dev.to/germey/how-to-configure-kimi-code-cli-with-an-openai-compatible-provider-1c4l</guid>
      <description>&lt;p&gt;If you like coding from the terminal, the frustrating part of trying a new AI coding CLI is usually not the CLI itself. It is the provider setup: where the config file lives, which base URL to use, which model name should go where, and how to verify that the key actually works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffirdiv2x3ca661r4al5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffirdiv2x3ca661r4al5g.png" alt="Kimi Code CLI provider setup cover" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This guide walks through configuring Kimi Code CLI to use Ace Data Cloud as an OpenAI-compatible provider. The goal is practical: install the CLI, add one provider block, verify the setup, and know what to check when it fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do
&lt;/h2&gt;

&lt;p&gt;Kimi Code is a terminal coding assistant. The Ace Data Cloud setup guide shows how to point it at an OpenAI-compatible endpoint by editing &lt;code&gt;~/.kimi/config.toml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The important fields are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value or role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;acedatacloud/MODEL_ID&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;provider name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;acedatacloud&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;provider &lt;code&gt;type&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openai&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;base_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://api.acedata.cloud/v1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;api_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;your Ace Data Cloud API Key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;model table&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[models."acedatacloud/MODEL_ID"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;model parameter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model = "MODEL_ID"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The subtle part is &lt;code&gt;MODEL_ID&lt;/code&gt;: the guide says to use the model name used by the API &lt;code&gt;model&lt;/code&gt; parameter, not a service UUID or API UUID. It also recommends using the &lt;code&gt;id&lt;/code&gt; value from the &lt;code&gt;/v1/models&lt;/code&gt; response for precise validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Kimi Code reads its provider configuration from &lt;code&gt;~/.kimi/config.toml&lt;/code&gt;. By setting the provider &lt;code&gt;type&lt;/code&gt; to &lt;code&gt;openai&lt;/code&gt;, you tell Kimi Code to speak the OpenAI Chat Completions protocol to the configured &lt;code&gt;base_url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this setup, Kimi Code sends model requests to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.acedata.cloud/v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ace Data Cloud handles the provider side, while Kimi Code keeps the local developer experience: run commands in a project, ask for changes, and iterate from the terminal.&lt;/p&gt;

&lt;p&gt;You will need an API Key first. The documented path is to open the Ace Data Cloud application list, enter an available application, and copy the API Key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Kimi Code
&lt;/h2&gt;

&lt;p&gt;The guide gives three install options. On macOS or Linux, use the shell installer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://code.kimi.com/kimi-code/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://code.kimi.com/kimi-code/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you prefer npm, install the package globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @moonshot-ai/kimi-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing, the rest of the work is configuration rather than code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure the provider
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;~/.kimi/config.toml&lt;/code&gt; and add the Ace Data Cloud provider configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;default_model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"acedatacloud/MODEL_ID"&lt;/span&gt;

&lt;span class="nn"&gt;[providers.acedatacloud]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"openai"&lt;/span&gt;
&lt;span class="py"&gt;base_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://api.acedata.cloud/v1"&lt;/span&gt;
&lt;span class="py"&gt;api_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your API Key"&lt;/span&gt;

&lt;span class="nn"&gt;[models."acedatacloud/MODEL_ID"]&lt;/span&gt;
&lt;span class="py"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"acedatacloud"&lt;/span&gt;
&lt;span class="py"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MODEL_ID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace every &lt;code&gt;MODEL_ID&lt;/code&gt; with the same model name. The source guide gives examples such as &lt;code&gt;claude-opus-5&lt;/code&gt; and &lt;code&gt;gpt-6-sol&lt;/code&gt;, but the safest approach is to check the model catalog or validate against the &lt;code&gt;/v1/models&lt;/code&gt; response and use the returned &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A good final config should be internally consistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;default_model&lt;/code&gt; should point to &lt;code&gt;acedatacloud/&amp;lt;your model id&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the model table name should match that same &lt;code&gt;acedatacloud/&amp;lt;your model id&amp;gt;&lt;/code&gt; string&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;model&lt;/code&gt; field inside the table should be the raw model id&lt;/li&gt;
&lt;li&gt;the provider should remain &lt;code&gt;acedatacloud&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt; should remain &lt;code&gt;openai&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if you choose &lt;code&gt;gpt-6-sol&lt;/code&gt;, the structure would follow this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;default_model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"acedatacloud/gpt-6-sol"&lt;/span&gt;

&lt;span class="nn"&gt;[providers.acedatacloud]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"openai"&lt;/span&gt;
&lt;span class="py"&gt;base_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://api.acedata.cloud/v1"&lt;/span&gt;
&lt;span class="py"&gt;api_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your API Key"&lt;/span&gt;

&lt;span class="nn"&gt;[models."acedatacloud/gpt-6-sol"]&lt;/span&gt;
&lt;span class="py"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"acedatacloud"&lt;/span&gt;
&lt;span class="py"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gpt-6-sol"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not mix model identifiers. A surprisingly common mistake is changing &lt;code&gt;default_model&lt;/code&gt; but leaving the model table or &lt;code&gt;model&lt;/code&gt; field on an older value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock down the config file
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;~/.kimi/config.toml&lt;/code&gt; contains the API Key, protect it on macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.kimi/config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not replace secret management for teams, but it is a sensible local baseline. Avoid committing this file into a repository, and be careful when sharing terminal recordings or config snippets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the setup
&lt;/h2&gt;

&lt;p&gt;The guide suggests three verification commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kimi doctor
kimi provider list
kimi &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Reply only OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I would run them in that order. &lt;code&gt;kimi doctor&lt;/code&gt; is the first sanity check. &lt;code&gt;kimi provider list&lt;/code&gt; confirms that the provider is visible to Kimi Code. The final command is a minimal model call that should return only &lt;code&gt;OK&lt;/code&gt; if the path is working.&lt;/p&gt;

&lt;p&gt;If you get a &lt;code&gt;401&lt;/code&gt;, check the API Key. If Kimi Code reports that the model was not found, inspect the three model locations again: &lt;code&gt;default_model&lt;/code&gt;, the &lt;code&gt;[models."..."]&lt;/code&gt; table name, and the &lt;code&gt;model&lt;/code&gt; field. They need to line up with the same API model id.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from a project directory
&lt;/h2&gt;

&lt;p&gt;Once verification passes, enter a project and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kimi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the point where the setup becomes useful. You can ask the CLI to inspect files, propose changes, or help with debugging without leaving the terminal. The provider configuration stays in Kimi Code, while your project workflow stays local.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;This is a small integration, but it removes a lot of provider friction: one OpenAI-compatible &lt;code&gt;base_url&lt;/code&gt;, one &lt;code&gt;type = "openai"&lt;/code&gt; provider block, one model mapping, and three verification commands. If you are evaluating terminal coding agents, this is the kind of setup worth keeping reproducible.&lt;/p&gt;

&lt;p&gt;For the original reference, see the Ace Data Cloud Kimi Code CLI setup guide: &lt;a href="https://platform.acedata.cloud/documents/kimi-code-integration" rel="noopener noreferrer"&gt;https://platform.acedata.cloud/documents/kimi-code-integration&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devtools</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Test blank, zero and service overrides in a branded AI workspace</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Sat, 17 Oct 2026 02:13:55 +0000</pubDate>
      <link>https://dev.to/germey/test-blank-zero-and-service-overrides-in-a-branded-ai-workspace-am6</link>
      <guid>https://dev.to/germey/test-blank-zero-and-service-overrides-in-a-branded-ai-workspace-am6</guid>
      <description>&lt;p&gt;A pricing form can save successfully while the customer journey still behaves differently from what the operator intended. One easy distinction to lose is a blank service markup versus an explicit zero: they represent different configuration states.&lt;/p&gt;

&lt;p&gt;This guide is from the Ace Data Cloud team and uses the documented behavior of a white-label Studio. It provides a manual acceptance workflow for the selected site and service. Check the current configuration guide and actual checkout before applying it to a commercial offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with four separate cases
&lt;/h2&gt;

&lt;p&gt;In the site's &lt;strong&gt;Price&lt;/strong&gt; settings, the site-wide markup is the default. A service-specific markup takes priority when it is explicitly set.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service configuration&lt;/th&gt;
&lt;th&gt;Expected markup behavior&lt;/th&gt;
&lt;th&gt;What the check must distinguish&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No service override&lt;/td&gt;
&lt;td&gt;Inherit the site-wide markup&lt;/td&gt;
&lt;td&gt;The site default is the applicable setting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An override exists, but its markup field is blank&lt;/td&gt;
&lt;td&gt;Inherit the site-wide markup&lt;/td&gt;
&lt;td&gt;A blank field is not an explicit zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service markup is &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Apply no markup for that service&lt;/td&gt;
&lt;td&gt;No markup does not make the underlying service free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service markup contains an explicit percentage&lt;/td&gt;
&lt;td&gt;Use the service percentage&lt;/td&gt;
&lt;td&gt;Do not apply it on top of the site-wide percentage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Record the configuration state as well as the visible number. If the site-wide markup is also zero, two different states may produce the same price. That observation alone cannot show whether the service is inheriting the default or using an explicit override.&lt;/p&gt;

&lt;p&gt;If you need a more distinguishing configuration, use an authorized test site and record its starting settings. Change one relevant setting at a time. Avoid changing a live commercial offer just to make a test easier to observe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the same customer path
&lt;/h2&gt;

&lt;p&gt;Choose one service and a specific option within it. Enter through the site address customers will actually use, with an appropriate test account. Check the selected option, charging unit, displayed currency and price before moving to the checkout for that same purchase path.&lt;/p&gt;

&lt;p&gt;Record the checkout amount and settlement currency separately. A number on a tool page and a number on a checkout page are not comparable until the service, option, quantity, charging unit and currency are aligned. Current offers or discounts can also affect the applicable price.&lt;/p&gt;

&lt;p&gt;Keep two results separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration check:&lt;/strong&gt; Which setting should apply, and was that state saved?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer-path check:&lt;/strong&gt; What price and conditions did the intended customer actually see?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A screenshot of an administrator's form answers only part of the first question. It does not establish checkout behavior, a completed payment, actual usage or an operator reward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat removing an override as another operation
&lt;/h2&gt;

&lt;p&gt;Removing a service override restores its default display settings and makes its markup inherit the site-wide setting. That can affect more than price: an override can also hold the service's custom name, description, ordering or visibility.&lt;/p&gt;

&lt;p&gt;Record those values before changing the override. If the task is to clear just the markup field, do not substitute deletion of the whole override. After a deliberate removal, recheck the service's presentation as well as the price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep display controls separate from spending controls
&lt;/h2&gt;

&lt;p&gt;Hiding the top-up entry does not clear an existing balance or stop a customer from buying shared packages through another entry point. Likewise, hiding a tool is a display choice, not proof that API access has been revoked.&lt;/p&gt;

&lt;p&gt;Studio branding uses a shared account, balance and package system. A course budget written in a handoff note is an operating agreement unless a real spending limit has also been configured and verified. Describe only the controls you have actually checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy a compact acceptance record
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;pass&lt;/strong&gt;, &lt;strong&gt;fail&lt;/strong&gt; or &lt;strong&gt;not checked&lt;/strong&gt; for each result. Keep the record in an authorized location and share only the parts appropriate to the recipient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test time and time zone:
Site and intended customer entry:
Test account role or non-secret reference:
Service, selected option and quantity:
Site-wide markup:
Service state: absent / blank / zero / explicit percentage
Expected applicable markup and its basis:
Displayed price, charging unit and currency:
Checkout amount and settlement currency, if checked:
Saved configuration result and customer-path result:
Actual usage or order evidence, if a paid test was necessary:
Open issue, next check and owner:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect the page and purchase path before deciding whether a paid generation is necessary. If it is, confirm the current service price and agree a small test scope first. Record the actual result and usage; do not generate repeatedly just to improve a screenshot. If a submission's outcome is unknown, reconcile the original task before deciding whether another request is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route a failed check to the right evidence
&lt;/h2&gt;

&lt;p&gt;When a check fails, keep the observed result and choose the next investigation from that evidence. Use this table after the acceptance record; it is a navigation aid, not an automatic diagnosis.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you observed&lt;/th&gt;
&lt;th&gt;What to keep and check next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A saved setting looks right, but the customer price differs&lt;/td&gt;
&lt;td&gt;Keep the site address, saved state and customer observation time. Recheck the same service, option, quantity, charging unit, currency and applicable offer using the record above.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A hidden catalogue entry can still be called through an API&lt;/td&gt;
&lt;td&gt;Treat display visibility and credential access as separate checks. Use the &lt;a href="https://dev.to/germey/test-api-credential-permissions-separately-from-your-service-catalog-4hlm"&gt;credential permission matrix&lt;/a&gt; to record one intended allowed operation and one intended denied operation with an authorized test credential.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A paid test timed out or produced an unclear result&lt;/td&gt;
&lt;td&gt;Preserve the original task reference and submission time. Use the &lt;a href="https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113"&gt;sanitized support record&lt;/a&gt; to separate submission, task state and output acceptance before deciding on another request.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two API guides apply when an API credential or request is actually involved. For a browser-only Studio issue, keep the tool-page evidence and use the current support route; do not introduce an API test just to follow a link.&lt;/p&gt;

&lt;p&gt;Record what remains &lt;strong&gt;not checked&lt;/strong&gt; and who will check it next. These checks do not themselves authorize a new payment, change a customer's price or grant API access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the three markup choices
&lt;/h2&gt;

&lt;p&gt;This 45-second silent recap covers inheritance, explicit zero and a service-specific percentage:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/qBwmz5BuCwo" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=qBwmz5BuCwo" rel="noopener noreferrer"&gt;Open the recap on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the current forms and rules, use the &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-pricing?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=studio_markup_acceptance_en_20261017" rel="noopener noreferrer"&gt;Studio capability and pricing guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Customer attribution, valid orders and distributable rewards need their own evidence. A successful task does not prove that a reward is payable; refunds and other applicable adjustments can change the records. Consult the &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-attribution?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=studio_markup_acceptance_en_20261017" rel="noopener noreferrer"&gt;customer attribution and earnings guide&lt;/a&gt; when checking those outcomes.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>saas</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Connect Coze to an OpenAI Responses API Provider</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Fri, 16 Oct 2026 01:04:25 +0000</pubDate>
      <link>https://dev.to/germey/how-to-connect-coze-to-an-openai-responses-api-provider-2ao0</link>
      <guid>https://dev.to/germey/how-to-connect-coze-to-an-openai-responses-api-provider-2ao0</guid>
      <description>&lt;p&gt;If you want a private, browser-based AI workspace without rebuilding chat, user management, and model selection from scratch, Open WebUI plus an OpenAI-compatible endpoint is a practical place to start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdxkowu2xb3zhoimsatmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdxkowu2xb3zhoimsatmg.png" alt="Open WebUI connected to Ace Data Cloud" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This guide walks through a small but production-minded setup: run Open WebUI in Docker, point it at Ace Data Cloud, verify the connection outside the UI, and avoid the configuration mistakes that tend to waste the most time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you are building
&lt;/h2&gt;

&lt;p&gt;The architecture is deliberately simple. Open WebUI provides the browser interface, local conversation storage, user roles, and optional knowledge-base features. Ace Data Cloud sits behind it as an OpenAI-compatible model endpoint.&lt;/p&gt;

&lt;p&gt;The important connection values are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base URL: &lt;code&gt;https://api.acedata.cloud/v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Chat endpoint: &lt;code&gt;POST https://api.acedata.cloud/v1/chat/completions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Authentication header: &lt;code&gt;Authorization: Bearer {token}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Model discovery: &lt;code&gt;GET https://api.acedata.cloud/v1/models&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That final endpoint matters more than it may seem. Model catalogs change, so it is safer to let Open WebUI load the current list than to copy a model ID from an old setup note.&lt;/p&gt;

&lt;p&gt;There is also a path detail worth understanding up front. The base URL must match the route family you intend to use. &lt;code&gt;https://api.acedata.cloud/v1&lt;/code&gt; correctly produces &lt;code&gt;/v1/chat/completions&lt;/code&gt;. The alternative base &lt;code&gt;https://api.acedata.cloud/openai&lt;/code&gt; is also available, but appending &lt;code&gt;/v1&lt;/code&gt; to that route creates &lt;code&gt;https://api.acedata.cloud/openai/v1/chat/completions&lt;/code&gt;, which does not exist and returns 404.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Open WebUI with Docker
&lt;/h2&gt;

&lt;p&gt;Create an API token in your Ace Data Cloud application, then run the container below. Keep the token out of shell history in a real deployment—for example, load it from an environment file or secret manager rather than committing it to a repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; open-webui &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;WEBUI_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;OPENAI_API_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.acedata.cloud/v1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;token&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; open-webui:/app/backend/data &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/open-webui/open-webui:main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each setting has a narrow job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OPENAI_API_BASE_URL&lt;/code&gt; tells the client where the compatible API begins. Keep the trailing &lt;code&gt;/v1&lt;/code&gt; in this configuration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OPENAI_API_KEY&lt;/code&gt; carries your Ace Data Cloud token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WEBUI_SECRET_KEY&lt;/code&gt; protects web sessions; the command generates a fresh value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-v open-webui:/app/backend/data&lt;/code&gt; persists conversations and user data across container restarts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open &lt;code&gt;http://your-server-IP:3000&lt;/code&gt; after the container starts. The first account registered becomes the administrator, so do the initial signup yourself rather than leaving a fresh instance exposed.&lt;/p&gt;

&lt;p&gt;Inside Open WebUI, go to &lt;strong&gt;Admin Panel → Settings → Connections&lt;/strong&gt; and use &lt;strong&gt;Verify Connection&lt;/strong&gt;. Then open &lt;strong&gt;Settings → Models&lt;/strong&gt; to filter or pin the models you actually want users to see. You can also add the same Base URL and API key directly through the Connections interface instead of environment variables; Open WebUI will call &lt;code&gt;/models&lt;/code&gt; to populate the catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the API before debugging the UI
&lt;/h2&gt;

&lt;p&gt;When a chat interface says only “connection failed,” it hides too many possible causes: a malformed base URL, an invalid token, an unavailable model, or a network problem. A direct request separates the API layer from the UI layer.&lt;/p&gt;

&lt;p&gt;First retrieve the current model list, choose an ID returned there, and replace &lt;code&gt;MODEL_ID&lt;/code&gt; below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.acedata.cloud/v1/chat/completions'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer {token}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "MODEL_ID",
    "messages": [
      {"role": "user", "content": "ping"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful request returns an OpenAI-compatible &lt;code&gt;chat.completion&lt;/code&gt; object. If you receive &lt;code&gt;HTTP 403 used_up&lt;/code&gt;, the token is valid but its balance is insufficient. If you receive a 404, inspect the URL first—especially the common accidental &lt;code&gt;/openai/v1&lt;/code&gt; combination.&lt;/p&gt;

&lt;p&gt;This is the debugging order I prefer as a builder: prove the token and route with curl, verify the connection in the admin panel, and only then investigate model or browser behavior. It avoids changing several layers at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the setup safer for a small team
&lt;/h2&gt;

&lt;p&gt;Open WebUI supports multiple users, but a public registration screen can also turn into unplanned API usage. In &lt;strong&gt;Admin Panel → Users&lt;/strong&gt;, set the default user role to &lt;code&gt;pending&lt;/code&gt; so each new account requires review. Available roles include Pending, User, and Admin.&lt;/p&gt;

&lt;p&gt;For document chat, Open WebUI uses ChromaDB for vector storage by default. The documented embedding option is &lt;code&gt;text-embedding-3-large&lt;/code&gt;. Original documents remain on your server, while matched segments are sent to the model during retrieval. If uploaded documents do not produce chat results, check the embedding model in the RAG settings before assuming the chat endpoint is broken.&lt;/p&gt;

&lt;p&gt;If you place nginx in front of the container, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;proxy_buffering&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;100M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first setting avoids buffered streaming behavior, while the second allows larger uploads. And do not skip the Docker volume: without &lt;code&gt;/app/backend/data&lt;/code&gt; persistence, a container replacement can take conversations and local user state with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short troubleshooting checklist
&lt;/h2&gt;

&lt;p&gt;Before changing the deployment, check these in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is the base URL exactly &lt;code&gt;https://api.acedata.cloud/v1&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does a direct request to &lt;code&gt;/v1/chat/completions&lt;/code&gt; work with the same token?&lt;/li&gt;
&lt;li&gt;Is the selected model present in the current &lt;code&gt;/v1/models&lt;/code&gt; response?&lt;/li&gt;
&lt;li&gt;Is the persistent volume mounted?&lt;/li&gt;
&lt;li&gt;For RAG, is &lt;code&gt;text-embedding-3-large&lt;/code&gt; selected as the embedding model?&lt;/li&gt;
&lt;li&gt;For team access, are new users held in the &lt;code&gt;pending&lt;/code&gt; role?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The useful part of an OpenAI-compatible interface is not that every client becomes identical; it is that you can test one clean HTTP boundary and then choose the interface that fits your team. Keep that boundary observable, start with the smallest deployment, and use the full &lt;a href="https://platform.acedata.cloud/documents/development_open_webui" rel="noopener noreferrer"&gt;Ace Data Cloud Open WebUI setup reference&lt;/a&gt; when you need to revisit the exact connection rules.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>tutorial</category>
      <category>devtools</category>
    </item>
    <item>
      <title>A Practical Guide to Building Multi-Track Music Workflows with the Suno Projects API</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Thu, 15 Oct 2026 01:05:12 +0000</pubDate>
      <link>https://dev.to/germey/a-practical-guide-to-building-multi-track-music-workflows-with-the-suno-projects-api-5aee</link>
      <guid>https://dev.to/germey/a-practical-guide-to-building-multi-track-music-workflows-with-the-suno-projects-api-5aee</guid>
      <description>&lt;p&gt;If you have ever tried to turn a single generated audio clip into an editable arrangement, you have probably hit the same wall: one file is easy to play, but hard to iterate on.&lt;/p&gt;

&lt;p&gt;The Suno Studio Projects API gives you a project-level workflow for that next step: create a project, add source material, render a reference mix, generate new track candidates, and commit the one that fits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do
&lt;/h2&gt;

&lt;p&gt;The core endpoint is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /suno/projects
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Idempotency-Key: &amp;lt;a new UUID for each write operation&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API is action-based. Every request sends an &lt;code&gt;action&lt;/code&gt; field, and the same endpoint handles project operations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;create&lt;/code&gt; — create an empty project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retrieve&lt;/code&gt; — read the latest project state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;save&lt;/code&gt; — save the complete project state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;upload&lt;/code&gt; — create an asset from an HTTPS audio URL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;add_track&lt;/code&gt; — place completed audio into the project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;render&lt;/code&gt; — export the current audible mix and get a reference &lt;code&gt;audio_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generate_track&lt;/code&gt; — generate new track candidates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replace_section&lt;/code&gt; — generate candidates for a partial replacement&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;commit_candidate&lt;/code&gt; — commit a selected candidate into the project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remove_track&lt;/code&gt; — remove a track&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most write operations require an &lt;code&gt;Idempotency-Key&lt;/code&gt; header. The exception is &lt;code&gt;retrieve&lt;/code&gt;. That matters in real integrations: if a paid generation times out or the client disconnects, you should query the original task or reuse the same idempotency key instead of blindly submitting a different request.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the workflow fits together
&lt;/h2&gt;

&lt;p&gt;A project workflow is mostly about passing the right IDs forward.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;create&lt;/code&gt; response gives you &lt;code&gt;data.id&lt;/code&gt;, which becomes the project &lt;code&gt;id&lt;/code&gt; for later calls. After the first &lt;code&gt;save&lt;/code&gt; or any modifying operation, you also get a &lt;code&gt;version_id&lt;/code&gt;. Use the latest &lt;code&gt;version_id&lt;/code&gt; when modifying, generating candidates, or exporting.&lt;/p&gt;

&lt;p&gt;When you upload audio, the completed task returns &lt;code&gt;response.data.candidate.audio_id&lt;/code&gt;. That is the ID you use with &lt;code&gt;add_track&lt;/code&gt;; do not pass the original audio URL into &lt;code&gt;add_track&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For new generated parts, there are two more IDs to track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;render_audio_id&lt;/code&gt; comes from the final &lt;code&gt;render&lt;/code&gt; task response as &lt;code&gt;response.data.audio_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;operation_id&lt;/code&gt; and &lt;code&gt;candidate_id&lt;/code&gt; come from the final generation task and are used when committing a candidate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One subtle but important detail: time can mean two different things. &lt;code&gt;start_seconds&lt;/code&gt; and &lt;code&gt;end_seconds&lt;/code&gt; refer to seconds inside the source audio. &lt;code&gt;start_beats&lt;/code&gt; and &lt;code&gt;end_beats&lt;/code&gt; refer to beats on the project timeline. The default &lt;code&gt;state.timing.bps&lt;/code&gt; is &lt;code&gt;2&lt;/code&gt;, so a 41-second clip starting at beat &lt;code&gt;0&lt;/code&gt; ends at beat &lt;code&gt;82&lt;/code&gt; by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: create and save a project
&lt;/h2&gt;

&lt;p&gt;Start with a small project object. A newly created project has no &lt;code&gt;version_id&lt;/code&gt;, so the first save can omit it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://api.acedata.cloud"&lt;/span&gt;

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/suno/projects"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Idempotency-Key: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "action": "create",
    "title": "Demo arrangement"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a project &lt;code&gt;id&lt;/code&gt; and an initial &lt;code&gt;state&lt;/code&gt;, typically with empty &lt;code&gt;tracks&lt;/code&gt; and &lt;code&gt;timing.bps&lt;/code&gt; set to &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then save the complete state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"save"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"af0e104a-2a4c-40e0-9c70-7614566824a6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Demo arrangement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tracks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this point, keep the returned &lt;code&gt;version_id&lt;/code&gt;. Old versions can be rejected, so do not cache a project state and edit it much later without retrieving the latest copy first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: upload audio and add it as a track
&lt;/h2&gt;

&lt;p&gt;To bring source material into a project, upload from an HTTPS URL that is publicly accessible and that you have the right to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"upload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"af0e104a-2a4c-40e0-9c70-7614566824a6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8f0c62fb-304f-420e-9707-426e8b64b5e3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"audio_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;HTTPS_AUDIO_URL&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"async"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Async operations return a &lt;code&gt;task_id&lt;/code&gt; first. That only means the task was accepted. Poll the task endpoint until the task has &lt;code&gt;finished_at&lt;/code&gt; and &lt;code&gt;response.success&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /suno/tasks
Content-Type: application/json

{"action":"retrieve","id":"&amp;lt;task_id&amp;gt;"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When upload completes, take &lt;code&gt;candidate.audio_id&lt;/code&gt; and add it to the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add_track"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"af0e104a-2a4c-40e0-9c70-7614566824a6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8f0c62fb-304f-420e-9707-426e8b64b5e3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"audio_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b21be571-bfe4-47c1-b59c-2abc55c6f9c3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Source demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start_beats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional fields such as &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;gain&lt;/code&gt;, &lt;code&gt;start_beats&lt;/code&gt;, and &lt;code&gt;end_beats&lt;/code&gt; let you control placement and volume. The default color is &lt;code&gt;#7251F7&lt;/code&gt;, and &lt;code&gt;gain&lt;/code&gt; defaults to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: generate a new track candidate
&lt;/h2&gt;

&lt;p&gt;For &lt;code&gt;generate_track&lt;/code&gt;, first save an empty target track in the complete project state. At minimum, the target track needs an &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;type: "audio"&lt;/code&gt;, and &lt;code&gt;clips: []&lt;/code&gt;. A fuller empty track can include &lt;code&gt;takeLanes&lt;/code&gt;, &lt;code&gt;mute&lt;/code&gt;, &lt;code&gt;solo&lt;/code&gt;, &lt;code&gt;amplitude&lt;/code&gt;, &lt;code&gt;instrument&lt;/code&gt;, and &lt;code&gt;color&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, render the current audible mix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"render"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"af0e104a-2a4c-40e0-9c70-7614566824a6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cb0a82c9-aa5b-48cc-9395-0528675fc18d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Demo source mix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lyrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[Instrumental]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"async"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the render task succeeds, use its &lt;code&gt;audio_id&lt;/code&gt; as &lt;code&gt;render_audio_id&lt;/code&gt; in &lt;code&gt;generate_track&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"generate_track"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"af0e104a-2a4c-40e0-9c70-7614566824a6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cb0a82c9-aa5b-48cc-9395-0528675fc18d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_audio_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b21be571-bfe4-47c1-b59c-2abc55c6f9c3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"render_audio_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"41038732-4dc4-40bf-b00d-2728d99bc26e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chirp-v5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"end_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stem_control_tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add Piano"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"batch_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instrumental"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warm piano, cinematic"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supported model names include &lt;code&gt;chirp-v3-5&lt;/code&gt;, &lt;code&gt;chirp-v4&lt;/code&gt;, &lt;code&gt;chirp-v4-5&lt;/code&gt;, &lt;code&gt;chirp-v4-5-plus&lt;/code&gt;, &lt;code&gt;chirp-v5&lt;/code&gt;, &lt;code&gt;chirp-v5-5&lt;/code&gt;, &lt;code&gt;chirp-v6&lt;/code&gt;, &lt;code&gt;chirp-v6-wild&lt;/code&gt;, and &lt;code&gt;chirp-v6-mini&lt;/code&gt;. Availability for a specific combination is ultimately reflected in the final task state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: commit the candidate you actually want
&lt;/h2&gt;

&lt;p&gt;Generation does not modify the project immediately. It gives you candidates. After listening or inspecting results, use &lt;code&gt;commit_candidate&lt;/code&gt; with the returned &lt;code&gt;operation_id&lt;/code&gt;, selected &lt;code&gt;candidate_id&lt;/code&gt;, and target &lt;code&gt;track_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That separation is useful: your app can present several takes to a user, let them choose one, and only then update the project version.&lt;/p&gt;

&lt;p&gt;In practice, I would wrap this workflow in a small state machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store &lt;code&gt;project_id&lt;/code&gt; and latest &lt;code&gt;version_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Store every async &lt;code&gt;task_id&lt;/code&gt; before polling.&lt;/li&gt;
&lt;li&gt;On timeout, retrieve the task instead of resubmitting.&lt;/li&gt;
&lt;li&gt;After any successful modifying operation, replace the stored &lt;code&gt;version_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Before editing &lt;code&gt;state&lt;/code&gt;, retrieve the full project and preserve unknown fields.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last point prevents accidental data loss. &lt;code&gt;save&lt;/code&gt; expects the complete project state, not a partial patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;The useful mental model is: treat &lt;code&gt;/suno/projects&lt;/code&gt; less like a single generation endpoint and more like an arrangement workspace. You create durable project state, attach source material, ask for candidates, and commit only the result that belongs in the mix.&lt;/p&gt;

&lt;p&gt;If you are building a tool where users iterate on stems, intros, backing tracks, or replacements, this project-based flow is much easier to reason about than repeatedly juggling standalone audio files.&lt;/p&gt;

&lt;p&gt;Full reference: &lt;a href="https://platform.acedata.cloud/documents/suno-projects-integration" rel="noopener noreferrer"&gt;https://platform.acedata.cloud/documents/suno-projects-integration&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>tutorial</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Check DNS and TLS before sending an API key to a new hostname</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Wed, 14 Oct 2026 18:24:00 +0000</pubDate>
      <link>https://dev.to/germey/check-dns-and-tls-before-sending-an-api-key-to-a-new-hostname-288f</link>
      <guid>https://dev.to/germey/check-dns-and-tls-before-sending-an-api-key-to-a-new-hostname-288f</guid>
      <description>&lt;p&gt;Before a customer's first API request, verify the hostname separately from the service operation. A branded portal's website domain and API domain can have different jobs. A working homepage establishes only part of the path.&lt;/p&gt;

&lt;p&gt;This guide uses a small Python probe to check DNS resolution and a verified TLS connection before sending any API credential. It sends &lt;strong&gt;no HTTP request&lt;/strong&gt; and has no input for an API key.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this is a practical guide from the Ace Data Cloud team. The setup notes reference our current public documentation; the probe uses Python's standard library and can be applied to an HTTPS hostname you are authorized to check.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the intended API hostname
&lt;/h2&gt;

&lt;p&gt;Keep these two addresses separate in your acceptance record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;th&gt;Intended role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;portal.example.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The website where customers read documentation and manage their account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;api.example.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The hostname their program uses for documented API requests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are reserved example names. Replace them with the actual addresses from your configuration.&lt;/p&gt;

&lt;p&gt;For Ace Data Cloud, the page-domain and API-domain controls each provide their own CNAME target. Copy the target from the correct control in the current console. Do not copy an old screenshot or replace an existing DNS record without checking what currently uses it. DNS resolution and HTTPS activation are separate checks.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-api-domains?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=dns_tls_preflight_en_20261015" rel="noopener noreferrer"&gt;current domain setup guide&lt;/a&gt; covers those controls. This article focuses on the next client-side check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the DNS and TLS preflight in 45 seconds
&lt;/h2&gt;

&lt;p&gt;This silent video uses English on-screen text and English captions to walk through the checks below. It separates DNS, connection and TLS checks from the later HTTP route and credential-permission tests.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/u1ucToqM1aA" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=u1ucToqM1aA" rel="noopener noreferrer"&gt;Watch the DNS and TLS checklist on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep the actual failure stage and the client used for the check. A verified TLS connection does not prove that the expected application is there or that an API operation is authorized. The Python probe below sends no HTTP request and accepts no API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Probe DNS and TLS without a key
&lt;/h2&gt;

&lt;p&gt;Save the following as &lt;code&gt;api_hostname_preflight.py&lt;/code&gt;. It uses Python 3 and the runtime's normal trust store. The argument is a bare hostname: no scheme, port, path, query string or credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Resolve a hostname and verify TLS; send no HTTP request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hostname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bare hostname, without scheme, port, path or credentials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/:@?#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isspace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pass a hostname only, not a URL, port or credential&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;idna&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ascii&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;UnicodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hostname could not be converted to an ASCII DNS name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hostname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;started_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getaddrinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resolved_addresses&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;connect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_default_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_connection&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap_socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;certificate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getpeercert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tls_verified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connected_peer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getpeername&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                              &lt;span class="n"&gt;tls_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                              &lt;span class="n"&gt;certificate_not_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notAfter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SSLCertVerificationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;certificate_rejected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;verification_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;probe_failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checked_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tls_verified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it against the API hostname you intend to configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 api_hostname_preflight.py api.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code opens port 443, verifies the certificate using the configured trust store, and closes the connection. It does not send a request path, an &lt;code&gt;Authorization&lt;/code&gt; header, a cookie or a generation request.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssl.create_default_context()&lt;/code&gt; enables certificate verification. Passing &lt;code&gt;server_hostname=host&lt;/code&gt; lets TLS use the hostname for SNI and hostname verification. Do not replace this with an unverified context to make a failing check appear successful.&lt;/p&gt;

&lt;p&gt;The five-second timeout applies to connection and TLS socket operations. Operating-system DNS resolution has its own behavior, and trying multiple addresses can make the total run take longer than five seconds. This is a small diagnostic probe, not a full monitoring agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the result at the right level
&lt;/h2&gt;

&lt;p&gt;The result is JSON, with UTC timestamps and a clear stage:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;th&gt;What to check next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tls_verified&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;This client resolved the name and accepted a TLS connection for it using this runtime's trust configuration&lt;/td&gt;
&lt;td&gt;The intended application route and later authenticated API behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;certificate_rejected&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Certificate verification failed&lt;/td&gt;
&lt;td&gt;The hostname, certificate validity and chain, local clock and trust configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;probe_failed&lt;/code&gt;, stage &lt;code&gt;dns&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The system resolver did not return usable address information&lt;/td&gt;
&lt;td&gt;The configured name, DNS records and the client's resolver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;probe_failed&lt;/code&gt;, stage &lt;code&gt;connect&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Address resolution completed, but connection setup or its prerequisites failed&lt;/td&gt;
&lt;td&gt;Reachability, port 443, local network policy and client configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;probe_failed&lt;/code&gt;, stage &lt;code&gt;tls&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;A connection was made, but the TLS operation failed or timed out&lt;/td&gt;
&lt;td&gt;TLS configuration and the network path; this is not an API-key response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argument error, exit code 2&lt;/td&gt;
&lt;td&gt;The input was not a supported hostname&lt;/td&gt;
&lt;td&gt;Remove the scheme, port, path or credentials before probing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On success, &lt;code&gt;resolved_addresses&lt;/code&gt; records the local resolver's answers, while &lt;code&gt;connected_peer&lt;/code&gt; records the connection actually selected. The connection helper may resolve the name again, so those observations can differ. A successful connection checks one selected address; it does not certify every A or AAAA record.&lt;/p&gt;

&lt;p&gt;This is also not an authoritative DNS lookup or a check of the entire CNAME chain. Resolvers, proxies and trust stores can change what a particular machine observes. Some networks synthesize addresses even for names that do not exist, which can move an eventual failure from the DNS stage to the connection or TLS stage. Preserve the observed stage instead of assigning an expected one. Compare the configured CNAME with the current setup instructions separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep routing and authorization as separate tests
&lt;/h2&gt;

&lt;p&gt;A TLS pass establishes neither the right application route nor access to a service. A different application can present a valid certificate for the hostname. The probe intentionally makes no HTTP request, so it cannot identify that application.&lt;/p&gt;

&lt;p&gt;After the API-domain status is ready, inspect the documented Base URL and request path. In Ace Data Cloud's Usage and Testing area, &lt;strong&gt;View Code&lt;/strong&gt; helps you inspect the selected API example before running it. An example containing a real key should stay private. Manually maintained documentation needs its own address review; do not assume an old hard-coded host was rewritten automatically.&lt;/p&gt;

&lt;p&gt;A later HTTP response is a new piece of evidence. For example, a route returning 404 or an authenticated request returning 401 should be investigated at that layer. Neither response proves that DNS is broken. Preserve the service's actual status and sanitized error rather than relabelling it.&lt;/p&gt;

&lt;p&gt;A branded API hostname also does not create credential isolation. The &lt;a href="https://dev.to/germey/test-api-credential-permissions-separately-from-your-service-catalog-4hlm"&gt;credential-permission guide&lt;/a&gt; covers allowlists and allowed-versus-rejected operation tests separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hand off a small, useful record
&lt;/h2&gt;

&lt;p&gt;Keep the probe output with the intended hostname, the network it was run from, and the expected API configuration. Before sharing it, check whether hostnames or network addresses disclose information that belongs in a private support channel.&lt;/p&gt;

&lt;p&gt;A concise handoff can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intended API hostname:
Source of expected host / domain configuration:
Client and network used for the check:
Started and checked at (UTC):
DNS answers and connected peer, if appropriate to share:
TLS result and certificate expiry, or exact failed stage:
HTTP route check: not performed / observed result:
Authenticated operation: not performed / observed result:
Remaining uncertainty:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not attach a full environment dump or a credential-bearing command. Use the &lt;a href="https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113"&gt;support-report guide&lt;/a&gt; to prepare any additional logs or screenshots.&lt;/p&gt;

&lt;p&gt;Once these transport checks and the intended route are understood, select one documented, small API operation with an authorized test customer's own credential. Confirm its current cost, inspect the actual response and usage record, and preserve any original asynchronous task ID before considering a retry. That final operation is a separate acceptance step; the DNS/TLS probe has not performed it for you.&lt;/p&gt;

</description>
      <category>api</category>
      <category>security</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Poll Async Image Tasks with the Flux Tasks API</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Wed, 14 Oct 2026 01:11:23 +0000</pubDate>
      <link>https://dev.to/germey/how-to-poll-async-image-tasks-with-the-flux-tasks-api-1kb5</link>
      <guid>https://dev.to/germey/how-to-poll-async-image-tasks-with-the-flux-tasks-api-1kb5</guid>
      <description>&lt;p&gt;Async image generation needs two separate operations: creating the work and checking the task that was already created. Save the returned platform task ID so a slow response or a page refresh does not turn into an accidental second generation request.&lt;/p&gt;

&lt;p&gt;This guide from Ace Data Cloud covers the Flux Tasks API for an existing image-generation task. The same task endpoint also supports platform task IDs returned by Flux video generation. The request and response examples below are illustrative; they are not results from a live call made for this article.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Updated October 18, 2026: restored the missing endpoint names, field names and code examples.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq3us3vs2gybz0h67c6p0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq3us3vs2gybz0h67c6p0.png" alt="Ace Data Cloud Flux Tasks API cover" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can query
&lt;/h2&gt;

&lt;p&gt;Use the task ID returned by the original generation request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base URL:&lt;/strong&gt; &lt;code&gt;https://api.acedata.cloud&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint:&lt;/strong&gt; &lt;code&gt;POST /flux/tasks&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; &lt;code&gt;authorization: Bearer {token}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headers:&lt;/strong&gt; &lt;code&gt;accept: application/json&lt;/code&gt; and &lt;code&gt;content-type: application/json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single task action:&lt;/strong&gt; &lt;code&gt;retrieve&lt;/code&gt;, with an &lt;code&gt;id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch action:&lt;/strong&gt; &lt;code&gt;retrieve_batch&lt;/code&gt;, with an &lt;code&gt;ids&lt;/code&gt; array&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are retrieval operations even though the endpoint uses HTTP POST. Keep them separate from a generation request. Use an authorized token and an existing task ID; the placeholders in this article will not retrieve a real task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieve one task
&lt;/h2&gt;

&lt;p&gt;Replace &lt;code&gt;{token}&lt;/code&gt; and &lt;code&gt;YOUR_EXISTING_TASK_ID&lt;/code&gt; in your own authorized environment. Avoid publishing command output that contains credentials, customer inputs or private result links.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--max-time&lt;/span&gt; 20 &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.acedata.cloud/flux/tasks'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'authorization: Bearer {token}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "id": "YOUR_EXISTING_TASK_ID",
    "action": "retrieve"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following shortened example illustrates a completed task record. In the maintained reference, the original request is in &lt;code&gt;request&lt;/code&gt;, and the generation result is nested in &lt;code&gt;response&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_EXISTING_TASK_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1736304669.779&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"started_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1736304669.839&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"finished_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1736304679.439&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"elapsed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"generate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1024x1024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a white siamese cat"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EXAMPLE_TRACE_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_EXISTING_TASK_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.acedata.cloud/e724d7f13d.png"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reference documents these timing fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt;: creation time, as a Unix timestamp in seconds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;started_at&lt;/code&gt;: execution start time, as a Unix timestamp in seconds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finished_at&lt;/code&gt;: completion time; omitted when the task has not completed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;elapsed&lt;/code&gt;: execution duration in seconds; omitted when the task has not completed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;finished_at&lt;/code&gt; marks completion, not necessarily a successful usable image. Inspect the nested &lt;code&gt;response&lt;/code&gt; and its success or error information. For this successful image example, the output location is &lt;code&gt;response.data[].image_url&lt;/code&gt;. An HTTP 200 response from the lookup alone does not establish that generation succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep enough state to resume the lookup
&lt;/h2&gt;

&lt;p&gt;A small application record can retain the platform task &lt;code&gt;id&lt;/code&gt;, the relevant internal job reference, the last confirmed state, the query time and &lt;code&gt;trace_id&lt;/code&gt; when available. Store task timing and the final &lt;code&gt;response&lt;/code&gt; when returned, with access controls appropriate to its contents.&lt;/p&gt;

&lt;p&gt;Keep a reference to the original input in an authorized store when it is needed for support. A task record can contain the original prompt and output links; copying its entire body into a public ticket is unnecessary.&lt;/p&gt;

&lt;p&gt;Distinguish the task's last observed state from the state of the poller. For example, “last observed pending; lookup timed out” means the latest observation is unavailable. It does not establish that the task failed or was cancelled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieve a batch of existing tasks
&lt;/h2&gt;

&lt;p&gt;For a dashboard or recovery worker, send multiple task IDs using &lt;code&gt;retrieve_batch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--max-time&lt;/span&gt; 20 &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.acedata.cloud/flux/tasks'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'authorization: Bearer {token}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "ids": ["YOUR_FIRST_EXISTING_TASK_ID", "YOUR_SECOND_EXISTING_TASK_ID"],
    "action": "retrieve_batch"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The batch response has an &lt;code&gt;items&lt;/code&gt; array and a &lt;code&gt;count&lt;/code&gt;. Each returned item follows the task-record structure used for a single lookup. Match items to your saved jobs by &lt;code&gt;id&lt;/code&gt;; keep each job's state separate.&lt;/p&gt;

&lt;p&gt;A backend worker can consolidate polling so every browser tab does not independently query the same task. Choose a polling interval, per-query timeout, total observation window and maximum query count appropriate to the application. Those are your monitoring limits, not a request to cancel the generation task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle lookup errors separately from generation results
&lt;/h2&gt;

&lt;p&gt;The reference lists these HTTP statuses and error codes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;HTTP status&lt;/th&gt;
&lt;th&gt;Error code&lt;/th&gt;
&lt;th&gt;Next check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;&lt;code&gt;token_mismatched&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check required parameters and the request shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api_not_implemented&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check the supported path and action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;td&gt;&lt;code&gt;invalid_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check the authorized token used for the lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429&lt;/td&gt;
&lt;td&gt;&lt;code&gt;too_many_requests&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Slow down subsequent queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api_error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retain the diagnostic reference and investigate the query failure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An illustrative error body is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api_error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fetch failed"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EXAMPLE_TRACE_ID"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record whether an error came from the lookup request or from the stored generation &lt;code&gt;response&lt;/code&gt;. A lookup error by itself does not determine the generation outcome. After a query timeout or rate limit, preserve the existing task ID and use a bounded retrieval policy. Do not automatically resubmit generation just because observation stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  A polling workflow to review
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Persist the task ID returned by the original generation request.&lt;/li&gt;
&lt;li&gt;Query &lt;code&gt;/flux/tasks&lt;/code&gt; using &lt;code&gt;action: "retrieve"&lt;/code&gt; and that same &lt;code&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the returned record has no &lt;code&gt;finished_at&lt;/code&gt;, retain the pending observation and query again within the monitoring limits.&lt;/li&gt;
&lt;li&gt;When completion is recorded, inspect &lt;code&gt;response&lt;/code&gt; for success or failure and retain the relevant result.&lt;/li&gt;
&lt;li&gt;When a query fails or the observation window ends, record that condition separately and keep the original ID for reconciliation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For batch processing, use &lt;code&gt;retrieve_batch&lt;/code&gt; while keeping the same per-task checks. Consult the maintained &lt;a href="https://platform.acedata.cloud/documents/flux-tasks-integration" rel="noopener noreferrer"&gt;Flux Tasks API integration guide&lt;/a&gt; for the complete request and response examples.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>tutorial</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Test API credential permissions separately from your service catalog</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Tue, 13 Oct 2026 12:40:50 +0000</pubDate>
      <link>https://dev.to/germey/test-api-credential-permissions-separately-from-your-service-catalog-4hlm</link>
      <guid>https://dev.to/germey/test-api-credential-permissions-separately-from-your-service-catalog-4hlm</guid>
      <description>&lt;p&gt;A branded API portal has two separate questions to answer: which services should customers discover, and which operations should a particular credential be allowed to call?&lt;/p&gt;

&lt;p&gt;Treat those as separate acceptance tests. A screenshot of a tidy service catalog cannot establish the permissions of an API key.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this guide uses Ace Data Cloud's current white-label catalog and credential documentation. It is written by the Ace Data Cloud team. The examples below are test designs, not reported customer results.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the three permission cases
&lt;/h2&gt;

&lt;p&gt;This 40-second visual guide compares catalogue visibility with credential scope. It is silent, with English on-screen text and English and Simplified Chinese subtitles available in the player settings.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/_HKFYhdmUQE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=_HKFYhdmUQE" rel="noopener noreferrer"&gt;Watch the permission checklist on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The three fictional cases are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A:&lt;/strong&gt; visible in the catalogue and inside the intended credential scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B:&lt;/strong&gt; visible in the catalogue and outside the intended credential scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C:&lt;/strong&gt; hidden from the catalogue but still inside the intended credential scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are test setups, not observed API results. Hold the other prerequisites valid and record the actual response; a hidden catalogue entry does not establish that access was revoked, and a generic error does not establish an allowlist denial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record the boundary each setting controls
&lt;/h2&gt;

&lt;p&gt;In the current portal, service visibility and documentation visibility are configured separately. Hiding a service does not automatically hide its documentation. A saved detail link may still open even when its directory entry is hidden.&lt;/p&gt;

&lt;p&gt;Credential restrictions require their own configuration and evidence:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting or check&lt;/th&gt;
&lt;th&gt;What it establishes&lt;/th&gt;
&lt;th&gt;What to verify separately&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Service catalog visibility&lt;/td&gt;
&lt;td&gt;Which service entries appear in the catalog&lt;/td&gt;
&lt;td&gt;The credential's permitted API operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation visibility&lt;/td&gt;
&lt;td&gt;Which documentation entries or subtrees appear&lt;/td&gt;
&lt;td&gt;Whether an existing direct link is accessible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allowed_api_ids&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The configured API allowlist for that credential&lt;/td&gt;
&lt;td&gt;An allowed request and an appropriately designed rejected request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expired_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The configured expiry time&lt;/td&gt;
&lt;td&gt;Behavior after expiry, where safely testable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;limited_amount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The configured credential usage limit&lt;/td&gt;
&lt;td&gt;The documented unit, current usage and enforcement behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These settings do not create a separate customer database or billing system for each branded site. A customer's existing balance also does not disappear when a purchase button is hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check what an empty allowlist means
&lt;/h2&gt;

&lt;p&gt;The current credential-creation documentation says that &lt;code&gt;allowed_api_ids&lt;/code&gt; accepts API UUIDs and that &lt;strong&gt;an empty array is normalized to no restriction&lt;/strong&gt;. It is therefore unsafe to use &lt;code&gt;[]&lt;/code&gt; as shorthand for “deny every API.” The documented default also allows an unrestricted value.&lt;/p&gt;

&lt;p&gt;A non-empty list should contain the actual API IDs you intend to allow. Obtain them through the documented API catalog for the relevant service. Do not substitute model names, route strings, display titles or an Application ID.&lt;/p&gt;

&lt;p&gt;“No restriction” here describes this particular credential filter. It does not waive service acquisition requirements, available credits or the other conditions of a request.&lt;/p&gt;

&lt;p&gt;If your application's policy requires every newly issued test credential to have an explicit allowlist, validate that policy before sending a creation request. This small offline guard rejects an empty or malformed list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;require_explicit_allowlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;api_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Provide a non-empty list of allowed API UUIDs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;api_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Each API ID must be a UUID string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromkeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function makes no network calls, creates no credential and does not inspect any key. It only validates the local input shape. A well-formed UUID can still name a nonexistent API, so the service must validate the IDs and enforce the resulting scope. The guard also does not protect requests made through another client; runtime authorization belongs on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the guard locally
&lt;/h3&gt;

&lt;p&gt;Paste the function above and this example into one Python file. The UUID is a synthetic parser fixture; the example does not establish whether a matching API exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;synthetic_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;00000000-0000-4000-8000-000000000001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;malformed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not-a-uuid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;synthetic_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;synthetic_id&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require_explicit_allowlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: rejected locally&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; unique UUID(s)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;empty: rejected locally
malformed: rejected locally
duplicate: 1 unique UUID(s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;duplicate&lt;/code&gt; case confirms that the local guard retained one syntactically valid UUID. Server-side ID validation and permission checks remain separate steps. This example performs no network request and creates no credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test one allowed operation and one excluded operation
&lt;/h2&gt;

&lt;p&gt;Use an authorized test account and a dedicated test credential. Do not repurpose a customer's production key or assume that owning a branded site gives you permission to manage another account's credentials.&lt;/p&gt;

&lt;p&gt;Before calling anything, record the intended API scope, expiry and usage limit. Confirm the current service documentation and the cost of any operation you plan to run. Use synthetic inputs or material you have permission to use.&lt;/p&gt;

&lt;p&gt;Build a small matrix for the actual service:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Evidence to capture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Included API&lt;/td&gt;
&lt;td&gt;Valid credential, explicitly included API, otherwise valid request&lt;/td&gt;
&lt;td&gt;Actual response and associated usage or request record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Excluded API&lt;/td&gt;
&lt;td&gt;Same test identity; API deliberately excluded from its allowlist&lt;/td&gt;
&lt;td&gt;Actual rejection and the documented interpretation of that response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expired credential&lt;/td&gt;
&lt;td&gt;Dedicated short-lived test credential after its configured expiry&lt;/td&gt;
&lt;td&gt;Rejection after expiry, or an explicit “not exercised” result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catalog-only change&lt;/td&gt;
&lt;td&gt;Change a test site's display setting while holding credential policy constant&lt;/td&gt;
&lt;td&gt;Catalog behavior and credential behavior recorded as separate observations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep unrelated inputs valid where the service permits it. If the excluded request also has malformed parameters, missing service access or an invalid key, its failure may not demonstrate that the API allowlist worked. Record the actual reason instead of labelling every error an authorization success.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=WzQAHQFGqkU&amp;amp;cc_load_policy=1&amp;amp;cc_lang_pref=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=invalid_key_case_en_20261020" rel="noopener noreferrer"&gt;Watch the invalid-key test case&lt;/a&gt;. This 49-second fictional example uses synthetic English narration, with English and Simplified Chinese subtitles selectable in the player. It shows why an invalid-key failure leaves allowlist enforcement unproven; no live API request is made.&lt;/p&gt;

&lt;p&gt;Do not pre-fill the matrix with a universal error code. Read the service's documented response and preserve the observed status and sanitized message. Do not deliberately exhaust a budget just to prove a usage limit. Configuration review, a live test and an untested assumption are different evidence levels.&lt;/p&gt;

&lt;p&gt;For an asynchronous operation, preserve the original task ID when one is returned. Check that task after a timeout before considering another paid submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep an acceptance record without keeping the secret
&lt;/h2&gt;

&lt;p&gt;This template captures the useful evidence without asking for the credential value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Site and service:
Test time and time zone:
Authorized test identity / role:
Credential label (not the secret):
Configured API IDs, expiry and usage limit:
Operation expected to be allowed:
Operation expected to be rejected:
Observed status and sanitized result for each case:
Non-secret request/task reference, if appropriate:
Evidence level: settings reviewed / live result / not exercised
Remaining uncertainty and next owner:
Test credential cleanup owner:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove authorization headers, cookies, full tokens, private inputs and access-bearing result URLs from shared evidence. Credential-management responses can themselves contain secrets, so avoid logging their complete bodies. Use the &lt;a href="https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113"&gt;support-ticket redaction guide&lt;/a&gt; when preparing an attachment.&lt;/p&gt;

&lt;p&gt;After testing, have the authorized owner retire credentials that are no longer needed, following the service's documented process. Verify the credential label and dependents before changing it. Keep the catalog result and the authorization result as separate conclusions in the handoff.&lt;/p&gt;

&lt;p&gt;For this implementation, the &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-api-catalog?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl_credential_allowlist_en" rel="noopener noreferrer"&gt;catalog configuration guide&lt;/a&gt; describes the display controls, and the &lt;a href="https://platform.acedata.cloud/documents/platform-credential-create?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl_credential_allowlist_en" rel="noopener noreferrer"&gt;credential creation reference&lt;/a&gt; defines the accepted fields and their current semantics.&lt;/p&gt;

</description>
      <category>api</category>
      <category>security</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Create reproducible API support tickets without sharing credentials</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Mon, 12 Oct 2026 18:22:21 +0000</pubDate>
      <link>https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113</link>
      <guid>https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113</guid>
      <description>&lt;p&gt;When you operate a branded AI API portal, customers need a clear route from a failed request to useful help. A report such as “the API is broken” leaves the next person guessing. A complete terminal log can create a different problem: it may expose a working API key or private customer input.&lt;/p&gt;

&lt;p&gt;A useful support ticket captures the failing step, enough context to investigate it, and the evidence that can be shared safely.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this is a practical guide from the Ace Data Cloud team. The sample below is fictional and contains no customer data.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the step that failed
&lt;/h2&gt;

&lt;p&gt;Separate the website journey from the API operation. A customer might be unable to open a custom domain, sign in, locate the right service documentation, submit an API request, check an asynchronous task, or download a finished result. These are different starting points for an investigation.&lt;/p&gt;

&lt;p&gt;Write down the last step that worked and the first step that failed. For example: “I can open the portal and read the service guide. The documented request returns an error.” That narrows the problem before anyone changes DNS or account settings.&lt;/p&gt;

&lt;p&gt;Use the service's public name and the request path from its current documentation. A branded website address does not establish the correct API base URL. Check the documented request destination separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a small, structured report
&lt;/h2&gt;

&lt;p&gt;Here is a template a developer can paste into an approved support channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Site: &amp;lt;site address&amp;gt;
When: &amp;lt;date, time, and time zone&amp;gt;
Service or tool: &amp;lt;public service name&amp;gt;
Step: &amp;lt;submit request / check existing task / download result&amp;gt;
Request: &amp;lt;HTTP method and documented path, with sensitive parts removed&amp;gt;
Expected: &amp;lt;one sentence describing the intended result&amp;gt;
Observed: &amp;lt;HTTP status and sanitized error code/message&amp;gt;
Reference: &amp;lt;non-secret request or task ID, if the API provides one&amp;gt;
Repeatability: &amp;lt;happened once / intermittent / reproducible&amp;gt;
Last known success: &amp;lt;time, if known&amp;gt;
Recent relevant change: &amp;lt;one change, or none known&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A time zone matters when the customer and support team work in different regions. Keep the original error code and useful wording after removing sensitive material. Turning every failure into “server error” discards information that could explain the problem.&lt;/p&gt;

&lt;p&gt;Some APIs return a request identifier in a header; others return a task identifier in the body. Use the identifier actually supplied by that service. If none was returned, say so. Check whether an identifier is safe to share before putting it in a public comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the whole attachment before sending it
&lt;/h2&gt;

&lt;p&gt;Secrets can appear in several places, so removing one header is only part of the check:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;Review before sharing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request headers&lt;/td&gt;
&lt;td&gt;Remove credentials from &lt;code&gt;Authorization&lt;/code&gt;, API-key headers, and cookies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request and redirect URLs&lt;/td&gt;
&lt;td&gt;Check query parameters and path segments at every hop for tokens, signatures, private identifiers, or customer data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response headers&lt;/td&gt;
&lt;td&gt;Review &lt;code&gt;Location&lt;/code&gt; for signed redirect URLs and remove credentials from &lt;code&gt;Set-Cookie&lt;/code&gt; and other sensitive headers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request body&lt;/td&gt;
&lt;td&gt;Replace private prompts, documents, and media references with a minimal synthetic example when possible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response body&lt;/td&gt;
&lt;td&gt;Check for echoed inputs, personal data, and result links that grant access to private files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshot or terminal output&lt;/td&gt;
&lt;td&gt;Crop or remove credentials, account details, unrelated tabs, and sensitive URL parameters. Inspect the final exported file.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A signed download URL may grant access to a result even when it contains no field named &lt;code&gt;api_key&lt;/code&gt;. Treat it according to the access it grants. If it is unnecessary for the investigation, omit it from the report.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113#comment-3hi2b"&gt;G pointed out in the comments&lt;/a&gt;, a clean initial request URL does not make the redirect chain safe to share. A &lt;code&gt;302&lt;/code&gt; response can put a signed token in its &lt;code&gt;Location&lt;/code&gt; header, and a verbose client may log both that header and the follow-up URL. Inspect every hop and the final exported log; removing &lt;code&gt;Authorization&lt;/code&gt; from the first request is not enough. Keep the status and sanitized destination path when useful, while removing access-bearing parameters and private data.&lt;/p&gt;

&lt;p&gt;Never ask a customer to paste their password, verification code, full API key, complete authorization header, or payment details into a support ticket. For an authorized reproduction, use an appropriate test account and its own credentials. If a live credential has already been exposed, follow the service's credential-revocation process and remove the exposed copy from the discussion where possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watch why removing the query is not enough
&lt;/h3&gt;

&lt;p&gt;This fictional relative URL shows where an access-bearing placeholder can remain. It is not a live endpoint; &lt;code&gt;DEMO_TOKEN&lt;/code&gt; stands for a made-up access credential.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:        /result/DEMO_TOKEN?sig=DEMO
Query removed: /result/DEMO_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removing &lt;code&gt;?sig=DEMO&lt;/code&gt; still leaves the credential in the path. The second line is not ready to share in this example. Omit the access-bearing URL and record only fields approved for the support recipient, such as the failed stage, observed status and observation time. Keep any necessary reference in an authorized support system.&lt;/p&gt;

&lt;p&gt;The 44.6-second walkthrough uses locally synthesized English narration, with English and Simplified Chinese captions. It illustrates one redaction mistake; it is not a general-purpose sanitizer.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ohS2fj_G600" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=ohS2fj_G600&amp;amp;cc_load_policy=1&amp;amp;cc_lang_pref=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=path_credential_example_en_20261019" rel="noopener noreferrer"&gt;Open the narrated URL example on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve the original task after a timeout
&lt;/h2&gt;

&lt;p&gt;For an asynchronous operation, submission, completion, and downloading the result are separate stages. A browser or client timeout does not prove that the server rejected the work.&lt;/p&gt;

&lt;p&gt;For an asynchronous ticket, keep the first submission time separate from the time you checked its status. Add these fields to the general report above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First submitted at: &amp;lt;date, time, and time zone, or unknown&amp;gt;
Original task ID: &amp;lt;non-secret ID, if supplied and safe for this support channel&amp;gt;
Last observed task status: &amp;lt;status returned by the service, or unknown&amp;gt;
Status checked at: &amp;lt;date, time, and time zone, or not checked&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record the status exactly as the service returned it. If an ID was not supplied or a status has not been checked, leave that uncertainty explicit; do not infer that the task succeeded or failed. Thanks to &lt;a href="https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113#comment-3hikp"&gt;officialmailkr's comment&lt;/a&gt; for suggesting separate fields for this handoff.&lt;/p&gt;

&lt;p&gt;If the original task ID is available, use the service's documented status query to inspect that task. Keep the task ID and the time of the first submission with the report. Avoid submitting the same paid operation repeatedly while its outcome is unknown.&lt;/p&gt;

&lt;p&gt;If no task ID was returned, record that uncertainty and inspect the available request, task, or usage records through the appropriate authorized channel. Follow the service's retry guidance before attempting another paid operation. Agree on a small reproduction budget where a new test is necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watch the timeout handoff in 45 seconds
&lt;/h3&gt;

&lt;p&gt;This silent visual checklist covers a returned task ID, a submission timeout without an ID, and a finished result that will not download. It uses English on-screen text, with English and Simplified Chinese subtitles available in the player settings.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/yN7bpqeU0uw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=yN7bpqeU0uw" rel="noopener noreferrer"&gt;Watch the timeout checklist on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Use it alongside the reports below: keep the original ID and timestamps when available, record an unknown outcome explicitly, and follow the service's documented status and retry rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two timeout reports, two different next steps
&lt;/h3&gt;

&lt;p&gt;These fictional JSON records are examples for a support ticket, not request bodies for an API. The field names are local reporting choices, and &lt;code&gt;running&lt;/code&gt; is an illustrative status: copy the actual status returned by your service. All identifiers below are placeholders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case A: the submission returned a task ID, but the local waiting deadline expired.&lt;/strong&gt; A later status query returned &lt;code&gt;running&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"first_submitted_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T09:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"original_task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EXAMPLE-TASK-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_observed_task_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status_checked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T09:01:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_observation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The local waiting deadline expired."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Check the original task using the documented status query."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the task ID and the last response are useful evidence. Continue checking the same task according to the service's documented polling interval and limits. The expired local deadline is not a reason to create another paid task, and a &lt;code&gt;running&lt;/code&gt; response is not evidence that a usable result already exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case B: the submission connection timed out before a task ID arrived.&lt;/strong&gt; No task status has been checked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"first_submitted_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T10:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"original_task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_observed_task_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status_checked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"local_reference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LOCAL-EXAMPLE-002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_observation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The submission connection timed out before a task ID was received."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reconcile the submission outcome before deciding on another request."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local reference helps your team find its own incident record; it is not a server-issued task ID and must not be passed to a task query as though it were one. Use an authorized request or task-history lookup if the service provides one. If it does not, send the sanitized submission evidence through the approved support route and follow that service's retry guidance.&lt;/p&gt;

&lt;p&gt;A missing item in a paginated, filtered, delayed, or incomplete history view does not prove that no task was created. Record the lookup's time window and limits instead of turning “not found in this view” into “safe to submit again.” Use idempotency keys only when the service documents their scope and reuse rules; inventing a key does not make a request safe to replay.&lt;/p&gt;

&lt;p&gt;Keep unobserved values as &lt;code&gt;null&lt;/code&gt; or explicitly unknown. Do not fill in a success or failure status from the passage of time. Before sending either report, apply the attachment review above; a structured JSON record is not automatically redacted.&lt;/p&gt;

&lt;h3&gt;
  
  
  When the polling wait exceeds your local budget
&lt;/h3&gt;

&lt;p&gt;Consider a different fictional case: you have the original task ID, but no task status has been observed. A status query returns HTTP &lt;code&gt;429&lt;/code&gt; with the numeric header &lt;code&gt;Retry-After: 10&lt;/code&gt;, while this local monitoring run has only 5 seconds left.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;th&gt;Value in this example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original task ID&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EXAMPLE-TASK-01&lt;/code&gt; (placeholder)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last observed task state&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status-query response&lt;/td&gt;
&lt;td&gt;HTTP &lt;code&gt;429&lt;/code&gt;; &lt;code&gt;Retry-After: 10&lt;/code&gt; seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remaining local monitoring budget&lt;/td&gt;
&lt;td&gt;5 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local monitoring outcome&lt;/td&gt;
&lt;td&gt;Paused; keep the original task ID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The next permitted check falls outside this monitoring run's budget. Pause the local monitor; do not shorten the 10-second wait to 5 seconds. Pausing local monitoring does not cancel the task or prove that it failed. The query limit does not authorize a new submission.&lt;/p&gt;

&lt;p&gt;Keep the query response and its observation time separate from the last task state and the time that state was observed. If an earlier successful query returned &lt;code&gt;pending&lt;/code&gt; or &lt;code&gt;running&lt;/code&gt;, retain that earlier state with its original timestamp; a later &lt;code&gt;429&lt;/code&gt; does not replace it with &lt;code&gt;failed&lt;/code&gt;, &lt;code&gt;paused&lt;/code&gt;, or a fresh state observation. In this example there was no earlier state, so it remains unknown.&lt;/p&gt;

&lt;p&gt;If monitoring later resumes, use the same original task ID and the service's current polling rules, including any still-applicable wait. Reaching the end of &lt;code&gt;Retry-After&lt;/code&gt; does not guarantee task completion or a successful next query. The field names and values here are local support notes, not a service's API contract.&lt;/p&gt;

&lt;p&gt;The 47.4-second narrated example uses fictional inputs and locally synthesized English speech. Simplified Chinese captions are available. English automatic captions may misrecognize terms; the video description includes the complete English and Chinese transcripts.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/f1ZinW_iXaU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=f1ZinW_iXaU&amp;amp;cc_load_policy=1&amp;amp;cc_lang_pref=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=polling_budget_example_en_20261019" rel="noopener noreferrer"&gt;Open the polling-budget example on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Record output acceptance separately from task success
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Case C: a status query reports success, but nobody has inspected the output.&lt;/strong&gt; Keep the result check open until someone actually performs it. This fictional ticket extends the reporting format above; &lt;code&gt;succeeded&lt;/code&gt; is illustrative, so preserve your service's actual status wording in a real report.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"first_submitted_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T11:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"original_task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EXAMPLE-TASK-003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_observed_task_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"succeeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status_checked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T11:01:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"not_checked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_checked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"acceptance_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open the original result and check the agreed output requirements."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record &lt;code&gt;output_checked_at&lt;/code&gt; only after a person actually inspects the result. Add what was checked and a sanitized evidence reference, such as an approved review note. Use separate local outcomes for usable, unusable, and not checked; a task-success response alone cannot fill those fields.&lt;/p&gt;

&lt;p&gt;If downloading fails or the output fails acceptance, retain the original task reference while investigating access, file format, or content quality. Follow the service's retry guidance before another generation. Keep credential-bearing result URLs out of the ticket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=vM7lgilCzU8&amp;amp;cc_load_policy=1&amp;amp;cc_lang_pref=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=output_acceptance_record_en_20261018" rel="noopener noreferrer"&gt;Watch the 45-second Python record-validation demo&lt;/a&gt;. It uses a separate four-field local format to classify three fictional records and reject one inconsistent record. The silent video offers English and Simplified Chinese captions. Local validation does not query the server or authorize a new submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change one thing, then record the result
&lt;/h2&gt;

&lt;p&gt;Use the evidence to choose the next check. If the system address works while a custom domain fails, investigate that domain's DNS and certificate state. If the website works and the API request fails, check the specific service's key, available credits, request path, parameters, and error documentation.&lt;/p&gt;

&lt;p&gt;Record each change and its result. Changing the domain, credentials, request body, and client library at the same time makes it difficult to learn which change mattered.&lt;/p&gt;

&lt;p&gt;Close the ticket with the confirmed cause, the change that resolved it, and the evidence from the successful check. Keep credentials out of the resolution note too. This gives the next operator a usable record and helps identify repeated problems.&lt;/p&gt;

&lt;p&gt;For the white-label workflow used here, the &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-unbrand?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl_support_report_en" rel="noopener noreferrer"&gt;official launch and troubleshooting checklist&lt;/a&gt; covers the wider customer journey. Use the &lt;a href="https://platform.acedata.cloud/support?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl_support_report_en" rel="noopener noreferrer"&gt;official support route&lt;/a&gt; for account-specific questions, and review the information before sending it.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Setting up a branded AI API portal: a practical launch checklist</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Sun, 11 Oct 2026 17:34:12 +0000</pubDate>
      <link>https://dev.to/germey/setting-up-a-branded-ai-api-portal-a-practical-launch-checklist-401f</link>
      <guid>https://dev.to/germey/setting-up-a-branded-ai-api-portal-a-practical-launch-checklist-401f</guid>
      <description>&lt;p&gt;Building a useful AI API service involves more than exposing endpoints. Your customers also need somewhere to find the right capability, understand the purchase flow, read integration instructions, and reach support.&lt;/p&gt;

&lt;p&gt;This walkthrough covers setting up a branded developer portal with Ace Data Cloud's white-label API Platform. It focuses on the decisions that make the portal useful for customers: catalogue, documentation, branding, domains, and a verifiable first-use path.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this is a product guide from the Ace Data Cloud team. The examples use our public YourBrand demo; they are configuration examples, not customer revenue claims.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the setup walkthrough
&lt;/h2&gt;

&lt;p&gt;This 91-second walkthrough shows the configuration screens for branding, services, pricing, documentation, and domains. English and Simplified Chinese captions are available in the player settings.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/mb3URgxq7Yc" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=mb3URgxq7Yc" rel="noopener noreferrer"&gt;Watch the full API Platform setup demo on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with the customer and their first task
&lt;/h2&gt;

&lt;p&gt;Choose a concrete audience before building a broad catalogue. A software agency integrating image generation into client applications needs a different starting point from a community building music workflows.&lt;/p&gt;

&lt;p&gt;For a first version, write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who will use the portal?&lt;/li&gt;
&lt;li&gt;Which task should they be able to complete first?&lt;/li&gt;
&lt;li&gt;Which services do you understand well enough to support?&lt;/li&gt;
&lt;li&gt;Where should questions about accounts, orders, or integration go?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you a practical way to decide what belongs on the homepage and which documentation should appear first.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create the portal and configure the brand
&lt;/h2&gt;

&lt;p&gt;In the Platform console, open &lt;strong&gt;Site Management&lt;/strong&gt;, choose &lt;strong&gt;Create&lt;/strong&gt;, and enter a site name and subdomain prefix. The current prefix format is 3–32 lowercase letters, digits, or hyphens; it must begin and end with a letter or digit and cannot contain consecutive hyphens.&lt;/p&gt;

&lt;p&gt;Start with the system-provided address. In the site's management screen, configure the name, description, logo, and favicon. Check the site in a private browser window so you see what a customer sees.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.acedata.cloud%2Fuploads%2Fd2be4a48-ea33-444f-a24e-154fb688221c" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.acedata.cloud%2Fuploads%2Fd2be4a48-ea33-444f-a24e-154fb688221c" alt="API Platform site management and branding" width="1656" height="1802"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-api-platform?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl04_en_api_guide" rel="noopener noreferrer"&gt;step-by-step API site guide&lt;/a&gt; includes the current forms and screenshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Organise services and documentation around that first task
&lt;/h2&gt;

&lt;p&gt;Use the service and documentation settings to organise the portal. Adjust display names, descriptions, ordering, and the visible catalogue where those options are available.&lt;/p&gt;

&lt;p&gt;For each service you feature, a customer should be able to answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What does it do?&lt;/li&gt;
&lt;li&gt;Where are the usage and pricing details?&lt;/li&gt;
&lt;li&gt;How do I get from this page to the integration instructions?&lt;/li&gt;
&lt;li&gt;What should I provide to support if a request fails?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A visible catalogue entry is a discovery surface. Hiding an entry is a display decision; it should not be treated as revoking underlying API access.&lt;/p&gt;

&lt;p&gt;You can inspect the &lt;a href="https://yourbrand-demo-1007.platform.acedata.cloud/services?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl04_en_api_demo" rel="noopener noreferrer"&gt;public YourBrand API demo&lt;/a&gt; before choosing a catalogue structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Treat pricing, customer attribution, and earnings as separate checks
&lt;/h2&gt;

&lt;p&gt;A site can use site-wide pricing overrides and supported service-specific overrides. A service-specific setting takes priority; it is not another percentage to stack on top of the site-wide override.&lt;/p&gt;

&lt;p&gt;Customer purchase price and the operator's final earnings are separate records. Earnings depend on eligible orders, customer attribution, applicable distribution rules, and adjustments such as refunds. Example markup values in a demo are not a quote or a promise of profit.&lt;/p&gt;

&lt;p&gt;Before promoting a commercial offer, confirm the current &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-attribution?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl04_en_api_terms" rel="noopener noreferrer"&gt;customer attribution and earnings rules&lt;/a&gt; and verify the relevant order records.&lt;/p&gt;

&lt;p&gt;The white-label product uses a shared account, balance, and package system. Plan your customer explanation around that actual model; branding does not create a separate user database or an independently isolated billing stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Add domains after the system address works
&lt;/h2&gt;

&lt;p&gt;The website domain and API domain serve different customer needs. Configure the ones your product requires, then follow the current DNS and certificate instructions.&lt;/p&gt;

&lt;p&gt;Allow time for DNS and certificate processing. A successful site creation does not mean a new custom domain is already active.&lt;/p&gt;

&lt;p&gt;After domain setup, repeat the same customer checks you performed on the system address: homepage, catalogue, documentation, sign-in, and the published integration route.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Validate the full customer journey
&lt;/h2&gt;

&lt;p&gt;Before inviting a wider audience, walk through the launch sequence:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Evidence to keep&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open the site as a visitor&lt;/td&gt;
&lt;td&gt;Correct brand, clear primary service, working support link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow the documentation&lt;/td&gt;
&lt;td&gt;A customer can locate the next integration step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check registration attribution&lt;/td&gt;
&lt;td&gt;The actual new account is attributed as intended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Make an intentional small purchase or request&lt;/td&gt;
&lt;td&gt;Correct order, credit, and usage records where applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inspect the operator's records&lt;/td&gt;
&lt;td&gt;Eligible earnings follow the current rules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Real generation and payment checks can incur charges, so select an appropriate small test and inspect the actual records. A successful API request and an earnings entry are different outcomes; one is not proof of the other.&lt;/p&gt;

&lt;p&gt;If a launch check fails, use the &lt;a href="https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113"&gt;API support-ticket template&lt;/a&gt; to record the failing step, time, sanitized error, and non-secret request or task ID before contacting support. The companion guide also covers asynchronous timeouts and checking credentials, redirect URLs, and verbose logs before sharing evidence.&lt;/p&gt;

&lt;p&gt;Once the first path works, keep the initial catalogue focused and add services based on customer demand. Maintain a clear support route and review usage, repeat purchases, and support effort before expanding the offering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep a launch handoff record
&lt;/h2&gt;

&lt;p&gt;A useful handoff lets the next person see what was checked, which route was used, and what remains open. Record each result as &lt;strong&gt;pass&lt;/strong&gt;, &lt;strong&gt;fail&lt;/strong&gt;, or &lt;strong&gt;not checked&lt;/strong&gt;. A blank field is not a pass, and one successful test does not certify every customer, network, or service.&lt;/p&gt;

&lt;p&gt;Copy this record for one intended customer journey. Use an authorized test account; keep private evidence in an approved location and share only the references appropriate to the recipient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Purpose / first customer task:
Website / API hostname:
Client / network / test time:
Account reference (no secrets):
DNS / TLS result + evidence:
HTTP route result + evidence:
Allowed operation + result:
Denied operation + result:
Usage / order check + evidence:
Attribution / reward check:
Open issue + next owner:
Next check / acceptance scope:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, if DNS and TLS pass but the application returns 404, record the route result as failed and any unperformed credential tests as not checked. The transport result does not establish the route or credential scope. Conversely, opening the homepage does not prove that the API hostname is ready.&lt;/p&gt;

&lt;p&gt;Use the companion guide for the layer you actually need to inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hostname or connection:&lt;/strong&gt; the &lt;a href="https://dev.to/germey/check-dns-and-tls-before-sending-an-api-key-to-a-new-hostname-288f"&gt;DNS and TLS probe&lt;/a&gt; verifies transport without sending an API key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential scope:&lt;/strong&gt; the &lt;a href="https://dev.to/germey/test-api-credential-permissions-separately-from-your-service-catalog-4hlm"&gt;permission test matrix&lt;/a&gt; separates visible catalogue entries from allowed and denied operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An unresolved request:&lt;/strong&gt; the &lt;a href="https://dev.to/germey/create-reproducible-api-support-tickets-without-sharing-credentials-4113"&gt;support handoff template&lt;/a&gt; helps preserve the original task reference and remove secrets from evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before another paid test, check the current service price and define the small operation you intend to run. Keep API success, usage or order reconciliation, and customer attribution or rewards as separate observations. A team handoff record does not create a spending limit or replace the platform's records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore the product
&lt;/h2&gt;

&lt;p&gt;If your customers want to integrate AI into an application, begin with the &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-api-platform?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl04_en_api_cta" rel="noopener noreferrer"&gt;API Platform creation guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If they want to create directly in a browser, the &lt;a href="https://platform.acedata.cloud/documents/earning-whitelabel-studio?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl04_en_studio_cta" rel="noopener noreferrer"&gt;Studio white-label guide&lt;/a&gt; covers the other product route.&lt;/p&gt;

&lt;p&gt;For standard product or account questions, use the &lt;a href="https://platform.acedata.cloud/support?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=whitelabel_launch_20261012&amp;amp;utm_content=wl04_en_support" rel="noopener noreferrer"&gt;official support page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>saas</category>
    </item>
    <item>
      <title>Getting Started with the Ace Data Cloud TypeScript SDK</title>
      <dc:creator>Germey</dc:creator>
      <pubDate>Sun, 11 Oct 2026 01:11:53 +0000</pubDate>
      <link>https://dev.to/germey/getting-started-with-the-ace-data-cloud-typescript-sdk-3gig</link>
      <guid>https://dev.to/germey/getting-started-with-the-ace-data-cloud-typescript-sdk-3gig</guid>
      <description></description>
      <category>ai</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>api</category>
    </item>
  </channel>
</rss>
