Manage SFTP delivery accounts
Your organization has its own SFTP endpoint. How to give a partner a login on it, and how to manage that login over time. You need config_write.
Your endpoint#
catalog tells you the address and port partners connect to:
query {
catalog { sftpHost sftpPort }
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n catalog { sftpHost sftpPort }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
catalog { sftpHost sftpPort }
}
`,
}),
});
const { data, errors } = await res.json();{ "data": { "catalog": { "sftpHost": "203.0.113.24", "sftpPort": 22 } } }If sftpHost is null, your organization does not have an SFTP endpoint yet.
Issue an account with an SSH key#
Keys are the better choice for automated delivery. Issue the account — it starts with no password and cannot sign in yet — then authorize the key your partner sends you:
mutation {
issueDeliveryAccount(input: { username: "lanternfish", displayName: "Lanternfish Records" }) {
deliveryAccountPk
username
password
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n issueDeliveryAccount(input: { username: \"lanternfish\", displayName: \"Lanternfish Records\" }) {\n deliveryAccountPk\n username\n password\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
issueDeliveryAccount(input: { username: "lanternfish", displayName: "Lanternfish Records" }) {
deliveryAccountPk
username
password
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"issueDeliveryAccount": { "deliveryAccountPk": 64, "username": "lanternfish", "password": null }
}
}mutation AddKey($key: String!) {
addDeliveryKey(deliveryAccountPk: 64, publicKey: $key) {
fingerprint
}
}{
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHk3b2l0Z0N6c1l4b3dQZ2pQb0VlQ3ZkR2Z0T2xY ops@lanternfish"
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation AddKey($key: String!) {\n addDeliveryKey(deliveryAccountPk: 64, publicKey: $key) {\n fingerprint\n }\n}",
"variables": {
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHk3b2l0Z0N6c1l4b3dQZ2pQb0VlQ3ZkR2Z0T2xY ops@lanternfish"
}
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation AddKey($key: String!) {
addDeliveryKey(deliveryAccountPk: 64, publicKey: $key) {
fingerprint
}
}
`,
variables: {
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHk3b2l0Z0N6c1l4b3dQZ2pQb0VlQ3ZkR2Z0T2xY ops@lanternfish"
},
}),
});
const { data, errors } = await res.json();Read the fingerprint back to your partner; they can compare it with
ssh-keygen -lf on their side. Usernames are 3–32 characters, start with a
lowercase letter, and use only a–z, 0–9, _ and -.
Issue an account with a password#
For partners whose tools cannot use keys, pass withPassword: true:
mutation {
issueDeliveryAccount(
input: { username: "coastline", displayName: "Coastline Sound", withPassword: true }
) {
deliveryAccountPk
password
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n issueDeliveryAccount(\n input: { username: \"coastline\", displayName: \"Coastline Sound\", withPassword: true }\n ) {\n deliveryAccountPk\n password\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
issueDeliveryAccount(
input: { username: "coastline", displayName: "Coastline Sound", withPassword: true }
) {
deliveryAccountPk
password
}
}
`,
}),
});
const { data, errors } = await res.json();The password is returned once. If it is lost, reset it — the old one stops working immediately.
What your partner does#
Give them the host, port, username and password (or confirm their key), and
have them upload each delivery — the XML and its media — as its own
directory inside upload/. The directory is what ties a message to its
files, so keep one delivery per directory:
sftp -P 22 [email protected]
sftp> put -r 20260917_0196871402293 upload/Deliveries show up in messages, and anything still waiting for files in blockedReleases.
Rotate a key#
Several keys can be live at once:
- addDeliveryKey with the new key.
- Have the partner confirm they can connect with it.
- revokeDeliveryKey the old one, found with deliveryAccountKeys.
Key and password changes apply to the partner's next connection.
Revoke an account#
mutation {
revokeDeliveryAccount(deliveryAccountPk: 64)
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n revokeDeliveryAccount(deliveryAccountPk: 64)\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
revokeDeliveryAccount(deliveryAccountPk: 64)
}
`,
}),
});
const { data, errors } = await res.json();The account can no longer sign in. It stays in
deliveryAccounts with revokedAt set, and
everything it delivered keeps its history.
Several catalogs#
Delivery accounts belong to the organization, since it has one endpoint. If
your organization has more than one catalog, pass catalogPk when issuing
an account to choose which catalog its deliveries land in.