adobe-known-pitfalls
Identify and avoid Adobe-specific anti-patterns: using deprecated JWT auth, not caching IMS tokens, ignoring Firefly content policy, missing async job polling, and leaking p8_ secrets. Real code examples with fixes. Trigger with phrases like "adobe mistakes", "adobe anti-patterns", "adobe pitfalls", "adobe what not to do", "adobe code review".
What this skill does
# 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.
```typescript
// WRONG: JWT auth (no longer works as of 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' });
// RIGHT: OAuth Server-to-Server (current standard)
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:
```typescript
// WRONG: New token every request (200-500ms overhead each time)
async function callFirefly(prompt: string) {
const tokenRes = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', { ... });
const { access_token } = await tokenRes.json();
// ... use access_token
}
// RIGHT: Cache token with expiry check
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 * 1000 };
return cached.token;
}
```
---
### Pitfall 3: Using Firefly Sync Endpoint for Batch Operations
```typescript
// WRONG: Sequential sync calls (each blocks 5-20s)
for (const prompt of prompts) {
const result = await fetch('https://firefly-api.adobe.io/v3/images/generate', {
method: 'POST', ...
});
results.push(await result.json());
}
// Total time: N * 5-20s = very slow
// RIGHT: Async endpoint with parallel submission
const jobs = await Promise.all(
prompts.map(prompt =>
fetch('https://firefly-api.adobe.io/v3/images/generate-async', {
method: 'POST', ...
}).then(r => r.json())
)
);
// Poll all jobs in parallel
const results = await Promise.all(jobs.map(j => pollJob(j.statusUrl)));
// Total time: max(5-20s) = much faster
```
---
### Pitfall 4: Ignoring Firefly Content Policy Errors
```typescript
// WRONG: Treat all 400 errors the same
try {
const result = await generateImage({ prompt: 'Photo of Nike shoes' });
} catch (e) {
console.log('Generation failed'); // No idea why
}
// RIGHT: Handle content policy specifically
try {
const result = await generateImage({ prompt });
} catch (e: any) {
if (e.status === 400 && e.message?.includes('content policy')) {
// Save the credit — don't retry, fix the prompt
throw new Error(
'Firefly content policy violation. ' +
'Remove trademarks, real people, or explicit content from prompt.'
);
}
throw e; // Other errors might be retryable
}
```
---
### Pitfall 5: Uploading Files Directly to Photoshop/Lightroom API
```typescript
// WRONG: Trying to upload file directly (not supported)
const formData = new FormData();
formData.append('image', fs.readFileSync('photo.jpg'));
await fetch('https://image.adobe.io/v2/remove-background', {
method: 'POST',
body: formData, // Photoshop API doesn't accept direct uploads
});
// RIGHT: Use pre-signed cloud storage URLs
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: JSON.stringify({
input: { href: inputUrl, storage: 'external' },
output: { href: outputUrl, storage: 'external', type: 'image/png' },
}),
});
```
---
### Pitfall 6: Not Polling Async Job Status
Photoshop and Lightroom APIs return immediately with a job ID. You must poll for results:
```typescript
// WRONG: Treating response as the final result
const res = await fetch('https://image.adobe.io/v2/remove-background', { ... });
const result = await res.json();
console.log('Done!', result); // result is just { _links: { self: { href: ... } } }
// RIGHT: Poll the status URL until completion
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?.message);
```
---
### Pitfall 7: Leaking Adobe Credentials in Source Code
```typescript
// WRONG: Hardcoded credentials (Adobe OAuth secrets start with p8_)
const client_secret = 'p8_XYZ_your_actual_secret_here_do_not_do_this';
// WRONG: Committed .env file
// git add .env && git commit -m "add config"
// RIGHT: Environment variables + .gitignore
const client_secret = process.env.ADOBE_CLIENT_SECRET!;
// .gitignore includes: .env, .env.local, .env.*.local
```
---
### Pitfall 8: Not Handling PDF Services Quota
```typescript
// WRONG: No quota awareness (free tier = 500 tx/month)
async function extractAllPdfs(paths: string[]) {
for (const path of paths) {
await extractPdf(path); // Silently fails after 500th call
}
}
// RIGHT: Track and enforce quota
let txCount = 0;
async function trackedExtract(path: string) {
if (txCount >= 490) { // Leave buffer
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
```typescript
// WRONG: v1 endpoint (deprecated)
await fetch('https://image.adobe.io/sensei/cutout', { ... });
// RIGHT: v2 endpoint (current)
await fetch('https://image.adobe.io/v2/remove-background', { ... });
```
---
### Pitfall 10: Missing Webhook Signature Verification
```typescript
// WRONG: Trust any incoming request (attackers can forge events)
app.post('/webhooks/adobe', (req, res) => {
processEvent(req.body);
res.sendStatus(200);
});
// RIGHT: Verify RSA-SHA256 signature from Adobe I/O Events
app.post('/webhooks/adobe', express.raw({ type: 'application/json' }), async (req, res) => {
// Adobe uses RSA-SHA256 digital signatures (NOT HMAC)
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.toString()));
res.sendStatus(200);
});
```
## Quick Pitfall Scanner
```bash
# Run against your codebase
echo "=== Adobe Pitfall Scan ==="
# 1. JWT credentials (deprecated)
grep -rn "jsonwebtoken\|jwt\.sign\|RS256" --include="*.ts" --include="*.js" src/ && echo "FOUND: JWT auth (deprecated)" ||Related in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.