Skip to main content
A credential is one real set of secrets — a Five9 API login, an OAuth2 app registration, an API token. It belongs to the tenant it can authenticate to (see platform tenancy), not to any one service, and it serves every compatible service of that tenant. Tasks never carry secret values themselves; they reference a credential by id, and the platform resolves the material at runtime. Credentials belong to your organization and are never visible to another account.
The task field is spelled connection_id and its value is a cred_ id. The field name is fixed by workflow-history compatibility and is not going to change; read it as “the credential this task runs with”. Built-in utility actions (like getdialed__utils__echo) need none — their service reports requires_connection: false.

One credential, many services

The thing you actually possess is a login, not a login-per-service. So one credential record covers every service of its tenant that accepts its authentication method:
  • You create it once. Supply the platform, the authentication method and the secret material. There is no service to pick.
  • It is offered wherever it fits. A Five9 basic-auth credential is available to every Five9 service that takes basic auth, on the Five9 account it authenticates to.
  • You rotate it once. One update reaches every flow using it — see rotating a secret.

The credential object

Authentication methods

auth_method describes only the credential material and how it is presented. It says nothing about the transport — whether a service speaks SOAP or REST is the service’s own protocol field in the catalog. A catalog service publishes the method it accepts as its own auth_method, which additionally may be none — meaning the service needs no credential at all. A credential that presents nothing is not a credential, so none is not a value a credential can have.

Basic auth: two ways to supply it, one stored shape

A basic_auth credential is always stored as a username and a password. Supply it either way:
basic_token is the convenience input for when what you have on hand is the pre-encoded token a provider’s documentation prints. It is decoded and stored as the username and password pair, and the encoded form is never kept. Supply one or the other, never both — they are two encodings of one identity, and a body carrying both is refused. base64(username:password) is a wire encoding, not a credential format: encoding is the transport’s business, and the Authorization header is computed fresh each time the credential is used. Storing both forms would mean two records for one real login, which is exactly what “one credential, rotated once” is meant to prevent. A basic_token that is not valid base64, or that decodes to something without a colon in it, is refused with a message naming the field only — never any part of the token.

Declaring where an API key goes

An api_key credential needs one more fact than the key itself: where the key is presented. Providers disagree — some want a header, some want Bearer in front of the value, some want a query parameter — and the key alone does not say which. That fact is declared once, on the credential, as api_key_placement. Exactly one of header_name or query_param, and scheme only alongside header_name:
The three shapes that cover essentially every provider:

Postmark server token

Postmark takes the second shape. A transactional email credential is an api_key credential whose key is presented in an X-Postmark-Server-Token header, with no scheme in front of it:
The key is a Postmark server token, not an account token, and the distinction is not cosmetic: an account token administers the whole account and a server token is scoped to one server’s mail. Only the server token belongs here — the material field is named server_token to say so at the point where it is supplied. One credential is one Postmark server. GetDialed calls Postmark with the token, reads back which server it belongs to, and files the credential under that Server — you never look up or type a provider identifier.
A server token can read that server’s message history, so the server is an isolation boundary and not merely a budget key. Postmark retains message content after delivery and hands it back to anything holding the server token. That is why one server per account is the design: a token that covered several accounts’ mail would let one account’s credential reach another’s message content, which is a content leak rather than a shared allowance.Keep the account token out of GetDialed entirely. Nothing in a flow needs it, and a credential holding one would grant every server on the account rather than the one it was meant for.

Why the placement lives on the credential

Declaring it here rather than per step is what makes the guarantee hold: every step using this credential authenticates identically, and no step configuration mentions authentication at all. A placement that could be overridden per use would put the same credential on the wire two different ways and reintroduce the “which one is this step actually doing” question. It also decides what a step may not author. A step that selects this credential cannot set the header or query parameter the placement names — that name joins the three that are always refused. See calling an HTTP API. A placement declaring neither a header nor a query parameter, declaring both, or putting scheme on a query parameter is refused: each is either no answer to “where does the key go” or two answers.

OAuth2 and audience

An oauth2 credential exchanges its client id and secret at token_url for an access token, and the platform refreshes that token for you. Alongside grant_type and scopes, some authorization servers also require an audience — their own identifier for the API the token is being requested for.
Leave it out where the provider does not ask for one. Where it is required and missing, the symptom is not an obvious error at create time — the token exchange succeeds and the token it issues is rejected by the API you then call. If an OAuth2 credential authenticates but every call with it comes back unauthorized, audience is the first thing to check in the provider’s documentation. token_url is required for oauth2 and rejected on every other method, and only an https token endpoint is ever contacted — a plain http one is refused when the exchange runs. grant_type, scopes and audience are likewise oauth2-only, and api_key_placement is api_key-only and required there: a credential carrying configuration that belongs to a different method is refused, because it is a second and contradictory answer to what the credential presents. Both audience and api_key_placement are non-secret configuration. They are returned by the API like any other field, and a PUT can change either without touching the stored material — so correcting a placement is not a rotation and does not clear last_verified.

