Adobe Known Pitfalls
Overview
The 10 most common mistakes when integrating with Adobe APIs, based on real production issues. Each pitfall includes the anti-pattern, why it fails, and the correct approach.
Prerequisites
- Access to your Adobe integration codebase
- Understanding of Adobe API architecture (OAuth, async jobs, rate limits)
Instructions
Pitfall 1: Still Using JWT (Service Account) Credentials
Status: CRITICAL — JWT credentials reached End of Life June 2025.
import jwt from 'jsonwebtoken';
import fs from 'fs';
const privateKey = fs.readFileSync('private.key');
const jwtToken = jwt.sign({
exp: Math.round(Date.now() / 1000) + 86400,
iss: orgId,
sub: technicalAccountId,
aud: `https://ims-na1.adobelogin.com/c/${clientId}`,
}, privateKey, { algorithm: 'RS256' });
const res = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.ADOBE_CLIENT_ID!,
client_secret: process.env.ADOBE_CLIENT_SECRET!,
grant_type: 'client_credentials',
scope: process.env.ADOBE_SCOPES!,
}),
});
Pitfall 2: Not Caching IMS Access Tokens
IMS tokens are valid for 24 hours. Generating a new token per request wastes 200-500ms:
async function callFirefly(prompt: string) {
const tokenRes = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', { ... });
const { access_token } = await tokenRes.json();
}
let cached: { token: string; expiresAt: number } | null = null;
async function getToken(): Promise<string> {
if (cached && cached.expiresAt > Date.now() + 300_000) return cached.token;
const res = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', { ... });
const data = await res.json();
cached = { token: data.access_token, expiresAt: Date.now() + data.expires_in * };
cached.;
}
Pitfall 3: Using Firefly Sync Endpoint for Batch Operations
for (const prompt of prompts) {
const result = await fetch('https://firefly-api.adobe.io/v3/images/generate', {
method: 'POST', ...
});
results.push(await result.json());
}
const jobs = await Promise.all(
prompts.map(prompt =>
fetch('https://firefly-api.adobe.io/v3/images/generate-async', {
method: 'POST', ...
}).then(r => r.json())
)
);
const results = await Promise.all(jobs.map(j => pollJob(j.statusUrl)));
Pitfall 4: Ignoring Firefly Content Policy Errors
try {
const result = await generateImage({ prompt: 'Photo of Nike shoes' });
} catch (e) {
console.log('Generation failed');
}
try {
const result = await generateImage({ prompt });
} catch (e: any) {
if (e.status === 400 && e.message?.includes('content policy')) {
throw new Error(
'Firefly content policy violation. ' +
'Remove trademarks, real people, or explicit content from prompt.'
);
}
throw e;
}
Pitfall 5: Uploading Files Directly to Photoshop/Lightroom API
const formData = new FormData();
formData.append('image', fs.readFileSync('photo.jpg'));
await fetch('https://image.adobe.io/v2/remove-background', {
method: 'POST',
body: formData,
});
const inputUrl = await s3.getSignedUrl('getObject', {
Bucket: 'my-bucket', Key: 'photo.jpg', Expires: 3600,
});
const outputUrl = await s3.getSignedUrl('putObject', {
Bucket: 'my-bucket', Key: 'output.png', Expires: 3600,
});
await fetch('https://image.adobe.io/v2/remove-background', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'x-api-key': clientId, 'Content-Type': 'application/json' },
body: .({
: { : inputUrl, : },
: { : outputUrl, : , : },
}),
});
Pitfall 6: Not Polling Async Job Status
Photoshop and Lightroom APIs return immediately with a job ID. You must poll for results:
const res = await fetch('https://image.adobe.io/v2/remove-background', { ... });
const result = await res.json();
console.log('Done!', result);
const submission = await res.json();
let job;
do {
await new Promise(r => setTimeout(r, 2000));
const pollRes = await fetch(submission._links.self.href, {
headers: { Authorization: `Bearer ${token}`, 'x-api-key': clientId },
});
job = await pollRes.json();
} while (job.status !== 'succeeded' && job.status !== 'failed');
if (job.status === 'failed') throw new Error(job.error?.);
Pitfall 7: Leaking Adobe Credentials in Source Code
const client_secret = 'p8_XYZ_your_actual_secret_here_do_not_do_this';
const client_secret = process.env.ADOBE_CLIENT_SECRET!;
Pitfall 8: Not Handling PDF Services Quota
async function extractAllPdfs(paths: string[]) {
for (const path of paths) {
await extractPdf(path);
}
}
let txCount = 0;
async function trackedExtract(path: string) {
if (txCount >= 490) {
throw new Error('Approaching PDF Services monthly limit. 10 transactions remaining.');
}
const result = await extractPdf(path);
txCount++;
return result;
}
Pitfall 9: Using Deprecated Photoshop Endpoints
await fetch('https://image.adobe.io/sensei/cutout', { ... });
await fetch('https://image.adobe.io/v2/remove-background', { ... });
Pitfall 10: Missing Webhook Signature Verification
app.post('/webhooks/adobe', (req, res) => {
processEvent(req.body);
res.sendStatus(200);
});
app.post('/webhooks/adobe', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['x-adobe-digital-signature-1'];
const keyPath = req.headers['x-adobe-public-key1-path'];
const publicKey = await fetch(`https://static.adobeioevents.com${keyPath}`).then(r => r.text());
const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(req.body);
if (!verifier.verify(publicKey, sig, 'base64')) {
return res.sendStatus(401);
}
processEvent(JSON.parse(req.body.()));
res.();
});
Quick Pitfall Scanner
echo "=== Adobe Pitfall Scan ==="
grep -rn "jsonwebtoken\|jwt\.sign\|RS256" --include="*.ts" --include="*.js" src/ && echo "FOUND: JWT auth (deprecated)" || echo "OK: No JWT"
grep -rn "ims/token/v3" --include="*.ts" src/ | wc -l | xargs -I{} echo "Token endpoint calls: {} (should be 1 — in auth.ts only)"
grep -rn "p8_" --include="*.ts" --include="*.js" src/ && echo "FOUND: Hardcoded Adobe secret" || echo "OK: No hardcoded secrets"
grep -rn "sensei/cutout" --include="*.ts" src/ && echo "FOUND: Deprecated Photoshop endpoint" || echo "OK: No deprecated endpoints"
grep -rn "webhooks/adobe" --include="*.ts" src/ | grep -v "digital-signature\|verify\|RSA" && echo "WARNING: Webhook handler may lack signature verification"
Quick Reference Card
| Pitfall | Risk | Detection | Fix |
|---|
| JWT auth | Broken auth | Grep for jwt.sign | Migrate to OAuth S2S |
| No token cache | Perf (-500ms/req) | Multiple ims/token calls | Cache with expiry |
| Sync Firefly for batch | Slow (N*20s) | Sequential generate calls | Use async endpoint |
| Ignore content policy | Wasted credits | Catch 400 without reason | Pre-screen prompts |
| Direct file upload | 400 errors | FormData to Photoshop | Pre-signed URLs |
| No job polling | Missing results | No poll loop after submit | Poll _links.self |
Leaked p8_ secret | Credential compromise | Grep for p8_ | Env vars + .gitignore |
| No quota tracking | Silent failures | No counter | Track per-month usage |
| Old PS endpoint | 404 errors | /sensei/cutout | /v2/remove-background |
| No webhook verify | Security hole | No signature check | RSA-SHA256 verification |
Resources