AWS access key

An aws_access_key credential is the access-key pair of an IAM user in your AWS account, plus the region it works in. It is what the S3 file steps authenticate with, and it reaches nothing in your account that its IAM policy does not name.
The two halves land in different places, and the split is the same one every method here follows: credentials accepts exactly those two keys. A third — a session token, say — is refused rather than quietly dropped, because nothing ever reads one back out and storing an unread secret is worse than not accepting it. aws_region is required on this method and refused on every other, as are the other two aws_ fields. A credential carrying configuration belonging to a different method is refused: it is a second and contradictory answer to what the credential presents. aws_default_bucket is the bucket a storage step uses when it names none itself. Set it and the bucket name lives in one place instead of in every step. It is a bucket name, not a URL or a path — a value containing /, :// or whitespace is refused.
There is no api-key placement to declare, and that is why this is its own method rather than a variant of api_key. An access-key pair is not presented as a header value: it signs the request, and the signature covers the whole request. There is nowhere to put the key, so api_key_placement would be simultaneously required and meaningless.
Creating the credential identifies which AWS Account the pair belongs to and files the credential under it — you never look up or type the account number. See platform tenancy for what that record owns.

The IAM policy to attach

Scope the IAM user to the one bucket. Store a report in S3 prints the exact two-statement policy, including why s3:ListBucket on the bucket ARN belongs on it even though nothing lists your bucket — without it, S3 answers 403 for an object that is simply not there, and a missing object becomes indistinguishable from a broken credential.

S3-compatible storage

aws_endpoint_url points the credential at an S3-compatible service instead of Amazon’s — Cloudflare R2, MinIO, Backblaze B2, DigitalOcean Spaces. One field; the storage steps are otherwise identical. Set it together with aws_default_bucket. An S3-compatible provider exposes no account-identity call, so reaching the bucket is the only way to establish that the key pair works at all.
The endpoint must be an https URL naming a publicly routable host. A http URL, or one naming a private, loopback, link-local or otherwise internal address, is refused when you save it — on create and on update alike — and the same refusal is applied again when a step uses the credential.This is a platform safety rule and it has no exceptions or overrides. A stored endpoint is a destination the platform will connect to on your behalf, and an endpoint pointing inward would make a credential into a way to reach services that are not yours to reach. Every refused address class produces the same message, which deliberately does not tell you which rule you tripped — a message that distinguished them would answer questions about our network for anyone who asked enough times.
Leave the field empty for real AWS. A trailing newline or stray whitespace still counts as a set endpoint, so paste carefully — an R2 credential is not silently sent to Amazon.

Testing an AWS credential

POST /credentials/{credential_id}/test makes two checks, not one, and reports which of them it managed:
  • It resolves the account identity, proving the pair is valid. This call requires no IAM permission and cannot be denied by a policy, which is exactly what makes it a good identity check and a worthless permission check.
  • If aws_default_bucket is set, it then checks it can reach that bucket — the grant you actually need.
With no default bucket configured, only the first check runs and the result says so in as many words rather than reporting a bare success. A credential can pass the identity check and still be unable to read one byte from your bucket, and a success that did not name what it left unproven would send you into your first flow with false confidence.
POST /credentials/test — the test-before-save endpoint — cannot test an AWS credential yet. Its body carries no aws_region, and the region is not optional, so the attempt comes back as INVALID_CREDENTIALS_SHAPE. Create the credential and test it by id instead; a create that cannot be verified fails before anything is stored, so nothing is left behind.

Not supported yet

Cross-account IAM roles. There is no way to give GetDialed a role to assume with an external id — the access-key pair is the only AWS credential shape today. If your security posture requires roles rather than long-lived keys, that is a real gap and it is worth waiting for rather than designing around.

Which credential an action can use

Compatibility is computed by the server, and the same rule runs in both places it matters — when you list candidates and when you save a definition. A credential offered to you will not be rejected at save time. GET /credentials?compatible_with={action_id} returns only the credentials that action can actually use:
  • For a vendor action, the credential’s tenant must belong to the action’s platform and its auth_method must be one the action’s service accepts. An unbound credential is never offered for a vendor action — it has no tenant to have authenticated to.
  • For a built-in action, the authentication method alone decides; there is no vendor tenant to compare against.
  • An action that needs no credential matches none. You get an empty list rather than every credential you own.
  • An unknown action_id is a 404, not an empty list.
When exactly one candidate exists, a client may preselect it for you — but the saved definition always carries an explicit credential id. Nothing is filled in on your behalf at save time; a task that names no credential for an action that requires one is rejected with 422.

Rotating a secret

Supply credentials on PUT /credentials/{credential_id} to replace the stored material. The credential keeps its id and keeps pointing at the same stored secret, so:
  • No flow edit. Every definition referencing this credential picks up the new material.
  • No re-selection and no redeployment.
  • A running worker refreshes its cached copy within 300 seconds — so allow up to five minutes before expecting the new material to be in force everywhere, and do not rotate and immediately re-dispatch expecting the new value.
Rotating clears last_verified, because the recorded check described the material you just replaced. Run a test to record a fresh one. platform_id cannot be changed — a credential belongs to one platform for its whole life, and a 400 is returned rather than a rebind. Server-managed fields (the tenant binding, the stored-secret pointer, the observed-use record) cannot be set from a request body and are preserved.

Credentials with no vendor account

Some platforms publish no way to identify “your account over there”, and some credentials are not tied to a vendor at all — an internal HTTP service, a customer-specific endpoint. Those work through the same create call: supply tenant_name with a label you choose, and the credential is filed under a manual tenant of that name. There is no create-the-tenant-first step. A manual tenant behaves like any other on /v1/tenants: it is listed, renamable and retirable. It has no provider-published rate caps, so its budget is empty, and its name is unique within your organization only.

Endpoints

All endpoints require authentication and operate only on your organization’s credentials. Cross-tenant access returns 404, never 403. Creating one — the platform, the method, and the material, and nothing else:
When the platform can identify the account those credentials belong to, this call discovers it, verifies the credentials in the process, and files the credential under that account — you never look up or type a provider account id. The response carries the resulting tenancy_id and a resolved tenant:
Because discovery is a live call, a create fails before anything is stored:
  • 400 when the credentials could not be verified — wrong material, or the platform could not be reached. Nothing is written. Use POST /credentials/test first if you want to check without committing.
  • 409 when that provider account already belongs to another GetDialed account, or when these exact credentials are already stored here. A vendor account belongs to exactly one GetDialed account platform-wide.
Supply credentials_ref instead of credentials to point at a secret that already exists. The two are mutually exclusive, and a ref-only create performs no live verification, so its tenancy_id stays null.

Deleting a credential

Deleting removes the record, and reclaims the stored secret only when GetDialed created that secret in the first place:
  • A secret GetDialed created, used by nothing else — the record is deleted and the secret is scheduled for deletion with a recovery window. Nothing is destroyed immediately.
  • A secret shared with other credentials in your account — the delete is refused with 409 and the record is left untouched, so the same request can be retried. The message tells you how many other credentials still reference it. One secret serving several credentials is normal, not an error.
  • A secret GetDialed did not create (one provisioned for you directly) — the record is removed and the secret is left exactly as it was.
Deleting the last credential bound to a vendor account frees that account for any organization to connect again. The tenant’s tuned rate-limit settings are kept, so re-adding the same account later restores them.

Testing credentials

Two endpoints check credentials against the live platform, so you catch a bad password before a flow fails at 2 a.m. Both require an admin caller.
1

Test before saving

POST /credentials/test takes platform_id, auth_method and a platform-specific credentials object. It returns a verdict and persists nothing — the test-before-save flow.
2

Test a saved credential

POST /credentials/{credential_id}/test (no request body) resolves the saved credential’s stored secret and runs the same check. On success, last_verified is updated. This is what you want after a rotation, or when a flow starts failing.
Both are liveness checks. They prove the credentials are real and the platform is reachable with them. They do not prove what the credentials are permitted to do — verifying permissions would mean performing the operations themselves against your live vendor account, which this API will not do. For permissions, read the observed-use record below. Both return 200 with a TestResult body for every outcome — success, bad credentials, even a timeout. 4xx statuses are reserved for problems with your request itself.
422 and INVALID_CREDENTIALS_SHAPE look similar but differ: 422 means the request body itself is malformed (wrong types, missing schema fields), while 200 + INVALID_CREDENTIALS_SHAPE means the body parsed fine but the credentials object lacks what the target platform needs.

What a credential can actually do

A liveness test cannot tell you what a credential is permitted to do. The record of what it has done can. Every credential carries a telemetry object keyed by service_id, stamped as work actually happens. For each service the credential has been used with it reports when it was last used, when it last succeeded, when it last failed, and what class of failure that was:
This is the honest answer to “what can this credential do”, and it costs nothing to read: the same record travels on every row of GET /credentials, so a list view can show health without a request per row and without running a flow to find out. A credential that has never been used has an empty record — which is a real answer, not a missing one. last_error_class is a failure class, never a failure message. Provider error strings can embed request URLs carrying credential material, so they are never published here.

Discovering credential fields

To build a credential form you need to know which fields a platform requires — a username and password for Five9 basic auth, a token for another platform. The catalog service listing carries that schema. GET /catalog/platforms/{platform_id}/services returns each service with a credential_fields array:
This is metadata only — it describes what to collect, never actual credential values. A service without a published schema returns an empty credential_fields list.

Next steps

Platform tenancy

The tenant a credential belongs to, and the budget they share.

Integration catalog

Browse the platforms and services a credential can reach.

Quota and progress

Which credential is spending a tenant’s provider budget.

Build your first flow

Put a credential to work in a multi-step flow.